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,111 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import type { Request, Response } from 'express';
|
|
3
|
+
import type { MediaProcessingService } from './media-processing.service.js';
|
|
4
|
+
import type { ImageOperation, VideoOperation, MediaType, ThumbnailOptions } from './types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create media processing routes
|
|
8
|
+
*/
|
|
9
|
+
export function createMediaProcessingRoutes(mediaService: MediaProcessingService): Router {
|
|
10
|
+
const router = Router();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Process image
|
|
14
|
+
* POST /image
|
|
15
|
+
*/
|
|
16
|
+
router.post('/image', async (req: Request, res: Response) => {
|
|
17
|
+
const { source, output, operations } = req.body as {
|
|
18
|
+
source: string;
|
|
19
|
+
output: string;
|
|
20
|
+
operations: ImageOperation[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const result = await mediaService.processImage(source, output, operations);
|
|
24
|
+
res.json(result);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Process video
|
|
29
|
+
* POST /video
|
|
30
|
+
*/
|
|
31
|
+
router.post('/video', async (req: Request, res: Response) => {
|
|
32
|
+
const { source, output, operations } = req.body as {
|
|
33
|
+
source: string;
|
|
34
|
+
output: string;
|
|
35
|
+
operations: VideoOperation[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const result = await mediaService.processVideo(source, output, operations);
|
|
39
|
+
res.json(result);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create processing job
|
|
44
|
+
* POST /jobs
|
|
45
|
+
*/
|
|
46
|
+
router.post('/jobs', async (req: Request, res: Response) => {
|
|
47
|
+
const { mediaType, source, output, operations } = req.body as {
|
|
48
|
+
mediaType: MediaType;
|
|
49
|
+
source: string;
|
|
50
|
+
output: string;
|
|
51
|
+
operations: (ImageOperation | VideoOperation)[];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const job = await mediaService.createJob(mediaType, source, output, operations);
|
|
55
|
+
res.status(201).json(job);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get job status
|
|
60
|
+
* GET /jobs/:id
|
|
61
|
+
*/
|
|
62
|
+
router.get('/jobs/:id', async (req: Request, res: Response) => {
|
|
63
|
+
const jobId = req.params.id;
|
|
64
|
+
if (!jobId) {
|
|
65
|
+
res.status(400).json({ error: 'Job ID is required' });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const job = await mediaService.getJob(jobId);
|
|
70
|
+
|
|
71
|
+
if (!job) {
|
|
72
|
+
res.status(404).json({ error: 'Job not found' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
res.json(job);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get media info
|
|
81
|
+
* POST /info
|
|
82
|
+
*/
|
|
83
|
+
router.post('/info', async (req: Request, res: Response) => {
|
|
84
|
+
const { filePath } = req.body as { filePath: string };
|
|
85
|
+
|
|
86
|
+
if (!filePath) {
|
|
87
|
+
res.status(400).json({ error: 'filePath is required' });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const info = await mediaService.getMediaInfo(filePath);
|
|
92
|
+
res.json(info);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Generate thumbnail
|
|
97
|
+
* POST /thumbnail
|
|
98
|
+
*/
|
|
99
|
+
router.post('/thumbnail', async (req: Request, res: Response) => {
|
|
100
|
+
const { source, output, options } = req.body as {
|
|
101
|
+
source: string;
|
|
102
|
+
output: string;
|
|
103
|
+
options?: ThumbnailOptions;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const result = await mediaService.generateThumbnail(source, output, options);
|
|
107
|
+
res.json(result);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return router;
|
|
111
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { logger } from '../../core/logger.js';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import type {
|
|
4
|
+
MediaProcessingConfig,
|
|
5
|
+
ProcessingJob,
|
|
6
|
+
ImageOperation,
|
|
7
|
+
VideoOperation,
|
|
8
|
+
MediaInfo,
|
|
9
|
+
ThumbnailOptions,
|
|
10
|
+
ProcessingResult,
|
|
11
|
+
MediaType,
|
|
12
|
+
} from './types.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Media Processing Service
|
|
16
|
+
* Image and video processing with FFmpeg
|
|
17
|
+
*/
|
|
18
|
+
export class MediaProcessingService {
|
|
19
|
+
private config: MediaProcessingConfig;
|
|
20
|
+
private jobs = new Map<string, ProcessingJob>();
|
|
21
|
+
private queue: ProcessingJob[] = [];
|
|
22
|
+
private processing = 0;
|
|
23
|
+
|
|
24
|
+
constructor(config: MediaProcessingConfig = {}) {
|
|
25
|
+
this.config = {
|
|
26
|
+
ffmpegPath: 'ffmpeg',
|
|
27
|
+
ffprobePath: 'ffprobe',
|
|
28
|
+
tempDir: './temp',
|
|
29
|
+
maxConcurrent: 3,
|
|
30
|
+
gpuAcceleration: false,
|
|
31
|
+
...config,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
logger.info('Media processing service initialized');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Process image
|
|
39
|
+
*/
|
|
40
|
+
async processImage(
|
|
41
|
+
source: string,
|
|
42
|
+
output: string,
|
|
43
|
+
operations: ImageOperation[]
|
|
44
|
+
): Promise<ProcessingResult> {
|
|
45
|
+
const startTime = Date.now();
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Note: In production, use Sharp library for image processing
|
|
49
|
+
// This is a placeholder implementation
|
|
50
|
+
logger.info({ source, output, operations }, 'Processing image');
|
|
51
|
+
|
|
52
|
+
// Simulate processing
|
|
53
|
+
await this.simulateProcessing();
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
outputPath: output,
|
|
58
|
+
processingTime: Date.now() - startTime,
|
|
59
|
+
};
|
|
60
|
+
} catch (error) {
|
|
61
|
+
logger.error({ source, error }, 'Image processing failed');
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
processingTime: Date.now() - startTime,
|
|
65
|
+
error: error instanceof Error ? error.message : 'Processing failed',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Process video
|
|
72
|
+
*/
|
|
73
|
+
async processVideo(
|
|
74
|
+
source: string,
|
|
75
|
+
output: string,
|
|
76
|
+
operations: VideoOperation[]
|
|
77
|
+
): Promise<ProcessingResult> {
|
|
78
|
+
const startTime = Date.now();
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
// Note: In production, use fluent-ffmpeg or node-ffmpeg
|
|
82
|
+
logger.info({ source, output, operations }, 'Processing video');
|
|
83
|
+
|
|
84
|
+
// Simulate processing
|
|
85
|
+
await this.simulateProcessing();
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
outputPath: output,
|
|
90
|
+
processingTime: Date.now() - startTime,
|
|
91
|
+
};
|
|
92
|
+
} catch (error) {
|
|
93
|
+
logger.error({ source, error }, 'Video processing failed');
|
|
94
|
+
return {
|
|
95
|
+
success: false,
|
|
96
|
+
processingTime: Date.now() - startTime,
|
|
97
|
+
error: error instanceof Error ? error.message : 'Processing failed',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Create job
|
|
104
|
+
*/
|
|
105
|
+
async createJob(
|
|
106
|
+
mediaType: MediaType,
|
|
107
|
+
source: string,
|
|
108
|
+
output: string,
|
|
109
|
+
operations: (ImageOperation | VideoOperation)[]
|
|
110
|
+
): Promise<ProcessingJob> {
|
|
111
|
+
const job: ProcessingJob = {
|
|
112
|
+
id: randomUUID(),
|
|
113
|
+
mediaType,
|
|
114
|
+
source,
|
|
115
|
+
output,
|
|
116
|
+
operations,
|
|
117
|
+
status: 'pending',
|
|
118
|
+
progress: 0,
|
|
119
|
+
createdAt: new Date(),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
this.jobs.set(job.id, job);
|
|
123
|
+
this.queue.push(job);
|
|
124
|
+
|
|
125
|
+
this.processQueue();
|
|
126
|
+
|
|
127
|
+
logger.info({ jobId: job.id }, 'Processing job created');
|
|
128
|
+
|
|
129
|
+
return job;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get job status
|
|
134
|
+
*/
|
|
135
|
+
async getJob(jobId: string): Promise<ProcessingJob | null> {
|
|
136
|
+
return this.jobs.get(jobId) || null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get media info
|
|
141
|
+
*/
|
|
142
|
+
async getMediaInfo(filePath: string): Promise<MediaInfo> {
|
|
143
|
+
// Note: In production, use ffprobe
|
|
144
|
+
logger.info({ filePath }, 'Getting media info');
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
path: filePath,
|
|
148
|
+
type: 'image',
|
|
149
|
+
format: 'jpeg',
|
|
150
|
+
size: 0,
|
|
151
|
+
width: 1920,
|
|
152
|
+
height: 1080,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Generate thumbnail
|
|
158
|
+
*/
|
|
159
|
+
async generateThumbnail(
|
|
160
|
+
source: string,
|
|
161
|
+
output: string,
|
|
162
|
+
options: ThumbnailOptions = {}
|
|
163
|
+
): Promise<ProcessingResult> {
|
|
164
|
+
const startTime = Date.now();
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
logger.info({ source, output, options }, 'Generating thumbnail');
|
|
168
|
+
|
|
169
|
+
// Simulate processing
|
|
170
|
+
await this.simulateProcessing();
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
success: true,
|
|
174
|
+
outputPath: output,
|
|
175
|
+
processingTime: Date.now() - startTime,
|
|
176
|
+
};
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
success: false,
|
|
180
|
+
processingTime: Date.now() - startTime,
|
|
181
|
+
error: error instanceof Error ? error.message : 'Thumbnail generation failed',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Process queue
|
|
188
|
+
*/
|
|
189
|
+
private async processQueue(): Promise<void> {
|
|
190
|
+
if (this.processing >= (this.config.maxConcurrent || 3)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const job = this.queue.shift();
|
|
195
|
+
if (!job) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.processing++;
|
|
200
|
+
job.status = 'processing';
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
let result: ProcessingResult;
|
|
204
|
+
|
|
205
|
+
if (job.mediaType === 'image') {
|
|
206
|
+
result = await this.processImage(
|
|
207
|
+
job.source,
|
|
208
|
+
job.output,
|
|
209
|
+
job.operations as ImageOperation[]
|
|
210
|
+
);
|
|
211
|
+
} else if (job.mediaType === 'video') {
|
|
212
|
+
result = await this.processVideo(
|
|
213
|
+
job.source,
|
|
214
|
+
job.output,
|
|
215
|
+
job.operations as VideoOperation[]
|
|
216
|
+
);
|
|
217
|
+
} else {
|
|
218
|
+
throw new Error('Unsupported media type');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
job.status = result.success ? 'completed' : 'failed';
|
|
222
|
+
job.progress = 100;
|
|
223
|
+
job.completedAt = new Date();
|
|
224
|
+
job.processingTime = result.processingTime;
|
|
225
|
+
|
|
226
|
+
if (!result.success) {
|
|
227
|
+
job.error = result.error;
|
|
228
|
+
}
|
|
229
|
+
} catch (error) {
|
|
230
|
+
job.status = 'failed';
|
|
231
|
+
job.error = error instanceof Error ? error.message : 'Processing failed';
|
|
232
|
+
job.completedAt = new Date();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
this.processing--;
|
|
236
|
+
this.processQueue();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Simulate processing (placeholder)
|
|
241
|
+
*/
|
|
242
|
+
private async simulateProcessing(): Promise<void> {
|
|
243
|
+
return new Promise((resolve) => setTimeout(resolve, 100));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
export type MediaType = 'image' | 'video' | 'audio';
|
|
2
|
+
export type ImageFormat = 'jpeg' | 'png' | 'webp' | 'gif' | 'avif';
|
|
3
|
+
export type VideoFormat = 'mp4' | 'webm' | 'avi' | 'mov';
|
|
4
|
+
export type AudioFormat = 'mp3' | 'wav' | 'ogg' | 'aac';
|
|
5
|
+
|
|
6
|
+
export interface MediaProcessingConfig {
|
|
7
|
+
/** FFmpeg path */
|
|
8
|
+
ffmpegPath?: string;
|
|
9
|
+
/** FFprobe path */
|
|
10
|
+
ffprobePath?: string;
|
|
11
|
+
/** Temporary directory */
|
|
12
|
+
tempDir?: string;
|
|
13
|
+
/** Max concurrent jobs */
|
|
14
|
+
maxConcurrent?: number;
|
|
15
|
+
/** Enable GPU acceleration */
|
|
16
|
+
gpuAcceleration?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ImageOperation {
|
|
20
|
+
type: 'resize' | 'crop' | 'rotate' | 'flip' | 'watermark' | 'compress' | 'filter';
|
|
21
|
+
options: ImageOperationOptions;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ImageOperationOptions {
|
|
25
|
+
// Resize
|
|
26
|
+
width?: number;
|
|
27
|
+
height?: number;
|
|
28
|
+
fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
|
|
29
|
+
|
|
30
|
+
// Crop
|
|
31
|
+
x?: number;
|
|
32
|
+
y?: number;
|
|
33
|
+
|
|
34
|
+
// Rotate
|
|
35
|
+
angle?: number;
|
|
36
|
+
|
|
37
|
+
// Flip
|
|
38
|
+
direction?: 'horizontal' | 'vertical';
|
|
39
|
+
|
|
40
|
+
// Watermark
|
|
41
|
+
text?: string;
|
|
42
|
+
image?: string;
|
|
43
|
+
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'center';
|
|
44
|
+
opacity?: number;
|
|
45
|
+
|
|
46
|
+
// Compress
|
|
47
|
+
quality?: number;
|
|
48
|
+
|
|
49
|
+
// Filter
|
|
50
|
+
filter?: 'grayscale' | 'blur' | 'sharpen' | 'sepia' | 'brightness' | 'contrast';
|
|
51
|
+
intensity?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface VideoOperation {
|
|
55
|
+
type: 'trim' | 'resize' | 'compress' | 'extract-audio' | 'add-subtitle' | 'thumbnail';
|
|
56
|
+
options: VideoOperationOptions;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface VideoOperationOptions {
|
|
60
|
+
// Trim
|
|
61
|
+
startTime?: string;
|
|
62
|
+
endTime?: string;
|
|
63
|
+
duration?: string;
|
|
64
|
+
|
|
65
|
+
// Resize
|
|
66
|
+
width?: number;
|
|
67
|
+
height?: number;
|
|
68
|
+
scale?: string;
|
|
69
|
+
|
|
70
|
+
// Compress
|
|
71
|
+
bitrate?: string;
|
|
72
|
+
crf?: number;
|
|
73
|
+
|
|
74
|
+
// Thumbnail
|
|
75
|
+
timestamp?: string;
|
|
76
|
+
count?: number;
|
|
77
|
+
|
|
78
|
+
// Subtitle
|
|
79
|
+
subtitleFile?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ProcessingJob {
|
|
83
|
+
/** Job ID */
|
|
84
|
+
id: string;
|
|
85
|
+
/** Media type */
|
|
86
|
+
mediaType: MediaType;
|
|
87
|
+
/** Source file path */
|
|
88
|
+
source: string;
|
|
89
|
+
/** Output file path */
|
|
90
|
+
output: string;
|
|
91
|
+
/** Operations to perform */
|
|
92
|
+
operations: (ImageOperation | VideoOperation)[];
|
|
93
|
+
/** Job status */
|
|
94
|
+
status: 'pending' | 'processing' | 'completed' | 'failed';
|
|
95
|
+
/** Progress (0-100) */
|
|
96
|
+
progress: number;
|
|
97
|
+
/** Error message */
|
|
98
|
+
error?: string;
|
|
99
|
+
/** Created at */
|
|
100
|
+
createdAt: Date;
|
|
101
|
+
/** Completed at */
|
|
102
|
+
completedAt?: Date;
|
|
103
|
+
/** Processing time (ms) */
|
|
104
|
+
processingTime?: number;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface MediaInfo {
|
|
108
|
+
/** File path */
|
|
109
|
+
path: string;
|
|
110
|
+
/** Media type */
|
|
111
|
+
type: MediaType;
|
|
112
|
+
/** Format */
|
|
113
|
+
format: string;
|
|
114
|
+
/** Duration (for video/audio) */
|
|
115
|
+
duration?: number;
|
|
116
|
+
/** Width (for image/video) */
|
|
117
|
+
width?: number;
|
|
118
|
+
/** Height (for image/video) */
|
|
119
|
+
height?: number;
|
|
120
|
+
/** Bitrate */
|
|
121
|
+
bitrate?: number;
|
|
122
|
+
/** File size in bytes */
|
|
123
|
+
size: number;
|
|
124
|
+
/** Codec */
|
|
125
|
+
codec?: string;
|
|
126
|
+
/** Frame rate (for video) */
|
|
127
|
+
fps?: number;
|
|
128
|
+
/** Metadata */
|
|
129
|
+
metadata?: Record<string, unknown>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface ThumbnailOptions {
|
|
133
|
+
/** Timestamp for video (e.g., '00:00:05') */
|
|
134
|
+
timestamp?: string;
|
|
135
|
+
/** Width */
|
|
136
|
+
width?: number;
|
|
137
|
+
/** Height */
|
|
138
|
+
height?: number;
|
|
139
|
+
/** Format */
|
|
140
|
+
format?: ImageFormat;
|
|
141
|
+
/** Quality (1-100) */
|
|
142
|
+
quality?: number;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface ProcessingResult {
|
|
146
|
+
/** Success status */
|
|
147
|
+
success: boolean;
|
|
148
|
+
/** Output file path */
|
|
149
|
+
outputPath?: string;
|
|
150
|
+
/** Processing time (ms) */
|
|
151
|
+
processingTime: number;
|
|
152
|
+
/** File size */
|
|
153
|
+
size?: number;
|
|
154
|
+
/** Error message */
|
|
155
|
+
error?: string;
|
|
156
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { MFAService, getMFAService, createMFAService } from './mfa.service.js';
|
|
2
|
+
export { registerMFARoutes } from './mfa.routes.js';
|
|
3
|
+
export {
|
|
4
|
+
generateSecret,
|
|
5
|
+
generateTOTP,
|
|
6
|
+
verifyTOTP,
|
|
7
|
+
generateTOTPUri,
|
|
8
|
+
generateQRCode,
|
|
9
|
+
formatSecretForDisplay,
|
|
10
|
+
getRemainingSeconds,
|
|
11
|
+
} from './totp.js';
|
|
12
|
+
export type {
|
|
13
|
+
MFAConfig,
|
|
14
|
+
MFAMethod,
|
|
15
|
+
UserMFA,
|
|
16
|
+
TOTPSetup,
|
|
17
|
+
MFAChallenge,
|
|
18
|
+
MFAVerifyResult,
|
|
19
|
+
BackupCodesResult,
|
|
20
|
+
} from './types.js';
|