@valencets/cms 0.5.0 → 0.6.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/dist/admin/admin-routes.d.ts +2 -0
- package/dist/admin/admin-routes.d.ts.map +1 -1
- package/dist/admin/admin-routes.js +7 -5
- package/dist/admin/admin-routes.js.map +1 -1
- package/dist/admin/edit-view.d.ts +9 -1
- package/dist/admin/edit-view.d.ts.map +1 -1
- package/dist/admin/edit-view.js +105 -35
- package/dist/admin/edit-view.js.map +1 -1
- package/dist/admin/editor/admin-client.js +1 -1
- package/dist/admin/editor/admin-client.js.map +1 -1
- package/dist/admin/field-renderers.d.ts +13 -1
- package/dist/admin/field-renderers.d.ts.map +1 -1
- package/dist/admin/field-renderers.js +48 -8
- package/dist/admin/field-renderers.js.map +1 -1
- package/dist/admin/layout.d.ts.map +1 -1
- package/dist/admin/layout.js +49 -2
- package/dist/admin/layout.js.map +1 -1
- package/dist/admin/list-view.d.ts +9 -0
- package/dist/admin/list-view.d.ts.map +1 -1
- package/dist/admin/list-view.js +109 -7
- package/dist/admin/list-view.js.map +1 -1
- package/dist/admin-client.js +1 -1
- package/dist/api/local-api.d.ts +14 -3
- package/dist/api/local-api.d.ts.map +1 -1
- package/dist/api/local-api.js +112 -7
- package/dist/api/local-api.js.map +1 -1
- package/dist/api/rest-api.d.ts +8 -1
- package/dist/api/rest-api.d.ts.map +1 -1
- package/dist/api/rest-api.js +76 -12
- package/dist/api/rest-api.js.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/session.d.ts +7 -1
- package/dist/auth/session.d.ts.map +1 -1
- package/dist/auth/session.js +10 -3
- package/dist/auth/session.js.map +1 -1
- package/dist/config/cms-config.d.ts +12 -0
- package/dist/config/cms-config.d.ts.map +1 -1
- package/dist/config/cms-config.js +13 -3
- package/dist/config/cms-config.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/db/migration-generator.d.ts +3 -3
- package/dist/db/migration-generator.d.ts.map +1 -1
- package/dist/db/migration-generator.js +27 -9
- package/dist/db/migration-generator.js.map +1 -1
- package/dist/db/query-builder.d.ts +3 -1
- package/dist/db/query-builder.d.ts.map +1 -1
- package/dist/db/query-builder.js +54 -14
- package/dist/db/query-builder.js.map +1 -1
- package/dist/db/sql-sanitize.d.ts.map +1 -1
- package/dist/db/sql-sanitize.js +5 -1
- package/dist/db/sql-sanitize.js.map +1 -1
- package/dist/hooks/hook-types.d.ts +5 -1
- package/dist/hooks/hook-types.d.ts.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/media/image-processor.d.ts +17 -0
- package/dist/media/image-processor.d.ts.map +1 -0
- package/dist/media/image-processor.js +90 -0
- package/dist/media/image-processor.js.map +1 -0
- package/dist/media/index.d.ts +8 -3
- package/dist/media/index.d.ts.map +1 -1
- package/dist/media/index.js +4 -2
- package/dist/media/index.js.map +1 -1
- package/dist/media/media-config.d.ts +14 -0
- package/dist/media/media-config.d.ts.map +1 -1
- package/dist/media/media-config.js +8 -1
- package/dist/media/media-config.js.map +1 -1
- package/dist/media/serve-handler.d.ts +3 -1
- package/dist/media/serve-handler.d.ts.map +1 -1
- package/dist/media/serve-handler.js +39 -17
- package/dist/media/serve-handler.js.map +1 -1
- package/dist/media/storage-adapter.d.ts +10 -0
- package/dist/media/storage-adapter.d.ts.map +1 -0
- package/dist/media/storage-adapter.js +46 -0
- package/dist/media/storage-adapter.js.map +1 -0
- package/dist/media/upload-handler.d.ts +13 -1
- package/dist/media/upload-handler.d.ts.map +1 -1
- package/dist/media/upload-handler.js +140 -30
- package/dist/media/upload-handler.js.map +1 -1
- package/dist/scheduler.d.ts +7 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +14 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/schema/collection.d.ts +9 -1
- package/dist/schema/collection.d.ts.map +1 -1
- package/dist/schema/collection.js.map +1 -1
- package/dist/schema/index.d.ts +2 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/types.d.ts +5 -0
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +4 -0
- package/dist/schema/types.js.map +1 -1
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +1 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/zod-generator.d.ts +4 -0
- package/dist/validation/zod-generator.d.ts.map +1 -1
- package/dist/validation/zod-generator.js +12 -1
- package/dist/validation/zod-generator.js.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import sharp from 'sharp';
|
|
2
|
+
import { ok, err, ResultAsync } from 'neverthrow';
|
|
3
|
+
import { CmsErrorCode } from '../schema/types.js';
|
|
4
|
+
function toSharpError(e) {
|
|
5
|
+
return {
|
|
6
|
+
code: CmsErrorCode.INTERNAL,
|
|
7
|
+
message: e instanceof Error ? e.message : 'Image processing failed'
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
async function applyFocalPointCrop(pipeline, size, focalPoint) {
|
|
11
|
+
const metaResult = await ResultAsync.fromPromise(pipeline.metadata(), toSharpError);
|
|
12
|
+
if (metaResult.isErr())
|
|
13
|
+
return err(metaResult.error);
|
|
14
|
+
const meta = metaResult.value;
|
|
15
|
+
const srcW = meta.width ?? 0;
|
|
16
|
+
const srcH = meta.height ?? 0;
|
|
17
|
+
if (srcW === 0 || srcH === 0) {
|
|
18
|
+
return ok(pipeline.resize({ width: size.width, height: size.height, fit: 'cover' }));
|
|
19
|
+
}
|
|
20
|
+
const targetW = size.width;
|
|
21
|
+
const targetH = size.height;
|
|
22
|
+
const scale = Math.max(targetW / srcW, targetH / srcH);
|
|
23
|
+
const scaledW = Math.round(srcW * scale);
|
|
24
|
+
const scaledH = Math.round(srcH * scale);
|
|
25
|
+
const focalX = Math.round(focalPoint.x * scaledW);
|
|
26
|
+
const focalY = Math.round(focalPoint.y * scaledH);
|
|
27
|
+
const left = Math.min(Math.max(focalX - Math.round(targetW / 2), 0), scaledW - targetW);
|
|
28
|
+
const top = Math.min(Math.max(focalY - Math.round(targetH / 2), 0), scaledH - targetH);
|
|
29
|
+
return ok(pipeline
|
|
30
|
+
.resize({ width: scaledW, height: scaledH, fit: 'fill' })
|
|
31
|
+
.extract({ left, top, width: targetW, height: targetH }));
|
|
32
|
+
}
|
|
33
|
+
async function buildPipeline(inputBuffer, size, focalPoint) {
|
|
34
|
+
const fit = size.fit ?? 'cover';
|
|
35
|
+
if (focalPoint && fit === 'cover') {
|
|
36
|
+
return applyFocalPointCrop(sharp(inputBuffer), size, focalPoint);
|
|
37
|
+
}
|
|
38
|
+
return ok(sharp(inputBuffer).resize({
|
|
39
|
+
width: size.width,
|
|
40
|
+
height: size.height,
|
|
41
|
+
fit
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
function toProcessedImage(name, outputBuffer, info) {
|
|
45
|
+
return {
|
|
46
|
+
name,
|
|
47
|
+
buffer: outputBuffer,
|
|
48
|
+
width: info.width,
|
|
49
|
+
height: info.height,
|
|
50
|
+
filesize: outputBuffer.length,
|
|
51
|
+
mimeType: `image/${info.format}`
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
async function processSize(inputBuffer, size, focalPoint) {
|
|
55
|
+
const pipelineResult = await buildPipeline(inputBuffer, size, focalPoint);
|
|
56
|
+
if (pipelineResult.isErr())
|
|
57
|
+
return err(pipelineResult.error);
|
|
58
|
+
const bufferResult = await ResultAsync.fromPromise(pipelineResult.value.toBuffer({ resolveWithObject: true }), toSharpError);
|
|
59
|
+
if (bufferResult.isErr())
|
|
60
|
+
return err(bufferResult.error);
|
|
61
|
+
const { data, info } = bufferResult.value;
|
|
62
|
+
return ok(toProcessedImage(size.name, data, info));
|
|
63
|
+
}
|
|
64
|
+
async function processWebpVariant(inputBuffer, size, focalPoint) {
|
|
65
|
+
const pipelineResult = await buildPipeline(inputBuffer, size, focalPoint);
|
|
66
|
+
if (pipelineResult.isErr())
|
|
67
|
+
return err(pipelineResult.error);
|
|
68
|
+
const webpResult = await ResultAsync.fromPromise(pipelineResult.value.webp({ quality: 80 }).toBuffer({ resolveWithObject: true }), toSharpError);
|
|
69
|
+
if (webpResult.isErr())
|
|
70
|
+
return err(webpResult.error);
|
|
71
|
+
const { data, info } = webpResult.value;
|
|
72
|
+
return ok(toProcessedImage(`${size.name}-webp`, data, info));
|
|
73
|
+
}
|
|
74
|
+
export async function processImageSizes(inputBuffer, sizes, formats = [], focalPoint) {
|
|
75
|
+
const results = [];
|
|
76
|
+
for (const size of sizes) {
|
|
77
|
+
const sizeResult = await processSize(inputBuffer, size, focalPoint);
|
|
78
|
+
if (sizeResult.isErr())
|
|
79
|
+
return err(sizeResult.error);
|
|
80
|
+
results.push(sizeResult.value);
|
|
81
|
+
if (formats.includes('webp')) {
|
|
82
|
+
const webpResult = await processWebpVariant(inputBuffer, size, focalPoint);
|
|
83
|
+
if (webpResult.isErr())
|
|
84
|
+
return err(webpResult.error);
|
|
85
|
+
results.push(webpResult.value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return ok(results);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=image-processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-processor.js","sourceRoot":"","sources":["../../src/media/image-processor.ts"],"names":[],"mappings":"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"}
|
package/dist/media/index.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
export { isUploadEnabled, getMediaFields, getMimeType } from './media-config.js';
|
|
2
|
-
export {
|
|
1
|
+
export { isUploadEnabled, getMediaFields, getMimeType, getUploadConfig } from './media-config.js';
|
|
2
|
+
export type { ImageSize, UploadConfig } from './media-config.js';
|
|
3
|
+
export { createServeHandler, buildMediaUrl } from './serve-handler.js';
|
|
3
4
|
export { createUploadHandler } from './upload-handler.js';
|
|
4
|
-
export type { UploadResult } from './upload-handler.js';
|
|
5
|
+
export type { UploadResult, SizeMetadata } from './upload-handler.js';
|
|
6
|
+
export { createLocalStorage } from './storage-adapter.js';
|
|
7
|
+
export type { StorageAdapter } from './storage-adapter.js';
|
|
8
|
+
export { processImageSizes } from './image-processor.js';
|
|
9
|
+
export type { ProcessedImage, FocalPoint } from './image-processor.js';
|
|
5
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACjG,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChE,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AACzD,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACzD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACxD,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA"}
|
package/dist/media/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { isUploadEnabled, getMediaFields, getMimeType } from './media-config.js';
|
|
2
|
-
export { createServeHandler } from './serve-handler.js';
|
|
1
|
+
export { isUploadEnabled, getMediaFields, getMimeType, getUploadConfig } from './media-config.js';
|
|
2
|
+
export { createServeHandler, buildMediaUrl } from './serve-handler.js';
|
|
3
3
|
export { createUploadHandler } from './upload-handler.js';
|
|
4
|
+
export { createLocalStorage } from './storage-adapter.js';
|
|
5
|
+
export { processImageSizes } from './image-processor.js';
|
|
4
6
|
//# sourceMappingURL=index.js.map
|
package/dist/media/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAEjG,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAEzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAEzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA"}
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import type { CollectionConfig } from '../schema/collection.js';
|
|
2
2
|
import type { FieldConfig } from '../schema/field-types.js';
|
|
3
|
+
export interface ImageSize {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly width: number;
|
|
6
|
+
readonly height: number;
|
|
7
|
+
readonly fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside' | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface UploadConfig {
|
|
10
|
+
readonly mimeTypes?: readonly string[] | undefined;
|
|
11
|
+
readonly maxFileSize?: number | undefined;
|
|
12
|
+
readonly imageSizes?: readonly ImageSize[] | undefined;
|
|
13
|
+
readonly focalPoint?: boolean | undefined;
|
|
14
|
+
readonly formats?: readonly string[] | undefined;
|
|
15
|
+
}
|
|
3
16
|
export declare function isUploadEnabled(collection: CollectionConfig): boolean;
|
|
17
|
+
export declare function getUploadConfig(collection: CollectionConfig): UploadConfig | null;
|
|
4
18
|
export declare function getMediaFields(): readonly FieldConfig[];
|
|
5
19
|
export declare function getMimeType(filename: string): string;
|
|
6
20
|
//# sourceMappingURL=media-config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-config.d.ts","sourceRoot":"","sources":["../../src/media/media-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAE3D,wBAAgB,eAAe,CAAE,UAAU,EAAE,gBAAgB,GAAG,OAAO,CAEtE;AAED,wBAAgB,cAAc,IAAK,SAAS,WAAW,EAAE,CAQxD;AAuBD,wBAAgB,WAAW,CAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrD"}
|
|
1
|
+
{"version":3,"file":"media-config.d.ts","sourceRoot":"","sources":["../../src/media/media-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAE3D,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAA;CAC/E;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;IAClD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,CAAA;IACtD,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;CACjD;AAED,wBAAgB,eAAe,CAAE,UAAU,EAAE,gBAAgB,GAAG,OAAO,CAEtE;AAED,wBAAgB,eAAe,CAAE,UAAU,EAAE,gBAAgB,GAAG,YAAY,GAAG,IAAI,CAIlF;AAED,wBAAgB,cAAc,IAAK,SAAS,WAAW,EAAE,CAQxD;AAuBD,wBAAgB,WAAW,CAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrD"}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export function isUploadEnabled(collection) {
|
|
2
|
-
return collection.upload
|
|
2
|
+
return collection.upload !== undefined && collection.upload !== false;
|
|
3
|
+
}
|
|
4
|
+
export function getUploadConfig(collection) {
|
|
5
|
+
if (collection.upload === true)
|
|
6
|
+
return {};
|
|
7
|
+
if (collection.upload === false || collection.upload === undefined)
|
|
8
|
+
return null;
|
|
9
|
+
return collection.upload;
|
|
3
10
|
}
|
|
4
11
|
export function getMediaFields() {
|
|
5
12
|
return [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media-config.js","sourceRoot":"","sources":["../../src/media/media-config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"media-config.js","sourceRoot":"","sources":["../../src/media/media-config.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,eAAe,CAAE,UAA4B;IAC3D,OAAO,UAAU,CAAC,MAAM,KAAK,SAAS,IAAI,UAAU,CAAC,MAAM,KAAK,KAAK,CAAA;AACvE,CAAC;AAED,MAAM,UAAU,eAAe,CAAE,UAA4B;IAC3D,IAAI,UAAU,CAAC,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,CAAA;IACzC,IAAI,UAAU,CAAC,MAAM,KAAK,KAAK,IAAI,UAAU,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IAC/E,OAAO,UAAU,CAAC,MAAM,CAAA;AAC1B,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO;QACL,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE;QAClD,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE;QAClD,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE;QACpD,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE;QACpD,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE;KAClC,CAAA;AACH,CAAC;AAED,MAAM,QAAQ,GAA2B;IACvC,GAAG,EAAE,YAAY;IACjB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,eAAe;IACpB,GAAG,EAAE,iBAAiB;IACtB,IAAI,EAAE,kBAAkB;IACxB,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,YAAY;IACjB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,YAAY;IACjB,GAAG,EAAE,UAAU;IACf,IAAI,EAAE,WAAW;IACjB,GAAG,EAAE,UAAU;IACf,EAAE,EAAE,wBAAwB;IAC5B,GAAG,EAAE,iBAAiB;CACvB,CAAA;AAED,MAAM,UAAU,WAAW,CAAE,QAAgB;IAC3C,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAA;IAC1D,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAA;AACpD,CAAC"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
-
|
|
2
|
+
import type { StorageAdapter } from './storage-adapter.js';
|
|
3
|
+
export declare function createServeHandler(uploadDir: string, storage?: StorageAdapter): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
4
|
+
export declare function buildMediaUrl(filename: string, sizeName?: string): string;
|
|
3
5
|
//# sourceMappingURL=serve-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve-handler.d.ts","sourceRoot":"","sources":["../../src/media/serve-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"serve-handler.d.ts","sourceRoot":"","sources":["../../src/media/serve-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAOhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAkB1D,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,cAAc,GACvB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CA+C9D;AAED,wBAAgB,aAAa,CAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAW1E"}
|
|
@@ -4,25 +4,43 @@ import { ResultAsync } from 'neverthrow';
|
|
|
4
4
|
import { CmsErrorCode } from '../schema/types.js';
|
|
5
5
|
import { getMimeType } from './media-config.js';
|
|
6
6
|
const SAFE_FILENAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
7
|
-
|
|
7
|
+
function sendFile(res, filename, data) {
|
|
8
|
+
res.writeHead(200, {
|
|
9
|
+
'Content-Type': getMimeType(filename),
|
|
10
|
+
'Content-Length': data.length,
|
|
11
|
+
'Cache-Control': 'public, max-age=31536000, immutable'
|
|
12
|
+
});
|
|
13
|
+
res.end(data);
|
|
14
|
+
}
|
|
15
|
+
function sendError(res, status, error) {
|
|
16
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
17
|
+
res.end(JSON.stringify({ error }));
|
|
18
|
+
}
|
|
19
|
+
export function createServeHandler(uploadDir, storage) {
|
|
8
20
|
const resolvedUploadDir = resolve(uploadDir);
|
|
9
21
|
return async (req, res) => {
|
|
10
22
|
const rawFilename = req.url?.split('/').pop();
|
|
11
23
|
if (!rawFilename) {
|
|
12
|
-
res
|
|
13
|
-
res.end(JSON.stringify({ error: 'Missing filename' }));
|
|
24
|
+
sendError(res, 400, 'Missing filename');
|
|
14
25
|
return;
|
|
15
26
|
}
|
|
16
27
|
const filename = basename(rawFilename);
|
|
17
28
|
if (!SAFE_FILENAME_RE.test(filename)) {
|
|
18
|
-
res
|
|
19
|
-
|
|
29
|
+
sendError(res, 400, 'Invalid filename');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (storage) {
|
|
33
|
+
const result = await storage.read(filename);
|
|
34
|
+
if (result.isErr()) {
|
|
35
|
+
sendError(res, 404, 'File not found');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
sendFile(res, filename, result.value);
|
|
20
39
|
return;
|
|
21
40
|
}
|
|
22
41
|
const filePath = resolve(join(resolvedUploadDir, filename));
|
|
23
42
|
if (!filePath.startsWith(resolvedUploadDir)) {
|
|
24
|
-
res
|
|
25
|
-
res.end(JSON.stringify({ error: 'Forbidden' }));
|
|
43
|
+
sendError(res, 403, 'Forbidden');
|
|
26
44
|
return;
|
|
27
45
|
}
|
|
28
46
|
const result = await ResultAsync.fromPromise(readFile(filePath), (e) => ({
|
|
@@ -30,18 +48,22 @@ export function createServeHandler(uploadDir) {
|
|
|
30
48
|
message: e instanceof Error ? e.message : 'File read failed'
|
|
31
49
|
}));
|
|
32
50
|
if (result.isErr()) {
|
|
33
|
-
res
|
|
34
|
-
res.end(JSON.stringify({ error: 'File not found' }));
|
|
51
|
+
sendError(res, 404, 'File not found');
|
|
35
52
|
return;
|
|
36
53
|
}
|
|
37
|
-
|
|
38
|
-
const contentType = getMimeType(filename);
|
|
39
|
-
res.writeHead(200, {
|
|
40
|
-
'Content-Type': contentType,
|
|
41
|
-
'Content-Length': data.length,
|
|
42
|
-
'Cache-Control': 'public, max-age=31536000, immutable'
|
|
43
|
-
});
|
|
44
|
-
res.end(data);
|
|
54
|
+
sendFile(res, filename, result.value);
|
|
45
55
|
};
|
|
46
56
|
}
|
|
57
|
+
export function buildMediaUrl(filename, sizeName) {
|
|
58
|
+
const base = basename(filename);
|
|
59
|
+
if (!sizeName)
|
|
60
|
+
return `/media/${encodeURIComponent(base)}`;
|
|
61
|
+
const dotIndex = base.lastIndexOf('.');
|
|
62
|
+
if (dotIndex === -1) {
|
|
63
|
+
return `/media/${encodeURIComponent(`${base}-${sizeName}`)}`;
|
|
64
|
+
}
|
|
65
|
+
const nameWithoutExt = base.slice(0, dotIndex);
|
|
66
|
+
const ext = base.slice(dotIndex + 1);
|
|
67
|
+
return `/media/${encodeURIComponent(`${nameWithoutExt}-${sizeName}.${ext}`)}`;
|
|
68
|
+
}
|
|
47
69
|
//# sourceMappingURL=serve-handler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve-handler.js","sourceRoot":"","sources":["../../src/media/serve-handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"serve-handler.js","sourceRoot":"","sources":["../../src/media/serve-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ResultAsync } from 'neverthrow';
|
|
2
|
+
import type { CmsError } from '../schema/types.js';
|
|
3
|
+
export interface StorageAdapter {
|
|
4
|
+
readonly write: (filename: string, data: Buffer) => ResultAsync<string, CmsError>;
|
|
5
|
+
readonly read: (filename: string) => ResultAsync<Buffer, CmsError>;
|
|
6
|
+
readonly remove: (filename: string) => ResultAsync<void, CmsError>;
|
|
7
|
+
readonly url: (filename: string) => string;
|
|
8
|
+
}
|
|
9
|
+
export declare function createLocalStorage(uploadDir: string): StorageAdapter;
|
|
10
|
+
//# sourceMappingURL=storage-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-adapter.d.ts","sourceRoot":"","sources":["../../src/media/storage-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAY,MAAM,YAAY,CAAA;AAElD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAIlD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IACjF,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAClE,QAAQ,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;IAClE,QAAQ,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAA;CAC3C;AAED,wBAAgB,kBAAkB,CAAE,SAAS,EAAE,MAAM,GAAG,cAAc,CAqDrE"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ResultAsync, errAsync } from 'neverthrow';
|
|
2
|
+
import { CmsErrorCode } from '../schema/types.js';
|
|
3
|
+
import { writeFile, readFile, unlink } from 'node:fs/promises';
|
|
4
|
+
import { join, resolve, basename } from 'node:path';
|
|
5
|
+
export function createLocalStorage(uploadDir) {
|
|
6
|
+
const resolvedDir = resolve(uploadDir);
|
|
7
|
+
return {
|
|
8
|
+
write(filename, data) {
|
|
9
|
+
const safeName = basename(filename);
|
|
10
|
+
const filePath = resolve(join(resolvedDir, safeName));
|
|
11
|
+
if (!filePath.startsWith(resolvedDir)) {
|
|
12
|
+
return errAsync({ code: CmsErrorCode.FORBIDDEN, message: 'Path traversal rejected' });
|
|
13
|
+
}
|
|
14
|
+
return ResultAsync.fromPromise(writeFile(filePath, data).then(() => safeName), (e) => ({
|
|
15
|
+
code: CmsErrorCode.INTERNAL,
|
|
16
|
+
message: e instanceof Error ? e.message : 'File write failed'
|
|
17
|
+
}));
|
|
18
|
+
},
|
|
19
|
+
read(filename) {
|
|
20
|
+
const safeName = basename(filename);
|
|
21
|
+
const filePath = resolve(join(resolvedDir, safeName));
|
|
22
|
+
if (!filePath.startsWith(resolvedDir)) {
|
|
23
|
+
return errAsync({ code: CmsErrorCode.FORBIDDEN, message: 'Path traversal rejected' });
|
|
24
|
+
}
|
|
25
|
+
return ResultAsync.fromPromise(readFile(filePath), (e) => ({
|
|
26
|
+
code: CmsErrorCode.NOT_FOUND,
|
|
27
|
+
message: e instanceof Error ? e.message : 'File read failed'
|
|
28
|
+
}));
|
|
29
|
+
},
|
|
30
|
+
remove(filename) {
|
|
31
|
+
const safeName = basename(filename);
|
|
32
|
+
const filePath = resolve(join(resolvedDir, safeName));
|
|
33
|
+
if (!filePath.startsWith(resolvedDir)) {
|
|
34
|
+
return errAsync({ code: CmsErrorCode.FORBIDDEN, message: 'Path traversal rejected' });
|
|
35
|
+
}
|
|
36
|
+
return ResultAsync.fromPromise(unlink(filePath), (e) => ({
|
|
37
|
+
code: CmsErrorCode.NOT_FOUND,
|
|
38
|
+
message: e instanceof Error ? e.message : 'File delete failed'
|
|
39
|
+
}));
|
|
40
|
+
},
|
|
41
|
+
url(filename) {
|
|
42
|
+
return `/media/${encodeURIComponent(basename(filename))}`;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=storage-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-adapter.js","sourceRoot":"","sources":["../../src/media/storage-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { UploadConfig } from './media-config.js';
|
|
3
|
+
import type { StorageAdapter } from './storage-adapter.js';
|
|
4
|
+
export interface SizeMetadata {
|
|
5
|
+
readonly filename: string;
|
|
6
|
+
readonly width: number;
|
|
7
|
+
readonly height: number;
|
|
8
|
+
readonly filesize: number;
|
|
9
|
+
readonly mimeType: string;
|
|
10
|
+
}
|
|
2
11
|
export interface UploadResult {
|
|
3
12
|
readonly filename: string;
|
|
4
13
|
readonly storedPath: string;
|
|
5
14
|
readonly mimeType: string;
|
|
6
15
|
readonly filesize: number;
|
|
16
|
+
readonly focalX?: number | undefined;
|
|
17
|
+
readonly focalY?: number | undefined;
|
|
18
|
+
readonly sizes?: Record<string, SizeMetadata> | undefined;
|
|
7
19
|
}
|
|
8
|
-
export declare function createUploadHandler(uploadDir: string): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
20
|
+
export declare function createUploadHandler(uploadDir: string, storage?: StorageAdapter, uploadConfig?: UploadConfig): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
9
21
|
//# sourceMappingURL=upload-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload-handler.d.ts","sourceRoot":"","sources":["../../src/media/upload-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"upload-handler.d.ts","sourceRoot":"","sources":["../../src/media/upload-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAKhE,OAAO,KAAK,EAAa,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AA0B1D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,SAAS,CAAA;CAC1D;AAoID,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,cAAc,EACxB,YAAY,CAAC,EAAE,YAAY,GAC1B,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAkE9D"}
|
|
@@ -1,63 +1,173 @@
|
|
|
1
|
-
import { ResultAsync } from 'neverthrow';
|
|
1
|
+
import { ResultAsync, okAsync } from 'neverthrow';
|
|
2
2
|
import { CmsErrorCode } from '../schema/types.js';
|
|
3
3
|
import { writeFile } from 'node:fs/promises';
|
|
4
4
|
import { join, resolve, basename } from 'node:path';
|
|
5
5
|
import { randomBytes } from 'node:crypto';
|
|
6
6
|
import { getMimeType } from './media-config.js';
|
|
7
|
+
import { processImageSizes } from './image-processor.js';
|
|
7
8
|
import { readRawBody } from '../api/read-body.js';
|
|
8
9
|
const MAX_UPLOAD_BYTES = 10_485_760;
|
|
9
10
|
const SAFE_FILENAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
11
|
+
const IMAGE_MIMES = new Set([
|
|
12
|
+
'image/jpeg',
|
|
13
|
+
'image/png',
|
|
14
|
+
'image/webp',
|
|
15
|
+
'image/avif',
|
|
16
|
+
'image/gif'
|
|
17
|
+
]);
|
|
18
|
+
function isImageFile(mimeType) {
|
|
19
|
+
return IMAGE_MIMES.has(mimeType);
|
|
20
|
+
}
|
|
10
21
|
function generateStoredName(originalName) {
|
|
11
22
|
const ext = originalName.split('.').pop() ?? '';
|
|
12
23
|
const prefix = randomBytes(16).toString('hex');
|
|
13
24
|
return ext ? `${prefix}.${ext}` : prefix;
|
|
14
25
|
}
|
|
15
|
-
|
|
26
|
+
function parseFocalPoint(req) {
|
|
27
|
+
const rawX = req.headers['x-focal-x'];
|
|
28
|
+
const rawY = req.headers['x-focal-y'];
|
|
29
|
+
const xStr = Array.isArray(rawX) ? rawX[0] : rawX;
|
|
30
|
+
const yStr = Array.isArray(rawY) ? rawY[0] : rawY;
|
|
31
|
+
if (xStr === undefined || yStr === undefined)
|
|
32
|
+
return undefined;
|
|
33
|
+
const x = Number(xStr);
|
|
34
|
+
const y = Number(yStr);
|
|
35
|
+
if (Number.isNaN(x) || Number.isNaN(y))
|
|
36
|
+
return undefined;
|
|
37
|
+
if (x < 0 || x > 1 || y < 0 || y > 1)
|
|
38
|
+
return undefined;
|
|
39
|
+
return { x, y };
|
|
40
|
+
}
|
|
41
|
+
function buildSizeMetadata(processed, originalExt) {
|
|
42
|
+
const sizes = {};
|
|
43
|
+
for (const img of processed) {
|
|
44
|
+
const ext = img.mimeType.split('/').pop() ?? originalExt;
|
|
45
|
+
sizes[img.name] = {
|
|
46
|
+
filename: `${img.name}.${ext}`,
|
|
47
|
+
width: img.width,
|
|
48
|
+
height: img.height,
|
|
49
|
+
filesize: img.filesize,
|
|
50
|
+
mimeType: img.mimeType
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return sizes;
|
|
54
|
+
}
|
|
55
|
+
function variantFilename(storedName, sizeName, ext) {
|
|
56
|
+
const base = storedName.split('.').slice(0, -1).join('.');
|
|
57
|
+
return `${base}-${sizeName}.${ext}`;
|
|
58
|
+
}
|
|
59
|
+
function writeVariants(storage, storedName, processed) {
|
|
60
|
+
let chain = okAsync(undefined);
|
|
61
|
+
for (const img of processed) {
|
|
62
|
+
const ext = img.mimeType.split('/').pop() ?? 'bin';
|
|
63
|
+
const name = variantFilename(storedName, img.name, ext);
|
|
64
|
+
chain = chain.andThen(() => storage.write(name, img.buffer).map(() => undefined));
|
|
65
|
+
}
|
|
66
|
+
return chain;
|
|
67
|
+
}
|
|
68
|
+
function sendJson(res, status, data) {
|
|
69
|
+
const body = JSON.stringify(data);
|
|
70
|
+
res.writeHead(status, {
|
|
71
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
72
|
+
'Content-Length': Buffer.byteLength(body)
|
|
73
|
+
});
|
|
74
|
+
res.end(body);
|
|
75
|
+
}
|
|
76
|
+
function sendError(res, status, message) {
|
|
77
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
78
|
+
res.end(JSON.stringify({ error: message }));
|
|
79
|
+
}
|
|
80
|
+
function getImageProcessingContext(storage, mimeType, uploadConfig) {
|
|
81
|
+
if (!storage || !isImageFile(mimeType))
|
|
82
|
+
return null;
|
|
83
|
+
if (!uploadConfig?.imageSizes || uploadConfig.imageSizes.length === 0)
|
|
84
|
+
return null;
|
|
85
|
+
return {
|
|
86
|
+
storage,
|
|
87
|
+
imageSizes: uploadConfig.imageSizes,
|
|
88
|
+
formats: uploadConfig.formats ?? []
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async function writeMainFile(storage, resolvedDir, storedName, data) {
|
|
92
|
+
if (storage) {
|
|
93
|
+
const writeResult = await storage.write(storedName, data);
|
|
94
|
+
return writeResult.isErr() ? writeResult.error : null;
|
|
95
|
+
}
|
|
96
|
+
const storedPath = resolve(join(resolvedDir, storedName));
|
|
97
|
+
if (!storedPath.startsWith(resolvedDir)) {
|
|
98
|
+
return { code: CmsErrorCode.FORBIDDEN, message: 'Forbidden' };
|
|
99
|
+
}
|
|
100
|
+
const writeResult = await ResultAsync.fromPromise(writeFile(storedPath, data), (e) => ({
|
|
101
|
+
code: CmsErrorCode.INTERNAL,
|
|
102
|
+
message: e instanceof Error ? e.message : 'File write failed'
|
|
103
|
+
}));
|
|
104
|
+
return writeResult.isErr() ? writeResult.error : null;
|
|
105
|
+
}
|
|
106
|
+
function buildUploadResult(originalName, storedName, mimeType, filesize, focalX, focalY, sizes) {
|
|
107
|
+
return {
|
|
108
|
+
filename: originalName,
|
|
109
|
+
storedPath: storedName,
|
|
110
|
+
mimeType,
|
|
111
|
+
filesize,
|
|
112
|
+
...(focalX !== undefined && focalY !== undefined ? { focalX, focalY } : {}),
|
|
113
|
+
...(sizes ? { sizes } : {})
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function createUploadHandler(uploadDir, storage, uploadConfig) {
|
|
16
117
|
const resolvedDir = resolve(uploadDir);
|
|
17
118
|
return async (req, res) => {
|
|
18
119
|
const filenameHeader = req.headers['x-filename'];
|
|
19
120
|
const rawName = Array.isArray(filenameHeader) ? filenameHeader[0] ?? 'upload' : filenameHeader ?? 'upload';
|
|
20
121
|
const originalName = basename(rawName);
|
|
21
122
|
if (!SAFE_FILENAME_RE.test(originalName)) {
|
|
22
|
-
res
|
|
23
|
-
res.end(JSON.stringify({ error: 'Invalid filename' }));
|
|
123
|
+
sendError(res, 400, 'Invalid filename');
|
|
24
124
|
return;
|
|
25
125
|
}
|
|
26
126
|
const bodyResult = await readRawBody(req, MAX_UPLOAD_BYTES);
|
|
27
127
|
if (bodyResult.isErr()) {
|
|
28
|
-
res
|
|
29
|
-
res.end(JSON.stringify({ error: bodyResult.error.message }));
|
|
128
|
+
sendError(res, 400, bodyResult.error.message);
|
|
30
129
|
return;
|
|
31
130
|
}
|
|
32
131
|
const data = bodyResult.value;
|
|
33
132
|
const storedName = generateStoredName(originalName);
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
133
|
+
const mimeType = getMimeType(originalName);
|
|
134
|
+
const writeError = await writeMainFile(storage, resolvedDir, storedName, data);
|
|
135
|
+
if (writeError) {
|
|
136
|
+
const status = writeError.code === CmsErrorCode.FORBIDDEN ? 403 : 500;
|
|
137
|
+
sendError(res, status, writeError.message);
|
|
38
138
|
return;
|
|
39
139
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
140
|
+
let focalX;
|
|
141
|
+
let focalY;
|
|
142
|
+
let sizes;
|
|
143
|
+
if (isImageFile(mimeType) && uploadConfig?.focalPoint) {
|
|
144
|
+
const fp = parseFocalPoint(req);
|
|
145
|
+
if (fp) {
|
|
146
|
+
focalX = fp.x;
|
|
147
|
+
focalY = fp.y;
|
|
148
|
+
}
|
|
48
149
|
}
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
150
|
+
const imageCtx = getImageProcessingContext(storage, mimeType, uploadConfig);
|
|
151
|
+
if (imageCtx) {
|
|
152
|
+
const focalPoint = (focalX !== undefined && focalY !== undefined)
|
|
153
|
+
? { x: focalX, y: focalY }
|
|
154
|
+
: undefined;
|
|
155
|
+
const processResult = await processImageSizes(data, imageCtx.imageSizes, imageCtx.formats, focalPoint);
|
|
156
|
+
if (processResult.isErr()) {
|
|
157
|
+
sendError(res, 500, processResult.error.message);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const processed = processResult.value;
|
|
161
|
+
const originalExt = originalName.split('.').pop() ?? '';
|
|
162
|
+
sizes = buildSizeMetadata(processed, originalExt);
|
|
163
|
+
const variantResult = await writeVariants(imageCtx.storage, storedName, processed);
|
|
164
|
+
if (variantResult.isErr()) {
|
|
165
|
+
sendError(res, 500, variantResult.error.message);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const result = buildUploadResult(originalName, storedName, mimeType, data.length, focalX, focalY, sizes);
|
|
170
|
+
sendJson(res, 201, result);
|
|
61
171
|
};
|
|
62
172
|
}
|
|
63
173
|
//# sourceMappingURL=upload-handler.js.map
|