servcraft 0.1.0 → 0.1.3
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/.claude/settings.local.json +30 -0
- package/.github/CODEOWNERS +18 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
- package/.github/dependabot.yml +59 -0
- package/.github/workflows/ci.yml +188 -0
- package/.github/workflows/release.yml +195 -0
- package/AUDIT.md +602 -0
- package/LICENSE +21 -0
- package/README.md +1102 -1
- package/dist/cli/index.cjs +2026 -2168
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2026 -2168
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +595 -616
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -52
- package/dist/index.d.ts +114 -52
- package/dist/index.js +595 -616
- package/dist/index.js.map +1 -1
- package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
- package/docs/DATABASE_MULTI_ORM.md +399 -0
- package/docs/PHASE1_BREAKDOWN.md +346 -0
- package/docs/PROGRESS.md +550 -0
- package/docs/modules/ANALYTICS.md +226 -0
- package/docs/modules/API-VERSIONING.md +252 -0
- package/docs/modules/AUDIT.md +192 -0
- package/docs/modules/AUTH.md +431 -0
- package/docs/modules/CACHE.md +346 -0
- package/docs/modules/EMAIL.md +254 -0
- package/docs/modules/FEATURE-FLAG.md +291 -0
- package/docs/modules/I18N.md +294 -0
- package/docs/modules/MEDIA-PROCESSING.md +281 -0
- package/docs/modules/MFA.md +266 -0
- package/docs/modules/NOTIFICATION.md +311 -0
- package/docs/modules/OAUTH.md +237 -0
- package/docs/modules/PAYMENT.md +804 -0
- package/docs/modules/QUEUE.md +540 -0
- package/docs/modules/RATE-LIMIT.md +339 -0
- package/docs/modules/SEARCH.md +288 -0
- package/docs/modules/SECURITY.md +327 -0
- package/docs/modules/SESSION.md +382 -0
- package/docs/modules/SWAGGER.md +305 -0
- package/docs/modules/UPLOAD.md +296 -0
- package/docs/modules/USER.md +505 -0
- package/docs/modules/VALIDATION.md +294 -0
- package/docs/modules/WEBHOOK.md +270 -0
- package/docs/modules/WEBSOCKET.md +691 -0
- package/package.json +53 -38
- package/prisma/schema.prisma +395 -1
- package/src/cli/commands/add-module.ts +520 -87
- package/src/cli/commands/db.ts +3 -4
- package/src/cli/commands/docs.ts +256 -6
- package/src/cli/commands/generate.ts +12 -19
- package/src/cli/commands/init.ts +384 -214
- package/src/cli/index.ts +0 -4
- package/src/cli/templates/repository.ts +6 -1
- package/src/cli/templates/routes.ts +6 -21
- package/src/cli/utils/docs-generator.ts +6 -7
- package/src/cli/utils/env-manager.ts +717 -0
- package/src/cli/utils/field-parser.ts +16 -7
- package/src/cli/utils/interactive-prompt.ts +223 -0
- package/src/cli/utils/template-manager.ts +346 -0
- package/src/config/database.config.ts +183 -0
- package/src/config/env.ts +0 -10
- package/src/config/index.ts +0 -14
- package/src/core/server.ts +1 -1
- package/src/database/adapters/mongoose.adapter.ts +132 -0
- package/src/database/adapters/prisma.adapter.ts +118 -0
- package/src/database/connection.ts +190 -0
- package/src/database/interfaces/database.interface.ts +85 -0
- package/src/database/interfaces/index.ts +7 -0
- package/src/database/interfaces/repository.interface.ts +129 -0
- package/src/database/models/mongoose/index.ts +7 -0
- package/src/database/models/mongoose/payment.schema.ts +347 -0
- package/src/database/models/mongoose/user.schema.ts +154 -0
- package/src/database/prisma.ts +1 -4
- package/src/database/redis.ts +101 -0
- package/src/database/repositories/mongoose/index.ts +7 -0
- package/src/database/repositories/mongoose/payment.repository.ts +380 -0
- package/src/database/repositories/mongoose/user.repository.ts +255 -0
- package/src/database/seed.ts +6 -1
- package/src/index.ts +9 -20
- package/src/middleware/security.ts +2 -6
- package/src/modules/analytics/analytics.routes.ts +80 -0
- package/src/modules/analytics/analytics.service.ts +364 -0
- package/src/modules/analytics/index.ts +18 -0
- package/src/modules/analytics/types.ts +180 -0
- package/src/modules/api-versioning/index.ts +15 -0
- package/src/modules/api-versioning/types.ts +86 -0
- package/src/modules/api-versioning/versioning.middleware.ts +120 -0
- package/src/modules/api-versioning/versioning.routes.ts +54 -0
- package/src/modules/api-versioning/versioning.service.ts +189 -0
- package/src/modules/audit/audit.repository.ts +206 -0
- package/src/modules/audit/audit.service.ts +27 -59
- package/src/modules/auth/auth.controller.ts +2 -2
- package/src/modules/auth/auth.middleware.ts +3 -9
- package/src/modules/auth/auth.routes.ts +10 -107
- package/src/modules/auth/auth.service.ts +126 -23
- package/src/modules/auth/index.ts +3 -4
- package/src/modules/cache/cache.service.ts +367 -0
- package/src/modules/cache/index.ts +10 -0
- package/src/modules/cache/types.ts +44 -0
- package/src/modules/email/email.service.ts +3 -10
- package/src/modules/email/templates.ts +2 -8
- package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
- package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
- package/src/modules/feature-flag/feature-flag.service.ts +566 -0
- package/src/modules/feature-flag/index.ts +20 -0
- package/src/modules/feature-flag/types.ts +192 -0
- package/src/modules/i18n/i18n.middleware.ts +186 -0
- package/src/modules/i18n/i18n.routes.ts +191 -0
- package/src/modules/i18n/i18n.service.ts +456 -0
- package/src/modules/i18n/index.ts +18 -0
- package/src/modules/i18n/types.ts +118 -0
- package/src/modules/media-processing/index.ts +17 -0
- package/src/modules/media-processing/media-processing.routes.ts +111 -0
- package/src/modules/media-processing/media-processing.service.ts +245 -0
- package/src/modules/media-processing/types.ts +156 -0
- package/src/modules/mfa/index.ts +20 -0
- package/src/modules/mfa/mfa.repository.ts +206 -0
- package/src/modules/mfa/mfa.routes.ts +595 -0
- package/src/modules/mfa/mfa.service.ts +572 -0
- package/src/modules/mfa/totp.ts +150 -0
- package/src/modules/mfa/types.ts +57 -0
- package/src/modules/notification/index.ts +20 -0
- package/src/modules/notification/notification.repository.ts +356 -0
- package/src/modules/notification/notification.service.ts +483 -0
- package/src/modules/notification/types.ts +119 -0
- package/src/modules/oauth/index.ts +20 -0
- package/src/modules/oauth/oauth.repository.ts +219 -0
- package/src/modules/oauth/oauth.routes.ts +446 -0
- package/src/modules/oauth/oauth.service.ts +293 -0
- package/src/modules/oauth/providers/apple.provider.ts +250 -0
- package/src/modules/oauth/providers/facebook.provider.ts +181 -0
- package/src/modules/oauth/providers/github.provider.ts +248 -0
- package/src/modules/oauth/providers/google.provider.ts +189 -0
- package/src/modules/oauth/providers/twitter.provider.ts +214 -0
- package/src/modules/oauth/types.ts +94 -0
- package/src/modules/payment/index.ts +19 -0
- package/src/modules/payment/payment.repository.ts +733 -0
- package/src/modules/payment/payment.routes.ts +390 -0
- package/src/modules/payment/payment.service.ts +354 -0
- package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
- package/src/modules/payment/providers/paypal.provider.ts +190 -0
- package/src/modules/payment/providers/stripe.provider.ts +215 -0
- package/src/modules/payment/types.ts +140 -0
- package/src/modules/queue/cron.ts +438 -0
- package/src/modules/queue/index.ts +87 -0
- package/src/modules/queue/queue.routes.ts +600 -0
- package/src/modules/queue/queue.service.ts +842 -0
- package/src/modules/queue/types.ts +222 -0
- package/src/modules/queue/workers.ts +366 -0
- package/src/modules/rate-limit/index.ts +59 -0
- package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
- package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
- package/src/modules/rate-limit/rate-limit.service.ts +348 -0
- package/src/modules/rate-limit/stores/memory.store.ts +165 -0
- package/src/modules/rate-limit/stores/redis.store.ts +322 -0
- package/src/modules/rate-limit/types.ts +153 -0
- package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
- package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
- package/src/modules/search/adapters/memory.adapter.ts +278 -0
- package/src/modules/search/index.ts +21 -0
- package/src/modules/search/search.service.ts +234 -0
- package/src/modules/search/types.ts +214 -0
- package/src/modules/security/index.ts +40 -0
- package/src/modules/security/sanitize.ts +223 -0
- package/src/modules/security/security-audit.service.ts +388 -0
- package/src/modules/security/security.middleware.ts +398 -0
- package/src/modules/session/index.ts +3 -0
- package/src/modules/session/session.repository.ts +159 -0
- package/src/modules/session/session.service.ts +340 -0
- package/src/modules/session/types.ts +38 -0
- package/src/modules/swagger/index.ts +7 -1
- package/src/modules/swagger/schema-builder.ts +16 -4
- package/src/modules/swagger/swagger.service.ts +9 -10
- package/src/modules/swagger/types.ts +0 -2
- package/src/modules/upload/index.ts +14 -0
- package/src/modules/upload/types.ts +83 -0
- package/src/modules/upload/upload.repository.ts +199 -0
- package/src/modules/upload/upload.routes.ts +311 -0
- package/src/modules/upload/upload.service.ts +448 -0
- package/src/modules/user/index.ts +3 -3
- package/src/modules/user/user.controller.ts +15 -9
- package/src/modules/user/user.repository.ts +237 -113
- package/src/modules/user/user.routes.ts +39 -164
- package/src/modules/user/user.service.ts +4 -3
- package/src/modules/validation/validator.ts +12 -17
- package/src/modules/webhook/index.ts +91 -0
- package/src/modules/webhook/retry.ts +196 -0
- package/src/modules/webhook/signature.ts +135 -0
- package/src/modules/webhook/types.ts +181 -0
- package/src/modules/webhook/webhook.repository.ts +358 -0
- package/src/modules/webhook/webhook.routes.ts +442 -0
- package/src/modules/webhook/webhook.service.ts +457 -0
- package/src/modules/websocket/features.ts +504 -0
- package/src/modules/websocket/index.ts +106 -0
- package/src/modules/websocket/middlewares.ts +298 -0
- package/src/modules/websocket/types.ts +181 -0
- package/src/modules/websocket/websocket.service.ts +692 -0
- package/src/utils/errors.ts +7 -0
- package/src/utils/pagination.ts +4 -1
- package/tests/helpers/db-check.ts +79 -0
- package/tests/integration/auth-redis.test.ts +94 -0
- package/tests/integration/cache-redis.test.ts +387 -0
- package/tests/integration/mongoose-repositories.test.ts +410 -0
- package/tests/integration/payment-prisma.test.ts +637 -0
- package/tests/integration/queue-bullmq.test.ts +417 -0
- package/tests/integration/user-prisma.test.ts +441 -0
- package/tests/integration/websocket-socketio.test.ts +552 -0
- package/tests/setup.ts +11 -9
- package/vitest.config.ts +3 -8
- package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
- package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
- package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
- package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
- package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# Swagger Module
|
|
2
|
+
|
|
3
|
+
OpenAPI 3.0 documentation with Swagger UI integration.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **OpenAPI 3.0** - Full OpenAPI specification support
|
|
8
|
+
- **Swagger UI** - Interactive API documentation at `/docs`
|
|
9
|
+
- **JWT Auth** - Bearer token authentication support
|
|
10
|
+
- **Common Schemas** - Pre-built response schemas
|
|
11
|
+
- **Zod Integration** - Convert Zod schemas to JSON Schema
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Basic Setup
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import Fastify from 'fastify';
|
|
19
|
+
import { registerSwagger } from 'servcraft/modules/swagger';
|
|
20
|
+
|
|
21
|
+
const app = Fastify();
|
|
22
|
+
|
|
23
|
+
await registerSwagger(app, {
|
|
24
|
+
title: 'My API',
|
|
25
|
+
description: 'API documentation',
|
|
26
|
+
version: '1.0.0',
|
|
27
|
+
tags: [
|
|
28
|
+
{ name: 'Auth', description: 'Authentication endpoints' },
|
|
29
|
+
{ name: 'Users', description: 'User management' },
|
|
30
|
+
{ name: 'Products', description: 'Product catalog' },
|
|
31
|
+
],
|
|
32
|
+
servers: [
|
|
33
|
+
{ url: 'http://localhost:3000', description: 'Development' },
|
|
34
|
+
{ url: 'https://api.example.com', description: 'Production' },
|
|
35
|
+
],
|
|
36
|
+
contact: {
|
|
37
|
+
name: 'API Support',
|
|
38
|
+
email: 'support@example.com',
|
|
39
|
+
},
|
|
40
|
+
license: {
|
|
41
|
+
name: 'MIT',
|
|
42
|
+
url: 'https://opensource.org/licenses/MIT',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Documentation available at http://localhost:3000/docs
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Route Documentation
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
fastify.post('/api/users', {
|
|
53
|
+
schema: {
|
|
54
|
+
description: 'Create a new user',
|
|
55
|
+
tags: ['Users'],
|
|
56
|
+
summary: 'Create user',
|
|
57
|
+
body: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
required: ['email', 'password', 'name'],
|
|
60
|
+
properties: {
|
|
61
|
+
email: { type: 'string', format: 'email' },
|
|
62
|
+
password: { type: 'string', minLength: 8 },
|
|
63
|
+
name: { type: 'string' },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
response: {
|
|
67
|
+
201: {
|
|
68
|
+
description: 'User created successfully',
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {
|
|
71
|
+
success: { type: 'boolean' },
|
|
72
|
+
data: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
id: { type: 'string', format: 'uuid' },
|
|
76
|
+
email: { type: 'string' },
|
|
77
|
+
name: { type: 'string' },
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
400: commonResponses.error,
|
|
83
|
+
},
|
|
84
|
+
security: [{ bearerAuth: [] }],
|
|
85
|
+
},
|
|
86
|
+
}, async (request, reply) => {
|
|
87
|
+
// Handler
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Common Response Schemas
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { commonResponses, paginationQuery, idParam } from 'servcraft/modules/swagger';
|
|
95
|
+
|
|
96
|
+
// Success response
|
|
97
|
+
commonResponses.success
|
|
98
|
+
// { success: boolean, data: object }
|
|
99
|
+
|
|
100
|
+
// Error response
|
|
101
|
+
commonResponses.error
|
|
102
|
+
// { success: boolean, message: string, errors: object }
|
|
103
|
+
|
|
104
|
+
// Unauthorized
|
|
105
|
+
commonResponses.unauthorized
|
|
106
|
+
// { success: boolean, message: 'Unauthorized' }
|
|
107
|
+
|
|
108
|
+
// Not found
|
|
109
|
+
commonResponses.notFound
|
|
110
|
+
// { success: boolean, message: 'Resource not found' }
|
|
111
|
+
|
|
112
|
+
// Paginated response
|
|
113
|
+
commonResponses.paginated
|
|
114
|
+
// { success: boolean, data: { data: [], meta: { total, page, ... } } }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Query Parameters
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// Pagination query parameters
|
|
121
|
+
fastify.get('/api/users', {
|
|
122
|
+
schema: {
|
|
123
|
+
querystring: paginationQuery,
|
|
124
|
+
// Adds: page, limit, sortBy, sortOrder, search
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// ID parameter
|
|
129
|
+
fastify.get('/api/users/:id', {
|
|
130
|
+
schema: {
|
|
131
|
+
params: idParam,
|
|
132
|
+
// Adds: id (uuid)
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Authentication
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// Protected route
|
|
141
|
+
fastify.get('/api/profile', {
|
|
142
|
+
schema: {
|
|
143
|
+
tags: ['Users'],
|
|
144
|
+
security: [{ bearerAuth: [] }],
|
|
145
|
+
response: {
|
|
146
|
+
200: userSchema,
|
|
147
|
+
401: commonResponses.unauthorized,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Public route (no security)
|
|
153
|
+
fastify.get('/api/public', {
|
|
154
|
+
schema: {
|
|
155
|
+
tags: ['Public'],
|
|
156
|
+
// No security property = public endpoint
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Configuration
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
interface SwaggerConfig {
|
|
165
|
+
title: string;
|
|
166
|
+
description?: string;
|
|
167
|
+
version: string;
|
|
168
|
+
tags?: Array<{
|
|
169
|
+
name: string;
|
|
170
|
+
description?: string;
|
|
171
|
+
}>;
|
|
172
|
+
servers?: Array<{
|
|
173
|
+
url: string;
|
|
174
|
+
description?: string;
|
|
175
|
+
}>;
|
|
176
|
+
contact?: {
|
|
177
|
+
name?: string;
|
|
178
|
+
email?: string;
|
|
179
|
+
url?: string;
|
|
180
|
+
};
|
|
181
|
+
license?: {
|
|
182
|
+
name: string;
|
|
183
|
+
url?: string;
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Schema Examples
|
|
189
|
+
|
|
190
|
+
### User Schema
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
const userSchema = {
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: {
|
|
196
|
+
id: { type: 'string', format: 'uuid' },
|
|
197
|
+
email: { type: 'string', format: 'email' },
|
|
198
|
+
name: { type: 'string' },
|
|
199
|
+
role: { type: 'string', enum: ['user', 'admin'] },
|
|
200
|
+
createdAt: { type: 'string', format: 'date-time' },
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Request Body
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
const createUserBody = {
|
|
209
|
+
type: 'object',
|
|
210
|
+
required: ['email', 'password'],
|
|
211
|
+
properties: {
|
|
212
|
+
email: {
|
|
213
|
+
type: 'string',
|
|
214
|
+
format: 'email',
|
|
215
|
+
description: 'User email address',
|
|
216
|
+
},
|
|
217
|
+
password: {
|
|
218
|
+
type: 'string',
|
|
219
|
+
minLength: 8,
|
|
220
|
+
description: 'Password (min 8 characters)',
|
|
221
|
+
},
|
|
222
|
+
name: {
|
|
223
|
+
type: 'string',
|
|
224
|
+
maxLength: 100,
|
|
225
|
+
description: 'Display name',
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Array Response
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const usersListResponse = {
|
|
235
|
+
type: 'object',
|
|
236
|
+
properties: {
|
|
237
|
+
success: { type: 'boolean' },
|
|
238
|
+
data: {
|
|
239
|
+
type: 'array',
|
|
240
|
+
items: userSchema,
|
|
241
|
+
},
|
|
242
|
+
meta: {
|
|
243
|
+
type: 'object',
|
|
244
|
+
properties: {
|
|
245
|
+
total: { type: 'integer' },
|
|
246
|
+
page: { type: 'integer' },
|
|
247
|
+
limit: { type: 'integer' },
|
|
248
|
+
totalPages: { type: 'integer' },
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Complete Route Example
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
fastify.route({
|
|
259
|
+
method: 'GET',
|
|
260
|
+
url: '/api/products',
|
|
261
|
+
schema: {
|
|
262
|
+
description: 'Get all products with pagination and filtering',
|
|
263
|
+
tags: ['Products'],
|
|
264
|
+
summary: 'List products',
|
|
265
|
+
querystring: {
|
|
266
|
+
type: 'object',
|
|
267
|
+
properties: {
|
|
268
|
+
page: { type: 'integer', default: 1 },
|
|
269
|
+
limit: { type: 'integer', default: 20 },
|
|
270
|
+
category: { type: 'string', description: 'Filter by category' },
|
|
271
|
+
minPrice: { type: 'number', description: 'Minimum price' },
|
|
272
|
+
maxPrice: { type: 'number', description: 'Maximum price' },
|
|
273
|
+
search: { type: 'string', description: 'Search term' },
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
response: {
|
|
277
|
+
200: {
|
|
278
|
+
description: 'List of products',
|
|
279
|
+
...commonResponses.paginated,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
handler: async (request, reply) => {
|
|
284
|
+
// Implementation
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Swagger UI Features
|
|
290
|
+
|
|
291
|
+
The documentation UI at `/docs` includes:
|
|
292
|
+
- **Try It Out** - Execute API calls directly
|
|
293
|
+
- **Filter** - Search endpoints
|
|
294
|
+
- **Expand/Collapse** - Organize by tags
|
|
295
|
+
- **Request Duration** - Show response times
|
|
296
|
+
- **Authorization** - Set bearer token
|
|
297
|
+
|
|
298
|
+
## Best Practices
|
|
299
|
+
|
|
300
|
+
1. **Consistent Tags** - Group related endpoints
|
|
301
|
+
2. **Detailed Descriptions** - Document parameters and responses
|
|
302
|
+
3. **Examples** - Provide example values
|
|
303
|
+
4. **Error Responses** - Document all error cases
|
|
304
|
+
5. **Security** - Mark authenticated endpoints
|
|
305
|
+
6. **Versioning** - Include version in title
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Upload Module
|
|
2
|
+
|
|
3
|
+
File upload service with multiple storage providers and image transformation support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multiple Providers** - Local, AWS S3, Google Cloud Storage, Cloudinary
|
|
8
|
+
- **File Validation** - MIME type and size validation
|
|
9
|
+
- **Image Transformation** - Resize, crop, format conversion
|
|
10
|
+
- **Signed URLs** - Secure temporary URLs for private files
|
|
11
|
+
- **User Storage Tracking** - Track storage usage per user
|
|
12
|
+
|
|
13
|
+
## Supported Providers
|
|
14
|
+
|
|
15
|
+
| Provider | Use Case |
|
|
16
|
+
|----------|----------|
|
|
17
|
+
| Local | Development, simple deployments |
|
|
18
|
+
| AWS S3 | Production, scalable storage |
|
|
19
|
+
| Google Cloud Storage | GCP infrastructure |
|
|
20
|
+
| Cloudinary | Image optimization, transformations |
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Configuration
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { createUploadService } from 'servcraft/modules/upload';
|
|
28
|
+
|
|
29
|
+
// Local storage
|
|
30
|
+
const uploadService = createUploadService({
|
|
31
|
+
provider: 'local',
|
|
32
|
+
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
33
|
+
allowedMimeTypes: ['image/jpeg', 'image/png', 'application/pdf'],
|
|
34
|
+
local: {
|
|
35
|
+
uploadDir: './uploads',
|
|
36
|
+
publicPath: '/uploads',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// AWS S3
|
|
41
|
+
const s3Service = createUploadService({
|
|
42
|
+
provider: 's3',
|
|
43
|
+
maxFileSize: 50 * 1024 * 1024,
|
|
44
|
+
s3: {
|
|
45
|
+
bucket: 'my-bucket',
|
|
46
|
+
region: 'us-east-1',
|
|
47
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
48
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Cloudinary
|
|
53
|
+
const cloudinaryService = createUploadService({
|
|
54
|
+
provider: 'cloudinary',
|
|
55
|
+
cloudinary: {
|
|
56
|
+
cloudName: process.env.CLOUDINARY_CLOUD_NAME!,
|
|
57
|
+
apiKey: process.env.CLOUDINARY_API_KEY!,
|
|
58
|
+
apiSecret: process.env.CLOUDINARY_API_SECRET!,
|
|
59
|
+
folder: 'uploads',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Single File Upload
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// Fastify route example
|
|
68
|
+
fastify.post('/upload', async (request, reply) => {
|
|
69
|
+
const file = await request.file();
|
|
70
|
+
|
|
71
|
+
const uploaded = await uploadService.upload(file, {
|
|
72
|
+
folder: 'avatars',
|
|
73
|
+
filename: `user-${userId}-avatar`,
|
|
74
|
+
isPublic: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return { url: uploaded.url, id: uploaded.id };
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Multiple Files Upload
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
fastify.post('/upload-multiple', async (request, reply) => {
|
|
85
|
+
const files = await request.files();
|
|
86
|
+
const fileArray = [];
|
|
87
|
+
|
|
88
|
+
for await (const file of files) {
|
|
89
|
+
fileArray.push(file);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const uploaded = await uploadService.uploadMultiple(fileArray, {
|
|
93
|
+
folder: 'documents',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return uploaded.map(f => ({ id: f.id, url: f.url }));
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Image Transformation
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const uploaded = await uploadService.upload(file, {
|
|
104
|
+
transform: {
|
|
105
|
+
width: 300,
|
|
106
|
+
height: 300,
|
|
107
|
+
fit: 'cover',
|
|
108
|
+
format: 'webp',
|
|
109
|
+
quality: 80,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### File Management
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// Get file by ID
|
|
118
|
+
const file = await uploadService.getFile(fileId);
|
|
119
|
+
|
|
120
|
+
// Delete file
|
|
121
|
+
await uploadService.deleteFile(fileId);
|
|
122
|
+
|
|
123
|
+
// Get signed URL (for private files)
|
|
124
|
+
const signedUrl = await uploadService.getSignedUrl(fileId, 3600); // 1 hour
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### User Storage Management
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// Get user's files
|
|
131
|
+
const files = await uploadService.getFilesByUser(userId, {
|
|
132
|
+
limit: 20,
|
|
133
|
+
offset: 0,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Get storage usage
|
|
137
|
+
const usage = await uploadService.getUserStorageUsage(userId);
|
|
138
|
+
// { totalSize: 15728640, fileCount: 12 }
|
|
139
|
+
|
|
140
|
+
// Delete all user files
|
|
141
|
+
await uploadService.deleteUserFiles(userId);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Configuration Types
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
interface UploadConfig {
|
|
148
|
+
provider: 'local' | 's3' | 'cloudinary' | 'gcs';
|
|
149
|
+
maxFileSize: number; // in bytes
|
|
150
|
+
allowedMimeTypes: string[];
|
|
151
|
+
|
|
152
|
+
local?: {
|
|
153
|
+
uploadDir: string;
|
|
154
|
+
publicPath: string;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
s3?: {
|
|
158
|
+
bucket: string;
|
|
159
|
+
region: string;
|
|
160
|
+
accessKeyId: string;
|
|
161
|
+
secretAccessKey: string;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
cloudinary?: {
|
|
165
|
+
cloudName: string;
|
|
166
|
+
apiKey: string;
|
|
167
|
+
apiSecret: string;
|
|
168
|
+
folder?: string;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
gcs?: {
|
|
172
|
+
projectId: string;
|
|
173
|
+
bucket: string;
|
|
174
|
+
keyFilename?: string;
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
interface UploadOptions {
|
|
179
|
+
folder?: string;
|
|
180
|
+
filename?: string;
|
|
181
|
+
isPublic?: boolean;
|
|
182
|
+
transform?: ImageTransformOptions;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface ImageTransformOptions {
|
|
186
|
+
width?: number;
|
|
187
|
+
height?: number;
|
|
188
|
+
fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
|
|
189
|
+
format?: 'jpeg' | 'png' | 'webp' | 'avif';
|
|
190
|
+
quality?: number;
|
|
191
|
+
grayscale?: boolean;
|
|
192
|
+
blur?: number;
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Response Types
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
interface UploadedFile {
|
|
200
|
+
id: string;
|
|
201
|
+
originalName: string;
|
|
202
|
+
filename: string;
|
|
203
|
+
mimetype: string;
|
|
204
|
+
size: number;
|
|
205
|
+
path: string;
|
|
206
|
+
url: string;
|
|
207
|
+
provider: string;
|
|
208
|
+
bucket?: string;
|
|
209
|
+
userId?: string;
|
|
210
|
+
createdAt: Date;
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Fastify Integration
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import fastifyMultipart from '@fastify/multipart';
|
|
218
|
+
|
|
219
|
+
// Register multipart
|
|
220
|
+
fastify.register(fastifyMultipart, {
|
|
221
|
+
limits: {
|
|
222
|
+
fileSize: 10 * 1024 * 1024, // 10MB
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Upload route with validation
|
|
227
|
+
fastify.post('/api/files', async (request, reply) => {
|
|
228
|
+
const file = await request.file();
|
|
229
|
+
|
|
230
|
+
if (!file) {
|
|
231
|
+
return reply.status(400).send({ error: 'No file provided' });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const uploaded = await uploadService.upload(file, {
|
|
236
|
+
folder: 'documents',
|
|
237
|
+
isPublic: false,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
id: uploaded.id,
|
|
242
|
+
url: uploaded.url,
|
|
243
|
+
size: uploaded.size,
|
|
244
|
+
mimetype: uploaded.mimetype,
|
|
245
|
+
};
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (error.message.includes('File type not allowed')) {
|
|
248
|
+
return reply.status(400).send({ error: error.message });
|
|
249
|
+
}
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Database Schema
|
|
256
|
+
|
|
257
|
+
```sql
|
|
258
|
+
CREATE TABLE uploaded_files (
|
|
259
|
+
id UUID PRIMARY KEY,
|
|
260
|
+
user_id TEXT,
|
|
261
|
+
original_name TEXT NOT NULL,
|
|
262
|
+
filename TEXT NOT NULL,
|
|
263
|
+
mimetype TEXT NOT NULL,
|
|
264
|
+
size INTEGER NOT NULL,
|
|
265
|
+
path TEXT NOT NULL,
|
|
266
|
+
url TEXT NOT NULL,
|
|
267
|
+
provider TEXT NOT NULL,
|
|
268
|
+
bucket TEXT,
|
|
269
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
CREATE INDEX idx_uploaded_files_user ON uploaded_files(user_id);
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Default Allowed MIME Types
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
[
|
|
279
|
+
'image/jpeg',
|
|
280
|
+
'image/png',
|
|
281
|
+
'image/gif',
|
|
282
|
+
'image/webp',
|
|
283
|
+
'application/pdf',
|
|
284
|
+
'text/plain',
|
|
285
|
+
'application/json',
|
|
286
|
+
]
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Best Practices
|
|
290
|
+
|
|
291
|
+
1. **Validate on Server** - Always validate file types server-side
|
|
292
|
+
2. **Use Signed URLs** - Use signed URLs for sensitive files
|
|
293
|
+
3. **Set Size Limits** - Configure appropriate file size limits
|
|
294
|
+
4. **Clean Up** - Implement cleanup for orphaned files
|
|
295
|
+
5. **CDN** - Use CDN for public files in production
|
|
296
|
+
6. **Virus Scanning** - Consider virus scanning for user uploads
|