@airoom/nextmin-node 1.3.0 → 1.4.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.
|
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.setupFileRoutes = setupFileRoutes;
|
|
7
7
|
const multer_1 = __importDefault(require("multer"));
|
|
8
|
-
const filename_1 = require("../../files/filename");
|
|
9
8
|
function setupFileRoutes(ctx) {
|
|
10
9
|
if (!ctx.fileStorage)
|
|
11
10
|
return;
|
|
@@ -24,13 +23,8 @@ function setupFileRoutes(ctx) {
|
|
|
24
23
|
.json({ error: true, message: 'No files uploaded' });
|
|
25
24
|
}
|
|
26
25
|
const results = await Promise.all(files.map(async (f) => {
|
|
27
|
-
const folder = shortFolder();
|
|
28
|
-
const ext = (f.originalname.match(/\.([A-Za-z0-9]{1,8})$/)?.[1] ??
|
|
29
|
-
(0, filename_1.extFromMime)(f.mimetype) ??
|
|
30
|
-
'bin').toLowerCase();
|
|
31
|
-
const key = `${folder}/${shortUid()}.${ext}`;
|
|
32
26
|
const out = await ctx.fileStorage.upload({
|
|
33
|
-
|
|
27
|
+
originalFilename: f.originalname,
|
|
34
28
|
body: f.buffer,
|
|
35
29
|
contentType: f.mimetype,
|
|
36
30
|
metadata: { originalName: f.originalname || '' },
|
|
@@ -68,13 +62,8 @@ function setupFileRoutes(ctx) {
|
|
|
68
62
|
.json({ error: true, message: 'No files uploaded' });
|
|
69
63
|
}
|
|
70
64
|
const results = await Promise.all(files.map(async (f) => {
|
|
71
|
-
const folder = shortFolder();
|
|
72
|
-
const ext = (f.originalname.match(/\.([A-Za-z0-9]{1,8})$/)?.[1] ??
|
|
73
|
-
(0, filename_1.extFromMime)(f.mimetype) ??
|
|
74
|
-
'bin').toLowerCase();
|
|
75
|
-
const key = `${folder}/${shortUid()}.${ext}`;
|
|
76
65
|
const out = await ctx.fileStorage.upload({
|
|
77
|
-
|
|
66
|
+
originalFilename: f.originalname,
|
|
78
67
|
body: f.buffer,
|
|
79
68
|
contentType: f.mimetype,
|
|
80
69
|
metadata: { originalName: f.originalname || '' },
|
|
@@ -126,13 +115,3 @@ function setupFileRoutes(ctx) {
|
|
|
126
115
|
}
|
|
127
116
|
});
|
|
128
117
|
}
|
|
129
|
-
function shortFolder() {
|
|
130
|
-
const d = new Date();
|
|
131
|
-
const y = d.getFullYear();
|
|
132
|
-
const m = String(d.getMonth() + 1).padStart(2, '0');
|
|
133
|
-
const day = String(d.getDate()).padStart(2, '0');
|
|
134
|
-
return `uploads/${y}/${m}/${day}`;
|
|
135
|
-
}
|
|
136
|
-
function shortUid() {
|
|
137
|
-
return (Date.now().toString(36) + Math.random().toString(36).slice(2, 6)).toLowerCase();
|
|
138
|
-
}
|
|
@@ -2,6 +2,8 @@ export type FileProvider = 's3' | 'gcs' | 'local' | (string & {});
|
|
|
2
2
|
export interface UploadPayload {
|
|
3
3
|
/** Path-like key inside bucket/provider, e.g. "uploads/userId/2025/08/18/file.png" */
|
|
4
4
|
key?: string;
|
|
5
|
+
/** Original filename for slugification (e.g., "My Document.pdf") */
|
|
6
|
+
originalFilename?: string;
|
|
5
7
|
/** Raw file content */
|
|
6
8
|
body: Buffer | Uint8Array | ArrayBuffer;
|
|
7
9
|
/** MIME type */
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.S3FileStorageAdapter = void 0;
|
|
4
4
|
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
5
|
+
const filename_1 = require("./filename");
|
|
5
6
|
// Encode per path segment, keep slashes
|
|
6
7
|
function encodeKeySegments(key) {
|
|
7
8
|
return key.split('/').map(encodeURIComponent).join('/');
|
|
@@ -25,7 +26,7 @@ class S3FileStorageAdapter {
|
|
|
25
26
|
async upload(input) {
|
|
26
27
|
if (!input.body)
|
|
27
28
|
throw new Error('S3 upload: "body" is required');
|
|
28
|
-
const key = input.key ?? this.randomKey();
|
|
29
|
+
const key = input.key ?? this.randomKey(input.originalFilename);
|
|
29
30
|
// Build params without ACL by default
|
|
30
31
|
const params = {
|
|
31
32
|
Bucket: this.bucket,
|
|
@@ -71,14 +72,19 @@ class S3FileStorageAdapter {
|
|
|
71
72
|
return `https://${this.bucket}.s3.${this.regionStr}.amazonaws.com/${encKey}`;
|
|
72
73
|
}
|
|
73
74
|
// ---- private helpers ----
|
|
74
|
-
randomKey() {
|
|
75
|
+
randomKey(originalFilename) {
|
|
75
76
|
const ts = new Date();
|
|
76
77
|
const y = ts.getUTCFullYear();
|
|
77
78
|
const m = String(ts.getUTCMonth() + 1).padStart(2, '0');
|
|
78
79
|
const d = String(ts.getUTCDate()).padStart(2, '0');
|
|
79
|
-
const
|
|
80
|
-
//
|
|
81
|
-
|
|
80
|
+
const randomId = Math.random().toString(36).slice(2, 10);
|
|
81
|
+
// Generate filename: slugified-name-randomid.ext or just randomid if no original name
|
|
82
|
+
const filename = originalFilename
|
|
83
|
+
? (0, filename_1.filenameWithRandomId)(originalFilename, randomId)
|
|
84
|
+
: randomId;
|
|
85
|
+
// Path: uploads/YYYY/MM/DD/filename
|
|
86
|
+
const fullPath = `uploads/${y}/${m}/${d}/${filename}`;
|
|
87
|
+
return fullPath;
|
|
82
88
|
}
|
|
83
89
|
}
|
|
84
90
|
exports.S3FileStorageAdapter = S3FileStorageAdapter;
|
package/dist/files/filename.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
export declare function sanitizeFilename(name: string): string;
|
|
2
|
+
/**
|
|
3
|
+
* Slugify a filename: lowercase, replace spaces/special chars with hyphens
|
|
4
|
+
* Example: "My Photo (2024).jpg" → "my-photo-2024.jpg"
|
|
5
|
+
*/
|
|
6
|
+
export declare function slugify(text: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Generate a filename from original name + random ID
|
|
9
|
+
* Example: "my-document.pdf" + "abc123" → "my-document-abc123.pdf"
|
|
10
|
+
*/
|
|
11
|
+
export declare function filenameWithRandomId(originalName: string, randomId: string): string;
|
|
2
12
|
export declare function withTimestampPrefix(name: string): string;
|
|
3
13
|
export declare function joinKey(...parts: string[]): string;
|
|
4
14
|
export declare function ensureExt(name: string, fallbackExt?: string): string;
|
package/dist/files/filename.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.sanitizeFilename = sanitizeFilename;
|
|
4
|
+
exports.slugify = slugify;
|
|
5
|
+
exports.filenameWithRandomId = filenameWithRandomId;
|
|
4
6
|
exports.withTimestampPrefix = withTimestampPrefix;
|
|
5
7
|
exports.joinKey = joinKey;
|
|
6
8
|
exports.ensureExt = ensureExt;
|
|
@@ -9,6 +11,39 @@ function sanitizeFilename(name) {
|
|
|
9
11
|
const base = name.replace(/[/\\?%*:|"<>]/g, '-').replace(/\s+/g, '-');
|
|
10
12
|
return base.replace(/-+/g, '-').toLowerCase();
|
|
11
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Slugify a filename: lowercase, replace spaces/special chars with hyphens
|
|
16
|
+
* Example: "My Photo (2024).jpg" → "my-photo-2024.jpg"
|
|
17
|
+
*/
|
|
18
|
+
function slugify(text) {
|
|
19
|
+
return text
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.trim()
|
|
22
|
+
// Replace spaces and underscores with hyphens
|
|
23
|
+
.replace(/[\s_]+/g, '-')
|
|
24
|
+
// Remove special characters except hyphens, dots, and alphanumeric
|
|
25
|
+
.replace(/[^\w\-\.]+/g, '')
|
|
26
|
+
// Replace multiple consecutive hyphens with single hyphen
|
|
27
|
+
.replace(/-+/g, '-')
|
|
28
|
+
// Remove leading/trailing hyphens
|
|
29
|
+
.replace(/^-+|-+$/g, '');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generate a filename from original name + random ID
|
|
33
|
+
* Example: "my-document.pdf" + "abc123" → "my-document-abc123.pdf"
|
|
34
|
+
*/
|
|
35
|
+
function filenameWithRandomId(originalName, randomId) {
|
|
36
|
+
// Extract extension
|
|
37
|
+
const lastDot = originalName.lastIndexOf('.');
|
|
38
|
+
const hasExt = lastDot > 0 && lastDot < originalName.length - 1;
|
|
39
|
+
const nameWithoutExt = hasExt ? originalName.slice(0, lastDot) : originalName;
|
|
40
|
+
const ext = hasExt ? originalName.slice(lastDot) : '';
|
|
41
|
+
// Slugify the name part
|
|
42
|
+
const slugifiedName = slugify(nameWithoutExt);
|
|
43
|
+
// Combine: slugified-name-randomid.ext
|
|
44
|
+
const result = `${slugifiedName}-${randomId}${ext}`;
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
12
47
|
function withTimestampPrefix(name) {
|
|
13
48
|
const ts = Date.now();
|
|
14
49
|
return `${ts}-${name}`;
|