@soulbatical/tetra-core 0.1.48 → 0.1.50
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/frontend/storage/StorageDropzone.d.ts +43 -0
- package/dist/frontend/storage/StorageDropzone.d.ts.map +1 -0
- package/dist/frontend/storage/StorageDropzone.js +98 -0
- package/dist/frontend/storage/StorageDropzone.js.map +1 -0
- package/dist/frontend/storage/index.d.ts +3 -1
- package/dist/frontend/storage/index.d.ts.map +1 -1
- package/dist/frontend/storage/index.js +1 -0
- package/dist/frontend/storage/index.js.map +1 -1
- package/dist/frontend/storage/storageUrl.d.ts +31 -5
- package/dist/frontend/storage/storageUrl.d.ts.map +1 -1
- package/dist/frontend/storage/storageUrl.js +40 -6
- package/dist/frontend/storage/storageUrl.js.map +1 -1
- package/dist/frontend/storage/useStorageUpload.d.ts +45 -12
- package/dist/frontend/storage/useStorageUpload.d.ts.map +1 -1
- package/dist/frontend/storage/useStorageUpload.js +134 -28
- package/dist/frontend/storage/useStorageUpload.js.map +1 -1
- package/dist/shared/email/mailgun.js.map +1 -1
- package/dist/shared/storage/StorageProxyService.d.ts +23 -2
- package/dist/shared/storage/StorageProxyService.d.ts.map +1 -1
- package/dist/shared/storage/StorageProxyService.js +122 -6
- package/dist/shared/storage/StorageProxyService.js.map +1 -1
- package/dist/shared/storage/routes.d.ts.map +1 -1
- package/dist/shared/storage/routes.js +3 -1
- package/dist/shared/storage/routes.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StorageDropzone — drag & drop upload component
|
|
3
|
+
*
|
|
4
|
+
* Ready-to-use upload zone with drag & drop, file validation,
|
|
5
|
+
* progress indicator, and preview. Framework-agnostic styling via className props.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { StorageDropzone } from '@soulbatical/tetra-core/frontend';
|
|
10
|
+
*
|
|
11
|
+
* <StorageDropzone
|
|
12
|
+
* apiBaseUrl={process.env.NEXT_PUBLIC_API_URL}
|
|
13
|
+
* bucket="images"
|
|
14
|
+
* acceptedTypes={['image/jpeg', 'image/png']}
|
|
15
|
+
* maxSizeBytes={10 * 1024 * 1024}
|
|
16
|
+
* onComplete={(result) => setImageUrl(result.proxyUrl)}
|
|
17
|
+
* />
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import React from 'react';
|
|
21
|
+
import { type UseStorageUploadOptions, type UploadResult } from './useStorageUpload.js';
|
|
22
|
+
export interface StorageDropzoneProps extends UseStorageUploadOptions {
|
|
23
|
+
/** Allow multiple file selection */
|
|
24
|
+
multiple?: boolean;
|
|
25
|
+
/** Custom label text */
|
|
26
|
+
label?: string;
|
|
27
|
+
/** Sublabel text (e.g. "Max 10MB, JPEG or PNG") */
|
|
28
|
+
sublabel?: string;
|
|
29
|
+
/** CSS class for outer container */
|
|
30
|
+
className?: string;
|
|
31
|
+
/** CSS class when dragging over */
|
|
32
|
+
dragActiveClassName?: string;
|
|
33
|
+
/** CSS class when uploading */
|
|
34
|
+
uploadingClassName?: string;
|
|
35
|
+
/** Render custom content inside the dropzone */
|
|
36
|
+
children?: React.ReactNode;
|
|
37
|
+
/** Render the result after upload (e.g. preview image) */
|
|
38
|
+
renderResult?: (result: UploadResult) => React.ReactNode;
|
|
39
|
+
/** Disable the dropzone */
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export declare function StorageDropzone({ multiple, label, sublabel, className, dragActiveClassName, uploadingClassName, children, renderResult, disabled, ...uploadOptions }: StorageDropzoneProps): import("react/jsx-runtime").JSX.Element;
|
|
43
|
+
//# sourceMappingURL=StorageDropzone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageDropzone.d.ts","sourceRoot":"","sources":["../../../src/frontend/storage/StorageDropzone.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAwC,MAAM,OAAO,CAAC;AAC7D,OAAO,EAAoB,KAAK,uBAAuB,EAAE,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1G,MAAM,WAAW,oBAAqB,SAAQ,uBAAuB;IACnE,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,+BAA+B;IAC/B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gDAAgD;IAChD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,0DAA0D;IAC1D,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,KAAK,CAAC,SAAS,CAAC;IACzD,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,EAC9B,QAAgB,EAChB,KAAK,EACL,QAAQ,EACR,SAAc,EACd,mBAAwB,EACxB,kBAAuB,EACvB,QAAQ,EACR,YAAY,EACZ,QAAgB,EAChB,GAAG,aAAa,EACjB,EAAE,oBAAoB,2CAwItB"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* StorageDropzone — drag & drop upload component
|
|
4
|
+
*
|
|
5
|
+
* Ready-to-use upload zone with drag & drop, file validation,
|
|
6
|
+
* progress indicator, and preview. Framework-agnostic styling via className props.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```tsx
|
|
10
|
+
* import { StorageDropzone } from '@soulbatical/tetra-core/frontend';
|
|
11
|
+
*
|
|
12
|
+
* <StorageDropzone
|
|
13
|
+
* apiBaseUrl={process.env.NEXT_PUBLIC_API_URL}
|
|
14
|
+
* bucket="images"
|
|
15
|
+
* acceptedTypes={['image/jpeg', 'image/png']}
|
|
16
|
+
* maxSizeBytes={10 * 1024 * 1024}
|
|
17
|
+
* onComplete={(result) => setImageUrl(result.proxyUrl)}
|
|
18
|
+
* />
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import { useState, useCallback, useRef } from 'react';
|
|
22
|
+
import { useStorageUpload } from './useStorageUpload.js';
|
|
23
|
+
export function StorageDropzone({ multiple = false, label, sublabel, className = '', dragActiveClassName = '', uploadingClassName = '', children, renderResult, disabled = false, ...uploadOptions }) {
|
|
24
|
+
const { upload, uploadMultiple, uploading, progress, error, result, validate } = useStorageUpload(uploadOptions);
|
|
25
|
+
const [isDragActive, setIsDragActive] = useState(false);
|
|
26
|
+
const inputRef = useRef(null);
|
|
27
|
+
const handleFiles = useCallback(async (files) => {
|
|
28
|
+
const fileArray = Array.from(files);
|
|
29
|
+
if (fileArray.length === 0)
|
|
30
|
+
return;
|
|
31
|
+
if (multiple) {
|
|
32
|
+
await uploadMultiple(fileArray);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
await upload(fileArray[0]);
|
|
36
|
+
}
|
|
37
|
+
}, [upload, uploadMultiple, multiple]);
|
|
38
|
+
const handleDragOver = useCallback((e) => {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
e.stopPropagation();
|
|
41
|
+
if (!disabled && !uploading)
|
|
42
|
+
setIsDragActive(true);
|
|
43
|
+
}, [disabled, uploading]);
|
|
44
|
+
const handleDragLeave = useCallback((e) => {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
e.stopPropagation();
|
|
47
|
+
setIsDragActive(false);
|
|
48
|
+
}, []);
|
|
49
|
+
const handleDrop = useCallback((e) => {
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
e.stopPropagation();
|
|
52
|
+
setIsDragActive(false);
|
|
53
|
+
if (!disabled && !uploading && e.dataTransfer.files.length > 0) {
|
|
54
|
+
handleFiles(e.dataTransfer.files);
|
|
55
|
+
}
|
|
56
|
+
}, [disabled, uploading, handleFiles]);
|
|
57
|
+
const handleClick = useCallback(() => {
|
|
58
|
+
if (!disabled && !uploading) {
|
|
59
|
+
inputRef.current?.click();
|
|
60
|
+
}
|
|
61
|
+
}, [disabled, uploading]);
|
|
62
|
+
const handleInputChange = useCallback((e) => {
|
|
63
|
+
if (e.target.files && e.target.files.length > 0) {
|
|
64
|
+
handleFiles(e.target.files);
|
|
65
|
+
e.target.value = ''; // Reset so same file can be re-selected
|
|
66
|
+
}
|
|
67
|
+
}, [handleFiles]);
|
|
68
|
+
const acceptStr = uploadOptions.acceptedTypes?.join(',') || undefined;
|
|
69
|
+
// Build maxSize label
|
|
70
|
+
const maxSizeLabel = uploadOptions.maxSizeBytes
|
|
71
|
+
? `${(uploadOptions.maxSizeBytes / 1024 / 1024).toFixed(0)}MB`
|
|
72
|
+
: null;
|
|
73
|
+
const defaultSublabel = [
|
|
74
|
+
maxSizeLabel && `Max ${maxSizeLabel}`,
|
|
75
|
+
uploadOptions.acceptedTypes && uploadOptions.acceptedTypes.map(t => t.split('/')[1]?.toUpperCase()).join(', '),
|
|
76
|
+
].filter(Boolean).join(' · ') || undefined;
|
|
77
|
+
const containerClass = [
|
|
78
|
+
className,
|
|
79
|
+
isDragActive && dragActiveClassName,
|
|
80
|
+
uploading && uploadingClassName,
|
|
81
|
+
].filter(Boolean).join(' ');
|
|
82
|
+
return (_jsxs("div", { role: "button", tabIndex: 0, onClick: handleClick, onKeyDown: (e) => e.key === 'Enter' && handleClick(), onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, className: containerClass, style: !className ? {
|
|
83
|
+
border: `2px dashed ${isDragActive ? '#3b82f6' : '#d1d5db'}`,
|
|
84
|
+
borderRadius: '8px',
|
|
85
|
+
padding: '2rem',
|
|
86
|
+
textAlign: 'center',
|
|
87
|
+
cursor: disabled || uploading ? 'default' : 'pointer',
|
|
88
|
+
opacity: disabled ? 0.5 : 1,
|
|
89
|
+
transition: 'border-color 0.2s, background-color 0.2s',
|
|
90
|
+
backgroundColor: isDragActive ? '#eff6ff' : 'transparent',
|
|
91
|
+
} : undefined, children: [_jsx("input", { ref: inputRef, type: "file", accept: acceptStr, multiple: multiple, onChange: handleInputChange, style: { display: 'none' } }), children || (_jsx(_Fragment, { children: uploading ? (_jsxs("div", { children: [_jsxs("div", { style: { marginBottom: '0.5rem' }, children: ["Uploading... ", progress, "%"] }), _jsx("div", { style: {
|
|
92
|
+
width: '100%', height: '4px', backgroundColor: '#e5e7eb', borderRadius: '2px',
|
|
93
|
+
}, children: _jsx("div", { style: {
|
|
94
|
+
width: `${progress}%`, height: '100%', backgroundColor: '#3b82f6',
|
|
95
|
+
borderRadius: '2px', transition: 'width 0.3s',
|
|
96
|
+
} }) })] })) : result && renderResult ? (renderResult(result)) : (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginBottom: '0.25rem' }, children: label || (isDragActive ? 'Drop files here' : 'Click or drag files to upload') }), (sublabel || defaultSublabel) && (_jsx("div", { style: { fontSize: '0.875rem', color: '#6b7280' }, children: sublabel || defaultSublabel }))] })) })), error && (_jsx("div", { style: { color: '#ef4444', fontSize: '0.875rem', marginTop: '0.5rem' }, children: error }))] }));
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=StorageDropzone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageDropzone.js","sourceRoot":"","sources":["../../../src/frontend/storage/StorageDropzone.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { configureStorageUrls } from './storageUrl.js';
|
|
2
|
-
export type { StorageUrlHelpers, PhotoSize, Photo } from './storageUrl.js';
|
|
2
|
+
export type { StorageUrlHelpers, PhotoSize, Photo, ImageTransformParams } from './storageUrl.js';
|
|
3
3
|
export { useStorageUpload } from './useStorageUpload.js';
|
|
4
4
|
export type { UseStorageUploadOptions, UseStorageUploadReturn, UploadResult } from './useStorageUpload.js';
|
|
5
|
+
export { StorageDropzone } from './StorageDropzone.js';
|
|
6
|
+
export type { StorageDropzoneProps } from './StorageDropzone.js';
|
|
5
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/frontend/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/frontend/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACjG,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC3G,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/frontend/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/frontend/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Storage URL Builders — construct proxy URLs for stored images
|
|
3
3
|
*
|
|
4
|
+
* Supports on-the-fly image transforms via query parameters:
|
|
5
|
+
* ?w=400 — resize to max width
|
|
6
|
+
* ?h=300 — resize to max height
|
|
7
|
+
* ?q=80 — quality 1-100
|
|
8
|
+
* ?f=webp — output format
|
|
9
|
+
*
|
|
4
10
|
* Usage:
|
|
5
11
|
* ```typescript
|
|
6
12
|
* import { configureStorageUrls } from '@soulbatical/tetra-core/frontend';
|
|
7
13
|
*
|
|
8
14
|
* const storage = configureStorageUrls('https://api.myapp.com');
|
|
9
|
-
*
|
|
10
|
-
* //
|
|
15
|
+
*
|
|
16
|
+
* // Original size
|
|
17
|
+
* storage.buildStorageUrl('images', orgId, 'photo.png');
|
|
18
|
+
*
|
|
19
|
+
* // Thumbnail (400px wide, webp)
|
|
20
|
+
* storage.buildStorageUrl('images', orgId, 'photo.png', { w: 400, f: 'webp' });
|
|
21
|
+
*
|
|
22
|
+
* // Responsive srcset with on-the-fly resize
|
|
23
|
+
* storage.buildImageSrcSet('images', orgId, 'photo.png');
|
|
24
|
+
* // → ".../photo.png?w=150&f=webp 150w, .../photo.png?w=600&f=webp 600w, ..."
|
|
11
25
|
* ```
|
|
12
26
|
*/
|
|
13
27
|
export type PhotoSize = 'thumb' | 'medium' | 'large' | 'original';
|
|
@@ -16,13 +30,25 @@ export interface Photo {
|
|
|
16
30
|
storage_prefix?: string;
|
|
17
31
|
storage_path?: string;
|
|
18
32
|
}
|
|
33
|
+
export interface ImageTransformParams {
|
|
34
|
+
/** Max width in pixels */
|
|
35
|
+
w?: number;
|
|
36
|
+
/** Max height in pixels */
|
|
37
|
+
h?: number;
|
|
38
|
+
/** Quality 1-100 (default: 80) */
|
|
39
|
+
q?: number;
|
|
40
|
+
/** Output format */
|
|
41
|
+
f?: 'webp' | 'jpeg' | 'png';
|
|
42
|
+
}
|
|
19
43
|
export interface StorageUrlHelpers {
|
|
20
|
-
/** Build a proxy URL for any stored file */
|
|
21
|
-
buildStorageUrl(bucket: string, orgId: string, fileId: string): string;
|
|
44
|
+
/** Build a proxy URL for any stored file, with optional image transforms */
|
|
45
|
+
buildStorageUrl(bucket: string, orgId: string, fileId: string, transform?: ImageTransformParams): string;
|
|
22
46
|
/** Build a photo URL for a specific size (expects {size}_{filename} convention) */
|
|
23
47
|
buildPhotoUrl(bucket: string, photo: Photo, size: PhotoSize): string;
|
|
24
|
-
/** Build a srcSet string for
|
|
48
|
+
/** Build a srcSet string for multi-size photos (pre-generated sizes) */
|
|
25
49
|
buildPhotoSrcSet(bucket: string, photo: Photo): string;
|
|
50
|
+
/** Build a srcSet string using on-the-fly resize (no pre-generated sizes needed) */
|
|
51
|
+
buildImageSrcSet(bucket: string, orgId: string, fileId: string, widths?: number[]): string;
|
|
26
52
|
}
|
|
27
53
|
/**
|
|
28
54
|
* Configure storage URL helpers with a base API URL.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storageUrl.d.ts","sourceRoot":"","sources":["../../../src/frontend/storage/storageUrl.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"storageUrl.d.ts","sourceRoot":"","sources":["../../../src/frontend/storage/storageUrl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAElE,MAAM,WAAW,KAAK;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,0BAA0B;IAC1B,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,2BAA2B;IAC3B,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,oBAAoB,GAAG,MAAM,CAAC;IACzG,mFAAmF;IACnF,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;IACrE,wEAAwE;IACxE,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC;IACvD,oFAAoF;IACpF,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;CAC5F;AAsBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,iBAAiB,CAiC1E"}
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Storage URL Builders — construct proxy URLs for stored images
|
|
3
3
|
*
|
|
4
|
+
* Supports on-the-fly image transforms via query parameters:
|
|
5
|
+
* ?w=400 — resize to max width
|
|
6
|
+
* ?h=300 — resize to max height
|
|
7
|
+
* ?q=80 — quality 1-100
|
|
8
|
+
* ?f=webp — output format
|
|
9
|
+
*
|
|
4
10
|
* Usage:
|
|
5
11
|
* ```typescript
|
|
6
12
|
* import { configureStorageUrls } from '@soulbatical/tetra-core/frontend';
|
|
7
13
|
*
|
|
8
14
|
* const storage = configureStorageUrls('https://api.myapp.com');
|
|
9
|
-
*
|
|
10
|
-
* //
|
|
15
|
+
*
|
|
16
|
+
* // Original size
|
|
17
|
+
* storage.buildStorageUrl('images', orgId, 'photo.png');
|
|
18
|
+
*
|
|
19
|
+
* // Thumbnail (400px wide, webp)
|
|
20
|
+
* storage.buildStorageUrl('images', orgId, 'photo.png', { w: 400, f: 'webp' });
|
|
21
|
+
*
|
|
22
|
+
* // Responsive srcset with on-the-fly resize
|
|
23
|
+
* storage.buildImageSrcSet('images', orgId, 'photo.png');
|
|
24
|
+
* // → ".../photo.png?w=150&f=webp 150w, .../photo.png?w=600&f=webp 600w, ..."
|
|
11
25
|
* ```
|
|
12
26
|
*/
|
|
13
27
|
const SIZE_WIDTHS = {
|
|
@@ -16,18 +30,33 @@ const SIZE_WIDTHS = {
|
|
|
16
30
|
large: 1200,
|
|
17
31
|
original: 2000,
|
|
18
32
|
};
|
|
33
|
+
const DEFAULT_SRCSET_WIDTHS = [150, 400, 800, 1200, 2000];
|
|
34
|
+
function buildTransformQuery(transform) {
|
|
35
|
+
if (!transform)
|
|
36
|
+
return '';
|
|
37
|
+
const params = new URLSearchParams();
|
|
38
|
+
if (transform.w)
|
|
39
|
+
params.set('w', String(transform.w));
|
|
40
|
+
if (transform.h)
|
|
41
|
+
params.set('h', String(transform.h));
|
|
42
|
+
if (transform.q)
|
|
43
|
+
params.set('q', String(transform.q));
|
|
44
|
+
if (transform.f)
|
|
45
|
+
params.set('f', transform.f);
|
|
46
|
+
const str = params.toString();
|
|
47
|
+
return str ? `?${str}` : '';
|
|
48
|
+
}
|
|
19
49
|
/**
|
|
20
50
|
* Configure storage URL helpers with a base API URL.
|
|
21
51
|
* Call once at app startup, use the returned helpers everywhere.
|
|
22
52
|
*/
|
|
23
53
|
export function configureStorageUrls(apiBaseUrl) {
|
|
24
54
|
const base = apiBaseUrl.replace(/\/$/, '');
|
|
25
|
-
function buildStorageUrl(bucket, orgId, fileId) {
|
|
26
|
-
return `${base}/api/public/storage/${bucket}/${orgId}/${fileId}`;
|
|
55
|
+
function buildStorageUrl(bucket, orgId, fileId, transform) {
|
|
56
|
+
return `${base}/api/public/storage/${bucket}/${orgId}/${fileId}${buildTransformQuery(transform)}`;
|
|
27
57
|
}
|
|
28
58
|
function buildPhotoUrl(bucket, photo, size) {
|
|
29
59
|
if (photo.storage_path) {
|
|
30
|
-
// Full path provided — use directly
|
|
31
60
|
return `${base}/api/public/storage/${bucket}/${photo.storage_path}`;
|
|
32
61
|
}
|
|
33
62
|
const prefix = photo.storage_prefix || '';
|
|
@@ -41,6 +70,11 @@ export function configureStorageUrls(apiBaseUrl) {
|
|
|
41
70
|
.map((size) => `${buildPhotoUrl(bucket, photo, size)} ${SIZE_WIDTHS[size]}w`)
|
|
42
71
|
.join(', ');
|
|
43
72
|
}
|
|
44
|
-
|
|
73
|
+
function buildImageSrcSet(bucket, orgId, fileId, widths) {
|
|
74
|
+
return (widths || DEFAULT_SRCSET_WIDTHS)
|
|
75
|
+
.map((w) => `${buildStorageUrl(bucket, orgId, fileId, { w, f: 'webp', q: 80 })} ${w}w`)
|
|
76
|
+
.join(', ');
|
|
77
|
+
}
|
|
78
|
+
return { buildStorageUrl, buildPhotoUrl, buildPhotoSrcSet, buildImageSrcSet };
|
|
45
79
|
}
|
|
46
80
|
//# sourceMappingURL=storageUrl.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storageUrl.js","sourceRoot":"","sources":["../../../src/frontend/storage/storageUrl.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"storageUrl.js","sourceRoot":"","sources":["../../../src/frontend/storage/storageUrl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAgCH,MAAM,WAAW,GAA8B;IAC7C,KAAK,EAAE,GAAG;IACV,MAAM,EAAE,GAAG;IACX,KAAK,EAAE,IAAI;IACX,QAAQ,EAAE,IAAI;CACf,CAAC;AAEF,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAE1D,SAAS,mBAAmB,CAAC,SAAgC;IAC3D,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,CAAC;IAC1B,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;IACrC,IAAI,SAAS,CAAC,CAAC;QAAE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,IAAI,SAAS,CAAC,CAAC;QAAE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,IAAI,SAAS,CAAC,CAAC;QAAE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,IAAI,SAAS,CAAC,CAAC;QAAE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC9B,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB;IACrD,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAE3C,SAAS,eAAe,CAAC,MAAc,EAAE,KAAa,EAAE,MAAc,EAAE,SAAgC;QACtG,OAAO,GAAG,IAAI,uBAAuB,MAAM,IAAI,KAAK,IAAI,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,EAAE,CAAC;IACpG,CAAC;IAED,SAAS,aAAa,CAAC,MAAc,EAAE,KAAY,EAAE,IAAe;QAClE,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;YACvB,OAAO,GAAG,IAAI,uBAAuB,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QACtE,CAAC;QAED,MAAM,MAAM,GAAG,KAAK,CAAC,cAAc,IAAI,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,aAAa,IAAI,EAAE,CAAC;QAC3C,MAAM,aAAa,GAAG,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC7E,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,aAAa,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;QAEnE,OAAO,GAAG,IAAI,uBAAuB,MAAM,IAAI,IAAI,EAAE,CAAC;IACxD,CAAC;IAED,SAAS,gBAAgB,CAAC,MAAc,EAAE,KAAY;QACpD,OAAQ,MAAM,CAAC,IAAI,CAAC,WAAW,CAAiB;aAC7C,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;aAC5E,IAAI,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAED,SAAS,gBAAgB,CAAC,MAAc,EAAE,KAAa,EAAE,MAAc,EAAE,MAAiB;QACxF,OAAO,CAAC,MAAM,IAAI,qBAAqB,CAAC;aACrC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,eAAe,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;aACtF,IAAI,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC;IAED,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;AAChF,CAAC"}
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* React hook for uploading files to storage
|
|
2
|
+
* React hook for uploading files to storage — with progress, validation, and abort
|
|
3
|
+
*
|
|
4
|
+
* Inspired by UploadThing's API, adapted for Tetra's proxy-based storage.
|
|
3
5
|
*
|
|
4
6
|
* Usage:
|
|
5
7
|
* ```typescript
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* const { upload, uploading, error, result } = useStorageUpload({
|
|
8
|
+
* const { upload, uploading, progress, error, result } = useStorageUpload({
|
|
9
9
|
* apiBaseUrl: process.env.NEXT_PUBLIC_API_URL,
|
|
10
|
-
* bucket: '
|
|
11
|
-
*
|
|
10
|
+
* bucket: 'images',
|
|
11
|
+
* onProgress: (p) => console.log(`${p}%`),
|
|
12
|
+
* onComplete: (result) => console.log(result.proxyUrl),
|
|
13
|
+
* onError: (err) => alert(err),
|
|
14
|
+
* maxSizeBytes: 10 * 1024 * 1024,
|
|
15
|
+
* acceptedTypes: ['image/jpeg', 'image/png', 'image/webp'],
|
|
12
16
|
* });
|
|
13
|
-
*
|
|
14
|
-
* const handleFile = async (file: File) => {
|
|
15
|
-
* const uploaded = await upload(file);
|
|
16
|
-
* console.log(uploaded.proxyUrl);
|
|
17
|
-
* };
|
|
18
17
|
* ```
|
|
19
18
|
*/
|
|
20
19
|
export interface UploadResult {
|
|
@@ -23,19 +22,53 @@ export interface UploadResult {
|
|
|
23
22
|
proxyUrl: string;
|
|
24
23
|
contentType: string;
|
|
25
24
|
size: number;
|
|
25
|
+
/** Multi-size data (if generateMultipleSizes was requested) */
|
|
26
|
+
base_filename?: string;
|
|
27
|
+
storage_prefix?: string;
|
|
28
|
+
sizes?: Record<string, {
|
|
29
|
+
path: string;
|
|
30
|
+
publicUrl: string;
|
|
31
|
+
}>;
|
|
26
32
|
}
|
|
27
33
|
export interface UseStorageUploadOptions {
|
|
34
|
+
/** API base URL (e.g. https://api.myapp.com) */
|
|
28
35
|
apiBaseUrl: string;
|
|
36
|
+
/** Target bucket name */
|
|
29
37
|
bucket: string;
|
|
30
|
-
|
|
38
|
+
/** Organization ID (for path scoping) */
|
|
39
|
+
orgId?: string;
|
|
31
40
|
/** Upload endpoint path — defaults to '/api/admin/storage/upload' */
|
|
32
41
|
uploadPath?: string;
|
|
42
|
+
/** Generate multiple image sizes (thumb, medium, large, original) */
|
|
43
|
+
generateMultipleSizes?: boolean;
|
|
44
|
+
/** Max file size in bytes (validated client-side before upload) */
|
|
45
|
+
maxSizeBytes?: number;
|
|
46
|
+
/** Accepted MIME types (validated client-side) */
|
|
47
|
+
acceptedTypes?: string[];
|
|
48
|
+
/** Storage folder/path prefix */
|
|
49
|
+
folder?: string;
|
|
50
|
+
onProgress?: (percent: number) => void;
|
|
51
|
+
onComplete?: (result: UploadResult) => void;
|
|
52
|
+
onError?: (error: string) => void;
|
|
53
|
+
onBeforeUpload?: (file: File) => boolean | File | void;
|
|
33
54
|
}
|
|
34
55
|
export interface UseStorageUploadReturn {
|
|
56
|
+
/** Upload a single file */
|
|
35
57
|
upload: (file: File) => Promise<UploadResult>;
|
|
58
|
+
/** Upload multiple files sequentially */
|
|
59
|
+
uploadMultiple: (files: File[]) => Promise<UploadResult[]>;
|
|
60
|
+
/** Whether an upload is in progress */
|
|
36
61
|
uploading: boolean;
|
|
62
|
+
/** Upload progress 0-100 */
|
|
63
|
+
progress: number;
|
|
64
|
+
/** Last error message */
|
|
37
65
|
error: string | null;
|
|
66
|
+
/** Last upload result */
|
|
38
67
|
result: UploadResult | null;
|
|
68
|
+
/** Abort current upload */
|
|
69
|
+
abort: () => void;
|
|
70
|
+
/** Validate a file without uploading */
|
|
71
|
+
validate: (file: File) => string | null;
|
|
39
72
|
}
|
|
40
73
|
export declare function useStorageUpload(options: UseStorageUploadOptions): UseStorageUploadReturn;
|
|
41
74
|
//# sourceMappingURL=useStorageUpload.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStorageUpload.d.ts","sourceRoot":"","sources":["../../../src/frontend/storage/useStorageUpload.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"useStorageUpload.d.ts","sourceRoot":"","sources":["../../../src/frontend/storage/useStorageUpload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7D;AAED,MAAM,WAAW,uBAAuB;IACtC,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,yBAAyB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IAC5C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,GAAG,IAAI,GAAG,IAAI,CAAC;CACxD;AAED,MAAM,WAAW,sBAAsB;IACrC,2BAA2B;IAC3B,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAC9C,yCAAyC;IACzC,cAAc,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAC3D,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,yBAAyB;IACzB,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,2BAA2B;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,wCAAwC;IACxC,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,GAAG,IAAI,CAAC;CACzC;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,sBAAsB,CAqKzF"}
|
|
@@ -1,58 +1,164 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* React hook for uploading files to storage
|
|
2
|
+
* React hook for uploading files to storage — with progress, validation, and abort
|
|
3
|
+
*
|
|
4
|
+
* Inspired by UploadThing's API, adapted for Tetra's proxy-based storage.
|
|
3
5
|
*
|
|
4
6
|
* Usage:
|
|
5
7
|
* ```typescript
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* const { upload, uploading, error, result } = useStorageUpload({
|
|
8
|
+
* const { upload, uploading, progress, error, result } = useStorageUpload({
|
|
9
9
|
* apiBaseUrl: process.env.NEXT_PUBLIC_API_URL,
|
|
10
|
-
* bucket: '
|
|
11
|
-
*
|
|
10
|
+
* bucket: 'images',
|
|
11
|
+
* onProgress: (p) => console.log(`${p}%`),
|
|
12
|
+
* onComplete: (result) => console.log(result.proxyUrl),
|
|
13
|
+
* onError: (err) => alert(err),
|
|
14
|
+
* maxSizeBytes: 10 * 1024 * 1024,
|
|
15
|
+
* acceptedTypes: ['image/jpeg', 'image/png', 'image/webp'],
|
|
12
16
|
* });
|
|
13
|
-
*
|
|
14
|
-
* const handleFile = async (file: File) => {
|
|
15
|
-
* const uploaded = await upload(file);
|
|
16
|
-
* console.log(uploaded.proxyUrl);
|
|
17
|
-
* };
|
|
18
17
|
* ```
|
|
19
18
|
*/
|
|
20
|
-
import { useState, useCallback } from 'react';
|
|
19
|
+
import { useState, useCallback, useRef } from 'react';
|
|
21
20
|
export function useStorageUpload(options) {
|
|
22
|
-
const { apiBaseUrl, bucket, orgId, uploadPath = '/api/admin/storage/upload' } = options;
|
|
21
|
+
const { apiBaseUrl, bucket, orgId, uploadPath = '/api/admin/storage/upload', generateMultipleSizes = false, maxSizeBytes, acceptedTypes, folder, onProgress, onComplete, onError, onBeforeUpload, } = options;
|
|
23
22
|
const [uploading, setUploading] = useState(false);
|
|
23
|
+
const [progress, setProgress] = useState(0);
|
|
24
24
|
const [error, setError] = useState(null);
|
|
25
25
|
const [result, setResult] = useState(null);
|
|
26
|
+
const abortRef = useRef(null);
|
|
27
|
+
const validate = useCallback((file) => {
|
|
28
|
+
if (maxSizeBytes && file.size > maxSizeBytes) {
|
|
29
|
+
const maxMB = (maxSizeBytes / 1024 / 1024).toFixed(1);
|
|
30
|
+
return `File too large (${(file.size / 1024 / 1024).toFixed(1)}MB). Max: ${maxMB}MB`;
|
|
31
|
+
}
|
|
32
|
+
if (acceptedTypes && acceptedTypes.length > 0 && !acceptedTypes.includes(file.type)) {
|
|
33
|
+
return `File type "${file.type}" not accepted. Allowed: ${acceptedTypes.join(', ')}`;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}, [maxSizeBytes, acceptedTypes]);
|
|
26
37
|
const upload = useCallback(async (file) => {
|
|
38
|
+
// Client-side validation
|
|
39
|
+
const validationError = validate(file);
|
|
40
|
+
if (validationError) {
|
|
41
|
+
setError(validationError);
|
|
42
|
+
onError?.(validationError);
|
|
43
|
+
throw new Error(validationError);
|
|
44
|
+
}
|
|
45
|
+
// Before upload hook
|
|
46
|
+
if (onBeforeUpload) {
|
|
47
|
+
const hookResult = onBeforeUpload(file);
|
|
48
|
+
if (hookResult === false) {
|
|
49
|
+
const msg = 'Upload cancelled by onBeforeUpload';
|
|
50
|
+
setError(msg);
|
|
51
|
+
throw new Error(msg);
|
|
52
|
+
}
|
|
53
|
+
if (hookResult instanceof File) {
|
|
54
|
+
file = hookResult;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
27
57
|
setUploading(true);
|
|
58
|
+
setProgress(0);
|
|
28
59
|
setError(null);
|
|
29
60
|
setResult(null);
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
abortRef.current = controller;
|
|
30
63
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
64
|
+
// Convert file to base64
|
|
65
|
+
const base64 = await fileToBase64(file);
|
|
66
|
+
const base64Data = base64.split(',')[1]; // Remove data:type;base64, prefix
|
|
67
|
+
const timestamp = Date.now();
|
|
68
|
+
const safeFilename = file.name.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
69
|
+
const pathPrefix = folder || (orgId ? `uploads/${orgId}` : 'uploads');
|
|
70
|
+
const path = `${pathPrefix}/${timestamp}-${safeFilename}`;
|
|
71
|
+
setProgress(10); // File read complete
|
|
72
|
+
onProgress?.(10);
|
|
34
73
|
const base = apiBaseUrl.replace(/\/$/, '');
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
74
|
+
const body = {
|
|
75
|
+
bucketName: bucket,
|
|
76
|
+
path,
|
|
77
|
+
file: base64Data,
|
|
78
|
+
contentType: file.type,
|
|
79
|
+
generateMultipleSizes,
|
|
80
|
+
};
|
|
81
|
+
// Use XMLHttpRequest for upload progress
|
|
82
|
+
const uploadResult = await new Promise((resolve, reject) => {
|
|
83
|
+
const xhr = new XMLHttpRequest();
|
|
84
|
+
xhr.upload.addEventListener('progress', (e) => {
|
|
85
|
+
if (e.lengthComputable) {
|
|
86
|
+
const pct = Math.round(10 + (e.loaded / e.total) * 80); // 10-90%
|
|
87
|
+
setProgress(pct);
|
|
88
|
+
onProgress?.(pct);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
xhr.addEventListener('load', () => {
|
|
92
|
+
try {
|
|
93
|
+
const json = JSON.parse(xhr.responseText);
|
|
94
|
+
if (xhr.status >= 200 && xhr.status < 300 && json.success) {
|
|
95
|
+
setProgress(100);
|
|
96
|
+
onProgress?.(100);
|
|
97
|
+
const uploadResult = {
|
|
98
|
+
bucket,
|
|
99
|
+
path: json.data.path || path,
|
|
100
|
+
proxyUrl: json.data.publicUrl || json.data.proxyUrl || '',
|
|
101
|
+
contentType: file.type,
|
|
102
|
+
size: file.size,
|
|
103
|
+
...(json.data.base_filename && {
|
|
104
|
+
base_filename: json.data.base_filename,
|
|
105
|
+
storage_prefix: json.data.storage_prefix,
|
|
106
|
+
sizes: json.data.sizes,
|
|
107
|
+
}),
|
|
108
|
+
};
|
|
109
|
+
resolve(uploadResult);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
reject(new Error(json.message || json.error || 'Upload failed'));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
reject(new Error('Invalid server response'));
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
xhr.addEventListener('error', () => reject(new Error('Network error')));
|
|
120
|
+
xhr.addEventListener('abort', () => reject(new Error('Upload aborted')));
|
|
121
|
+
// Abort support
|
|
122
|
+
controller.signal.addEventListener('abort', () => xhr.abort());
|
|
123
|
+
xhr.open('POST', `${base}${uploadPath}`);
|
|
124
|
+
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
125
|
+
xhr.withCredentials = true;
|
|
126
|
+
xhr.send(JSON.stringify(body));
|
|
39
127
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
setResult(json.data);
|
|
45
|
-
return json.data;
|
|
128
|
+
setResult(uploadResult);
|
|
129
|
+
onComplete?.(uploadResult);
|
|
130
|
+
return uploadResult;
|
|
46
131
|
}
|
|
47
132
|
catch (err) {
|
|
48
133
|
const message = err instanceof Error ? err.message : 'Upload failed';
|
|
49
134
|
setError(message);
|
|
135
|
+
onError?.(message);
|
|
50
136
|
throw err;
|
|
51
137
|
}
|
|
52
138
|
finally {
|
|
53
139
|
setUploading(false);
|
|
140
|
+
abortRef.current = null;
|
|
141
|
+
}
|
|
142
|
+
}, [apiBaseUrl, bucket, orgId, uploadPath, generateMultipleSizes, maxSizeBytes, acceptedTypes, folder, onProgress, onComplete, onError, onBeforeUpload, validate]);
|
|
143
|
+
const uploadMultiple = useCallback(async (files) => {
|
|
144
|
+
const results = [];
|
|
145
|
+
for (const file of files) {
|
|
146
|
+
const r = await upload(file);
|
|
147
|
+
results.push(r);
|
|
54
148
|
}
|
|
55
|
-
|
|
56
|
-
|
|
149
|
+
return results;
|
|
150
|
+
}, [upload]);
|
|
151
|
+
const abort = useCallback(() => {
|
|
152
|
+
abortRef.current?.abort();
|
|
153
|
+
}, []);
|
|
154
|
+
return { upload, uploadMultiple, uploading, progress, error, result, abort, validate };
|
|
155
|
+
}
|
|
156
|
+
function fileToBase64(file) {
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
const reader = new FileReader();
|
|
159
|
+
reader.readAsDataURL(file);
|
|
160
|
+
reader.onload = () => resolve(reader.result);
|
|
161
|
+
reader.onerror = (error) => reject(error);
|
|
162
|
+
});
|
|
57
163
|
}
|
|
58
164
|
//# sourceMappingURL=useStorageUpload.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStorageUpload.js","sourceRoot":"","sources":["../../../src/frontend/storage/useStorageUpload.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"useStorageUpload.js","sourceRoot":"","sources":["../../../src/frontend/storage/useStorageUpload.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mailgun.js","sourceRoot":"","sources":["../../../src/shared/email/mailgun.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,mBAAmB,GAAG,+BAA+B,CAAC;AAE5D,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAWtC;IACC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAElC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;QACrE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC;IAC/D,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,IAAI,mBAAmB,CAAC;IAEvD,IAAI,CAAC;QACH,IAAI,IAAS,CAAC;QACd,IAAI,WAA+B,CAAC;QAEpC,IAAI,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;YAC/B,2CAA2C;YAC3C,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;YAChC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACrC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;YACjC,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3C,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,MAAM,CAAC,IAAI;gBAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACtD,IAAI,MAAM,CAAC,OAAO;gBAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YAElE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;gBACrC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"mailgun.js","sourceRoot":"","sources":["../../../src/shared/email/mailgun.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,mBAAmB,GAAG,+BAA+B,CAAC;AAE5D,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAWtC;IACC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAElC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;QACrE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC;IAC/D,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,IAAI,mBAAmB,CAAC;IAEvD,IAAI,CAAC;QACH,IAAI,IAAS,CAAC;QACd,IAAI,WAA+B,CAAC;QAEpC,IAAI,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;YAC/B,2CAA2C;YAC3C,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;YAChC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACrC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;YACjC,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3C,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,MAAM,CAAC,IAAI;gBAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACtD,IAAI,MAAM,CAAC,OAAO;gBAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YAElE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;gBACrC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,IAA2B,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,WAAW,IAAI,0BAA0B,EAAE,CAAC,CAAC;gBAClH,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;YACpD,CAAC;YAED,IAAI,GAAG,QAAQ,CAAC;YAChB,+CAA+C;QACjD,CAAC;aAAM,CAAC;YACN,2CAA2C;YAC3C,MAAM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;YACvC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACrC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;YACjC,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3C,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,MAAM,CAAC,IAAI;gBAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACtD,IAAI,MAAM,CAAC,OAAO;gBAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YAElE,IAAI,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAC;YAC3B,WAAW,GAAG,mCAAmC,CAAC;QACpD,CAAC;QAED,MAAM,OAAO,GAA2B;YACtC,aAAa,EAAE,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;SAC1E,CAAC;QACF,IAAI,WAAW;YAAE,OAAO,CAAC,cAAc,CAAC,GAAG,WAAW,CAAC;QAEvD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,IAAI,MAAM,WAAW,EAAE;YACxD,MAAM,EAAE,MAAM;YACd,OAAO;YACP,IAAI;SACL,CAAC,CAAC;QAEH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YAC/C,OAAO,CAAC,KAAK,CAAC,6BAA6B,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC;YACnE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,kBAAkB,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QACtE,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAsC,CAAC;QACtE,OAAO;YACL,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,YAAY;YACrC,SAAS,EAAE,IAAI,CAAC,EAAE;SACnB,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,GAAG,CAAC,CAAC;QACnD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,gBAAgB,GAAG,EAAE,EAAE,CAAC;IAC5D,CAAC;AACH,CAAC"}
|
|
@@ -3,17 +3,38 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Sets proper CORS/CORP/Cache headers so images work cross-origin.
|
|
5
5
|
* Never exposes Supabase URLs to the client.
|
|
6
|
+
*
|
|
7
|
+
* Supports on-the-fly image transforms via query parameters:
|
|
8
|
+
* ?w=400 — resize to max width 400px (aspect ratio preserved)
|
|
9
|
+
* ?h=300 — resize to max height 300px
|
|
10
|
+
* ?q=80 — quality 1-100 (default: 80)
|
|
11
|
+
* ?f=webp — output format: webp, jpeg, png (default: auto from original)
|
|
12
|
+
*
|
|
13
|
+
* Transforms require sharp as optional peer dependency.
|
|
14
|
+
* Without sharp, images are served at original size.
|
|
6
15
|
*/
|
|
7
16
|
import type { Response } from 'express';
|
|
8
17
|
import type { StorageConfig } from './types.js';
|
|
18
|
+
export interface ImageTransformOptions {
|
|
19
|
+
width?: number;
|
|
20
|
+
height?: number;
|
|
21
|
+
quality?: number;
|
|
22
|
+
format?: 'webp' | 'jpeg' | 'png';
|
|
23
|
+
}
|
|
9
24
|
export declare class StorageProxyService {
|
|
10
25
|
private readonly supabaseUrl;
|
|
11
26
|
private readonly cacheMaxAge;
|
|
12
27
|
constructor(config: StorageConfig);
|
|
28
|
+
/**
|
|
29
|
+
* Parse transform options from query parameters.
|
|
30
|
+
*/
|
|
31
|
+
static parseTransformOptions(query: Record<string, any>): ImageTransformOptions | null;
|
|
13
32
|
/**
|
|
14
33
|
* Stream a file from Supabase storage to the response.
|
|
15
|
-
*
|
|
34
|
+
* Optionally transforms images on-the-fly with sharp.
|
|
16
35
|
*/
|
|
17
|
-
streamFile(bucket: string, orgId: string, fileId: string, res: Response): Promise<void>;
|
|
36
|
+
streamFile(bucket: string, orgId: string, fileId: string, res: Response, transform?: ImageTransformOptions | null): Promise<void>;
|
|
37
|
+
private setHeaders;
|
|
38
|
+
private transformImage;
|
|
18
39
|
}
|
|
19
40
|
//# sourceMappingURL=StorageProxyService.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StorageProxyService.d.ts","sourceRoot":"","sources":["../../../src/shared/storage/StorageProxyService.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"StorageProxyService.d.ts","sourceRoot":"","sources":["../../../src/shared/storage/StorageProxyService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAKhD,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;CAClC;AA0BD,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,MAAM,EAAE,aAAa;IAKjC;;OAEG;IACH,MAAM,CAAC,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,qBAAqB,GAAG,IAAI;IAgBtF;;;OAGG;IACG,UAAU,CACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,QAAQ,EACb,SAAS,CAAC,EAAE,qBAAqB,GAAG,IAAI,GACvC,OAAO,CAAC,IAAI,CAAC;IAoFhB,OAAO,CAAC,UAAU;YAOJ,cAAc;CAiC7B"}
|
|
@@ -3,9 +3,41 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Sets proper CORS/CORP/Cache headers so images work cross-origin.
|
|
5
5
|
* Never exposes Supabase URLs to the client.
|
|
6
|
+
*
|
|
7
|
+
* Supports on-the-fly image transforms via query parameters:
|
|
8
|
+
* ?w=400 — resize to max width 400px (aspect ratio preserved)
|
|
9
|
+
* ?h=300 — resize to max height 300px
|
|
10
|
+
* ?q=80 — quality 1-100 (default: 80)
|
|
11
|
+
* ?f=webp — output format: webp, jpeg, png (default: auto from original)
|
|
12
|
+
*
|
|
13
|
+
* Transforms require sharp as optional peer dependency.
|
|
14
|
+
* Without sharp, images are served at original size.
|
|
6
15
|
*/
|
|
7
16
|
import { createLogger } from '../../utils/logger.js';
|
|
8
17
|
const logger = createLogger('storage:proxy');
|
|
18
|
+
// Simple LRU-ish in-memory cache for transformed images
|
|
19
|
+
const transformCache = new Map();
|
|
20
|
+
const MAX_CACHE_SIZE = 200;
|
|
21
|
+
const CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
|
22
|
+
function getCached(key) {
|
|
23
|
+
const entry = transformCache.get(key);
|
|
24
|
+
if (!entry)
|
|
25
|
+
return null;
|
|
26
|
+
if (Date.now() - entry.timestamp > CACHE_TTL_MS) {
|
|
27
|
+
transformCache.delete(key);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return entry;
|
|
31
|
+
}
|
|
32
|
+
function setCache(key, buffer, contentType) {
|
|
33
|
+
// Evict oldest if at capacity
|
|
34
|
+
if (transformCache.size >= MAX_CACHE_SIZE) {
|
|
35
|
+
const oldest = transformCache.keys().next().value;
|
|
36
|
+
if (oldest)
|
|
37
|
+
transformCache.delete(oldest);
|
|
38
|
+
}
|
|
39
|
+
transformCache.set(key, { buffer, contentType, timestamp: Date.now() });
|
|
40
|
+
}
|
|
9
41
|
export class StorageProxyService {
|
|
10
42
|
supabaseUrl;
|
|
11
43
|
cacheMaxAge;
|
|
@@ -13,16 +45,45 @@ export class StorageProxyService {
|
|
|
13
45
|
this.supabaseUrl = config.supabaseUrl || process.env.SUPABASE_URL || '';
|
|
14
46
|
this.cacheMaxAge = config.cacheMaxAge ?? 3600;
|
|
15
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse transform options from query parameters.
|
|
50
|
+
*/
|
|
51
|
+
static parseTransformOptions(query) {
|
|
52
|
+
const w = query.w ? parseInt(query.w, 10) : undefined;
|
|
53
|
+
const h = query.h ? parseInt(query.h, 10) : undefined;
|
|
54
|
+
const q = query.q ? parseInt(query.q, 10) : undefined;
|
|
55
|
+
const f = query.f;
|
|
56
|
+
if (!w && !h && !q && !f)
|
|
57
|
+
return null;
|
|
58
|
+
return {
|
|
59
|
+
width: w && w > 0 && w <= 4000 ? w : undefined,
|
|
60
|
+
height: h && h > 0 && h <= 4000 ? h : undefined,
|
|
61
|
+
quality: q && q > 0 && q <= 100 ? q : 80,
|
|
62
|
+
format: f && ['webp', 'jpeg', 'png'].includes(f) ? f : undefined,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
16
65
|
/**
|
|
17
66
|
* Stream a file from Supabase storage to the response.
|
|
18
|
-
*
|
|
67
|
+
* Optionally transforms images on-the-fly with sharp.
|
|
19
68
|
*/
|
|
20
|
-
async streamFile(bucket, orgId, fileId, res) {
|
|
69
|
+
async streamFile(bucket, orgId, fileId, res, transform) {
|
|
21
70
|
if (!this.supabaseUrl) {
|
|
22
71
|
res.status(503).json({ success: false, error: 'Storage not configured' });
|
|
23
72
|
return;
|
|
24
73
|
}
|
|
25
74
|
const url = `${this.supabaseUrl}/storage/v1/object/public/${bucket}/${orgId}/${fileId}`;
|
|
75
|
+
// Check transform cache
|
|
76
|
+
if (transform) {
|
|
77
|
+
const cacheKey = `${url}:w${transform.width || ''}:h${transform.height || ''}:q${transform.quality || ''}:f${transform.format || ''}`;
|
|
78
|
+
const cached = getCached(cacheKey);
|
|
79
|
+
if (cached) {
|
|
80
|
+
this.setHeaders(res, cached.contentType);
|
|
81
|
+
res.setHeader('Content-Length', cached.buffer.length.toString());
|
|
82
|
+
res.setHeader('X-Transform-Cache', 'hit');
|
|
83
|
+
res.end(cached.buffer);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
26
87
|
try {
|
|
27
88
|
const upstream = await fetch(url);
|
|
28
89
|
if (!upstream.ok) {
|
|
@@ -33,10 +94,31 @@ export class StorageProxyService {
|
|
|
33
94
|
return;
|
|
34
95
|
}
|
|
35
96
|
const contentType = upstream.headers.get('content-type') || 'application/octet-stream';
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
97
|
+
const isImage = contentType.startsWith('image/');
|
|
98
|
+
// If transform requested and it's an image, try sharp
|
|
99
|
+
if (transform && isImage) {
|
|
100
|
+
try {
|
|
101
|
+
const buffer = Buffer.from(await upstream.arrayBuffer());
|
|
102
|
+
const transformed = await this.transformImage(buffer, transform);
|
|
103
|
+
const outType = transform.format
|
|
104
|
+
? `image/${transform.format}`
|
|
105
|
+
: contentType;
|
|
106
|
+
// Cache the result
|
|
107
|
+
const cacheKey = `${url}:w${transform.width || ''}:h${transform.height || ''}:q${transform.quality || ''}:f${transform.format || ''}`;
|
|
108
|
+
setCache(cacheKey, transformed, outType);
|
|
109
|
+
this.setHeaders(res, outType);
|
|
110
|
+
res.setHeader('Content-Length', transformed.length.toString());
|
|
111
|
+
res.setHeader('X-Transform', 'applied');
|
|
112
|
+
res.end(transformed);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
catch (transformError) {
|
|
116
|
+
// Sharp not available or transform failed — fall through to stream original
|
|
117
|
+
logger.debug({ error: transformError }, 'Transform failed, streaming original');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Stream original
|
|
121
|
+
this.setHeaders(res, contentType);
|
|
40
122
|
const contentLength = upstream.headers.get('content-length');
|
|
41
123
|
if (contentLength) {
|
|
42
124
|
res.setHeader('Content-Length', contentLength);
|
|
@@ -59,5 +141,39 @@ export class StorageProxyService {
|
|
|
59
141
|
res.status(502).json({ success: false, error: 'Failed to fetch from storage' });
|
|
60
142
|
}
|
|
61
143
|
}
|
|
144
|
+
setHeaders(res, contentType) {
|
|
145
|
+
res.setHeader('Content-Type', contentType);
|
|
146
|
+
res.setHeader('Cache-Control', `public, max-age=${this.cacheMaxAge}, immutable`);
|
|
147
|
+
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
|
148
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
149
|
+
}
|
|
150
|
+
async transformImage(buffer, options) {
|
|
151
|
+
// Dynamic import — sharp is optional peer dep
|
|
152
|
+
const sharp = (await import('sharp')).default;
|
|
153
|
+
let pipeline = sharp(buffer);
|
|
154
|
+
if (options.width || options.height) {
|
|
155
|
+
pipeline = pipeline.resize(options.width, options.height, {
|
|
156
|
+
fit: 'inside',
|
|
157
|
+
withoutEnlargement: true,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const quality = options.quality || 80;
|
|
161
|
+
switch (options.format) {
|
|
162
|
+
case 'webp':
|
|
163
|
+
pipeline = pipeline.webp({ quality });
|
|
164
|
+
break;
|
|
165
|
+
case 'jpeg':
|
|
166
|
+
pipeline = pipeline.jpeg({ quality, progressive: true });
|
|
167
|
+
break;
|
|
168
|
+
case 'png':
|
|
169
|
+
pipeline = pipeline.png({ quality });
|
|
170
|
+
break;
|
|
171
|
+
default:
|
|
172
|
+
// Auto-detect from original — apply quality
|
|
173
|
+
pipeline = pipeline.jpeg({ quality, progressive: true });
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
return pipeline.toBuffer();
|
|
177
|
+
}
|
|
62
178
|
}
|
|
63
179
|
//# sourceMappingURL=StorageProxyService.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StorageProxyService.js","sourceRoot":"","sources":["../../../src/shared/storage/StorageProxyService.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"StorageProxyService.js","sourceRoot":"","sources":["../../../src/shared/storage/StorageProxyService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,MAAM,GAAG,YAAY,CAAC,eAAe,CAAC,CAAC;AAS7C,wDAAwD;AACxD,MAAM,cAAc,GAAG,IAAI,GAAG,EAAsE,CAAC;AACrG,MAAM,cAAc,GAAG,GAAG,CAAC;AAC3B,MAAM,YAAY,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,aAAa;AAElD,SAAS,SAAS,CAAC,GAAW;IAC5B,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,GAAG,YAAY,EAAE,CAAC;QAChD,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW,EAAE,MAAc,EAAE,WAAmB;IAChE,8BAA8B;IAC9B,IAAI,cAAc,CAAC,IAAI,IAAI,cAAc,EAAE,CAAC;QAC1C,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QAClD,IAAI,MAAM;YAAE,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;IACD,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,OAAO,mBAAmB;IACb,WAAW,CAAS;IACpB,WAAW,CAAS;IAErC,YAAY,MAAqB;QAC/B,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;QACxE,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,IAAI,CAAC;IAChD,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,qBAAqB,CAAC,KAA0B;QACrD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACtD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACtD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACtD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAuB,CAAC;QAExC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAEtC,OAAO;YACL,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;YAC9C,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;YAC/C,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;YACxC,MAAM,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAQ,CAAC,CAAC,CAAC,SAAS;SACxE,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU,CACd,MAAc,EACd,KAAa,EACb,MAAc,EACd,GAAa,EACb,SAAwC;QAExC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC,CAAC;YAC1E,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,WAAW,6BAA6B,MAAM,IAAI,KAAK,IAAI,MAAM,EAAE,CAAC;QAExF,wBAAwB;QACxB,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,QAAQ,GAAG,GAAG,GAAG,KAAK,SAAS,CAAC,KAAK,IAAI,EAAE,KAAK,SAAS,CAAC,MAAM,IAAI,EAAE,KAAK,SAAS,CAAC,OAAO,IAAI,EAAE,KAAK,SAAS,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;YACtI,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;YACnC,IAAI,MAAM,EAAE,CAAC;gBACX,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;gBACzC,GAAG,CAAC,SAAS,CAAC,gBAAgB,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACjE,GAAG,CAAC,SAAS,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;gBAC1C,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACvB,OAAO;YACT,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;YAElC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACnD,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,eAAe;iBACpE,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,0BAA0B,CAAC;YACvF,MAAM,OAAO,GAAG,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YAEjD,sDAAsD;YACtD,IAAI,SAAS,IAAI,OAAO,EAAE,CAAC;gBACzB,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;oBACzD,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;oBACjE,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM;wBAC9B,CAAC,CAAC,SAAS,SAAS,CAAC,MAAM,EAAE;wBAC7B,CAAC,CAAC,WAAW,CAAC;oBAEhB,mBAAmB;oBACnB,MAAM,QAAQ,GAAG,GAAG,GAAG,KAAK,SAAS,CAAC,KAAK,IAAI,EAAE,KAAK,SAAS,CAAC,MAAM,IAAI,EAAE,KAAK,SAAS,CAAC,OAAO,IAAI,EAAE,KAAK,SAAS,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;oBACtI,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;oBAEzC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;oBAC9B,GAAG,CAAC,SAAS,CAAC,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;oBAC/D,GAAG,CAAC,SAAS,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;oBACxC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;oBACrB,OAAO;gBACT,CAAC;gBAAC,OAAO,cAAc,EAAE,CAAC;oBACxB,4EAA4E;oBAC5E,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,sCAAsC,CAAC,CAAC;gBAClF,CAAC;YACH,CAAC;YAED,kBAAkB;YAClB,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;YAClC,MAAM,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;YAC7D,IAAI,aAAa,EAAE,CAAC;gBAClB,GAAG,CAAC,SAAS,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;YACjD,CAAC;YAED,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC;YAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBACpE,OAAO;YACT,CAAC;YAED,OAAO,IAAI,EAAE,CAAC;gBACZ,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC5C,IAAI,IAAI;oBAAE,MAAM;gBAChB,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACnB,CAAC;YACD,GAAG,CAAC,GAAG,EAAE,CAAC;QACZ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,uBAAuB,CAAC,CAAC;YAC7E,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC,CAAC;QAClF,CAAC;IACH,CAAC;IAEO,UAAU,CAAC,GAAa,EAAE,WAAmB;QACnD,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;QAC3C,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,mBAAmB,IAAI,CAAC,WAAW,aAAa,CAAC,CAAC;QACjF,GAAG,CAAC,SAAS,CAAC,8BAA8B,EAAE,cAAc,CAAC,CAAC;QAC9D,GAAG,CAAC,SAAS,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;IACpD,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,MAAc,EAAE,OAA8B;QACzE,8CAA8C;QAC9C,MAAM,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAE9C,IAAI,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAE7B,IAAI,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACpC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE;gBACxD,GAAG,EAAE,QAAQ;gBACb,kBAAkB,EAAE,IAAI;aACzB,CAAC,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;QAEtC,QAAQ,OAAO,CAAC,MAAM,EAAE,CAAC;YACvB,KAAK,MAAM;gBACT,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;gBACtC,MAAM;YACR,KAAK,MAAM;gBACT,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;gBACzD,MAAM;YACR,KAAK,KAAK;gBACR,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;gBACrC,MAAM;YACR;gBACE,4CAA4C;gBAC5C,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;gBACzD,MAAM;QACV,CAAC;QAED,OAAO,QAAQ,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/shared/storage/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAGpD,OAAO,KAAK,EAAiB,mBAAmB,EAAE,oBAAoB,EAAgB,MAAM,YAAY,CAAC;AAKzG;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/shared/storage/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,MAAM,EAAqB,MAAM,SAAS,CAAC;AAGpD,OAAO,KAAK,EAAiB,mBAAmB,EAAE,oBAAoB,EAAgB,MAAM,YAAY,CAAC;AAKzG;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,IAAI,CA2BxF;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,GAAG,IAAI,CA8F1F"}
|
|
@@ -47,7 +47,9 @@ export function addStorageProxyRoutes(router, options) {
|
|
|
47
47
|
res.status(400).json({ success: false, error: 'Invalid bucket' });
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
// Parse optional image transform params: ?w=400&h=300&q=80&f=webp
|
|
51
|
+
const transform = StorageProxyService.parseTransformOptions(req.query);
|
|
52
|
+
await service.streamFile(bucket, orgId, fileId, res, transform);
|
|
51
53
|
});
|
|
52
54
|
logger.info({ buckets: config.allowedBuckets }, 'Storage proxy routes mounted');
|
|
53
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/shared/storage/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,MAAM,GAAG,YAAY,CAAC,gBAAgB,CAAC,CAAC;AAE9C;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAc,EAAE,OAA4B;IAChF,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAE3B,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,MAAM,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAElD,MAAM,CAAC,GAAG,CAAC,yBAAyB,EAAE,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;QAC1E,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,MAAgB,CAAC;QAC3C,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,KAAe,CAAC;QACzC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,MAAgB,CAAC;QAE3C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;YAClE,OAAO;QACT,CAAC;QAED,MAAM,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/shared/storage/routes.ts"],"names":[],"mappings":"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"}
|