@yimingliao/cms 0.0.18 → 0.0.19
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/server/index.d.ts +31 -14
- package/dist/server/index.js +127 -8
- package/package.json +13 -2
package/dist/server/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { cookies } from 'next/headers';
|
|
|
4
4
|
import { S3Client } from '@aws-sdk/client-s3';
|
|
5
5
|
import { Logger } from 'logry';
|
|
6
6
|
import { i as BlobFile, e as AdminRole, w as SingleItem, B as BaseTranslation, a as Admin, c as AdminFull, f as AdminSafe, D as DeviceInfo, d as AdminRefreshToken, l as File, n as FileFull, p as FileType, q as Folder, F as FolderFull, v as PostType, M as MultiItems, E as ExternalLink, k as Faq, T as TocItem, r as Post, t as PostListCard, u as PostTranslation, s as PostFull, S as SeoMetadata, g as AdminTranslation, o as FileTranslation, h as Alternate } from '../types-Bhnz5Z1F.js';
|
|
7
|
+
import { Pool } from 'generic-pool';
|
|
8
|
+
import SFTPClient from 'ssh2-sftp-client';
|
|
7
9
|
import Keyv from 'keyv';
|
|
8
10
|
|
|
9
11
|
interface CreateJwtServiceOptions {
|
|
@@ -72,24 +74,39 @@ declare function createCookieService(nextCookies: () => Promise<Awaited<ReturnTy
|
|
|
72
74
|
}) => Promise<void>;
|
|
73
75
|
};
|
|
74
76
|
|
|
75
|
-
interface
|
|
76
|
-
|
|
77
|
-
bucketName: string;
|
|
78
|
-
logger: Logger;
|
|
79
|
-
}
|
|
80
|
-
declare function createR2Service({ r2Client, bucketName, logger, }: CreateR2ServiceOptions): {
|
|
81
|
-
upload: ({ blobFile, folderKey, }: {
|
|
77
|
+
interface StorageService {
|
|
78
|
+
upload(options: {
|
|
82
79
|
blobFile: BlobFile;
|
|
83
80
|
folderKey?: string;
|
|
84
|
-
})
|
|
85
|
-
remove
|
|
81
|
+
}): Promise<string>;
|
|
82
|
+
remove(options: {
|
|
86
83
|
key: string;
|
|
87
|
-
})
|
|
88
|
-
move
|
|
84
|
+
}): Promise<void>;
|
|
85
|
+
move(options: {
|
|
89
86
|
fromKey: string;
|
|
90
87
|
toKey: string;
|
|
91
|
-
})
|
|
92
|
-
}
|
|
88
|
+
}): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface CreateR2ServiceOptions {
|
|
92
|
+
r2Client: S3Client;
|
|
93
|
+
bucketName: string;
|
|
94
|
+
logger: Logger;
|
|
95
|
+
}
|
|
96
|
+
declare function createR2Service({ r2Client, bucketName, logger, }: CreateR2ServiceOptions): StorageService;
|
|
97
|
+
|
|
98
|
+
interface CreateSftpServiceOptions {
|
|
99
|
+
sftpPool: Pool<SFTPClient>;
|
|
100
|
+
basePath: string;
|
|
101
|
+
logger: Logger;
|
|
102
|
+
}
|
|
103
|
+
declare function createSftpService({ sftpPool, basePath, logger, }: CreateSftpServiceOptions): StorageService;
|
|
104
|
+
|
|
105
|
+
interface CreateSftpPoolOptions {
|
|
106
|
+
sftpClientOptions: SFTPClient.ConnectOptions;
|
|
107
|
+
logger: Logger;
|
|
108
|
+
}
|
|
109
|
+
declare const createSftpPool: ({ sftpClientOptions, logger, }: CreateSftpPoolOptions) => Pool<SFTPClient>;
|
|
93
110
|
|
|
94
111
|
interface CreateServerCacheOptions {
|
|
95
112
|
redisUrl: string;
|
|
@@ -616,4 +633,4 @@ declare const POST_ORDER_BY: ({
|
|
|
616
633
|
index: "asc";
|
|
617
634
|
})[];
|
|
618
635
|
|
|
619
|
-
export { ADMIN_ORDER_BY, ORDER_BY, POST_ORDER_BY, type RawCacheKey, createAdminCommandRepository, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenQueryRepository, createArgon2Service, createCache, createCacheResult, createCookieService, createCryptoService, createFileCommandRepository, createFileQueryRepository, createFolderCommandRepository, createFolderQueryRepository, createIpRateLimiter, createJwtService, createPostCommandRepository, createPostQueryRepository, createR2Service, createSeoMetadataCommandRepository, normalizeCacheKey };
|
|
636
|
+
export { ADMIN_ORDER_BY, ORDER_BY, POST_ORDER_BY, type RawCacheKey, createAdminCommandRepository, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenQueryRepository, createArgon2Service, createCache, createCacheResult, createCookieService, createCryptoService, createFileCommandRepository, createFileQueryRepository, createFolderCommandRepository, createFolderQueryRepository, createIpRateLimiter, createJwtService, createPostCommandRepository, createPostQueryRepository, createR2Service, createSeoMetadataCommandRepository, createSftpPool, createSftpService, normalizeCacheKey };
|
package/dist/server/index.js
CHANGED
|
@@ -4,8 +4,10 @@ import argon2 from 'argon2';
|
|
|
4
4
|
import crypto, { timingSafeEqual } from 'crypto';
|
|
5
5
|
import { headers } from 'next/headers';
|
|
6
6
|
import { PutObjectCommand, DeleteObjectCommand, CopyObjectCommand } from '@aws-sdk/client-s3';
|
|
7
|
-
import
|
|
7
|
+
import path2 from 'path/posix';
|
|
8
8
|
import { ulid } from 'ulid';
|
|
9
|
+
import { createPool } from 'generic-pool';
|
|
10
|
+
import SFTPClient from 'ssh2-sftp-client';
|
|
9
11
|
import KeyvRedis from '@keyv/redis';
|
|
10
12
|
import Keyv from 'keyv';
|
|
11
13
|
|
|
@@ -205,17 +207,17 @@ function createObjectKey({
|
|
|
205
207
|
mimeType,
|
|
206
208
|
folderKey = ""
|
|
207
209
|
}) {
|
|
208
|
-
const extensionFromFilename =
|
|
210
|
+
const extensionFromFilename = path2.extname(filename ?? "").toLowerCase();
|
|
209
211
|
const extensionFromMime = !extensionFromFilename && mimeType ? `.${mimeToExtension(mimeType)}` : "";
|
|
210
212
|
const extension = extensionFromFilename || extensionFromMime;
|
|
211
213
|
const normalizedFolder = folderKey.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
212
214
|
const id = ulid();
|
|
213
215
|
const filenameWithId = id + extension;
|
|
214
216
|
if (!normalizedFolder) return filenameWithId;
|
|
215
|
-
return
|
|
217
|
+
return path2.join(normalizedFolder, filenameWithId);
|
|
216
218
|
}
|
|
217
219
|
|
|
218
|
-
// src/server/infrastructure/r2/r2.service.ts
|
|
220
|
+
// src/server/infrastructure/storage/r2/r2.service.ts
|
|
219
221
|
function createR2Service({
|
|
220
222
|
r2Client,
|
|
221
223
|
bucketName,
|
|
@@ -243,7 +245,7 @@ function createR2Service({
|
|
|
243
245
|
logger.debug("R2 upload success", { key });
|
|
244
246
|
return key;
|
|
245
247
|
} catch (error) {
|
|
246
|
-
logger.error("upload failed", { key, error });
|
|
248
|
+
logger.error("R2 upload failed", { key, error });
|
|
247
249
|
throw error;
|
|
248
250
|
}
|
|
249
251
|
}
|
|
@@ -257,7 +259,7 @@ function createR2Service({
|
|
|
257
259
|
);
|
|
258
260
|
logger.debug("R2 object removed", { key });
|
|
259
261
|
} catch (error) {
|
|
260
|
-
logger.error("
|
|
262
|
+
logger.error("R2 remove failed", { key, error });
|
|
261
263
|
throw error;
|
|
262
264
|
}
|
|
263
265
|
}
|
|
@@ -277,7 +279,7 @@ function createR2Service({
|
|
|
277
279
|
await remove({ key: fromKey });
|
|
278
280
|
logger.debug("R2 object moved", { fromKey, toKey });
|
|
279
281
|
} catch (error) {
|
|
280
|
-
logger.error("
|
|
282
|
+
logger.error("R2 move failed", { fromKey, toKey, error });
|
|
281
283
|
throw error;
|
|
282
284
|
}
|
|
283
285
|
}
|
|
@@ -287,6 +289,123 @@ function createR2Service({
|
|
|
287
289
|
move
|
|
288
290
|
};
|
|
289
291
|
}
|
|
292
|
+
function createSftpService({
|
|
293
|
+
sftpPool,
|
|
294
|
+
basePath,
|
|
295
|
+
logger
|
|
296
|
+
}) {
|
|
297
|
+
async function upload({
|
|
298
|
+
blobFile,
|
|
299
|
+
folderKey = ""
|
|
300
|
+
}) {
|
|
301
|
+
const key = createObjectKey({
|
|
302
|
+
filename: blobFile.name,
|
|
303
|
+
mimeType: blobFile.type,
|
|
304
|
+
folderKey
|
|
305
|
+
});
|
|
306
|
+
const fullPath = path2.join(basePath, key);
|
|
307
|
+
const client = await sftpPool.acquire();
|
|
308
|
+
try {
|
|
309
|
+
const buffer = Buffer.from(await blobFile.arrayBuffer());
|
|
310
|
+
await client.put(buffer, fullPath);
|
|
311
|
+
logger.debug("SFTP upload success", { key });
|
|
312
|
+
return key;
|
|
313
|
+
} catch (error) {
|
|
314
|
+
logger.error("SFTP upload failed", { key, error });
|
|
315
|
+
throw error;
|
|
316
|
+
} finally {
|
|
317
|
+
await sftpPool.release(client);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function remove({ key }) {
|
|
321
|
+
const fullPath = path2.join(basePath, key);
|
|
322
|
+
const client = await sftpPool.acquire();
|
|
323
|
+
try {
|
|
324
|
+
await client.delete(fullPath);
|
|
325
|
+
logger.debug("SFTP object removed", { key });
|
|
326
|
+
} catch (error) {
|
|
327
|
+
if (error instanceof Error && /no such file/i.test(error.message)) {
|
|
328
|
+
logger.debug("SFTP remove skipped (not found)", { key });
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
logger.error("SFTP remove failed", { key, error });
|
|
332
|
+
throw error;
|
|
333
|
+
} finally {
|
|
334
|
+
await sftpPool.release(client);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
async function move({ fromKey, toKey }) {
|
|
338
|
+
if (fromKey === toKey) {
|
|
339
|
+
logger.debug("SFTP move skipped (same key)", { key: fromKey });
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const fromPath = path2.join(basePath, fromKey);
|
|
343
|
+
const toPath = path2.join(basePath, toKey);
|
|
344
|
+
const client = await sftpPool.acquire();
|
|
345
|
+
try {
|
|
346
|
+
await client.rename(fromPath, toPath);
|
|
347
|
+
logger.debug("SFTP object moved", { fromKey, toKey });
|
|
348
|
+
} catch (error) {
|
|
349
|
+
logger.error("SFTP move failed", { fromKey, toKey, error });
|
|
350
|
+
throw error;
|
|
351
|
+
} finally {
|
|
352
|
+
await sftpPool.release(client);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
upload,
|
|
357
|
+
remove,
|
|
358
|
+
move
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
var createSftpPool = ({
|
|
362
|
+
sftpClientOptions,
|
|
363
|
+
logger
|
|
364
|
+
}) => {
|
|
365
|
+
return createPool(
|
|
366
|
+
{
|
|
367
|
+
create: async () => {
|
|
368
|
+
const client = new SFTPClient();
|
|
369
|
+
try {
|
|
370
|
+
await client.connect({
|
|
371
|
+
readyTimeout: 2e4,
|
|
372
|
+
keepaliveInterval: 1e4,
|
|
373
|
+
keepaliveCountMax: 3,
|
|
374
|
+
...sftpClientOptions
|
|
375
|
+
});
|
|
376
|
+
return client;
|
|
377
|
+
} catch (error) {
|
|
378
|
+
logger.error("SFTP connect failed", { error });
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
validate: async (client) => {
|
|
383
|
+
try {
|
|
384
|
+
await client.realPath("/");
|
|
385
|
+
return true;
|
|
386
|
+
} catch {
|
|
387
|
+
logger.debug("SFTP session invalid, removing from pool");
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
destroy: async (client) => {
|
|
392
|
+
try {
|
|
393
|
+
await client.end();
|
|
394
|
+
} catch (error) {
|
|
395
|
+
logger.error("SFTP destroy failed", { error });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
max: 3,
|
|
401
|
+
min: 0,
|
|
402
|
+
idleTimeoutMillis: 6e4,
|
|
403
|
+
acquireTimeoutMillis: 1e4,
|
|
404
|
+
evictionRunIntervalMillis: 3e4,
|
|
405
|
+
testOnBorrow: true
|
|
406
|
+
}
|
|
407
|
+
);
|
|
408
|
+
};
|
|
290
409
|
|
|
291
410
|
// src/server/infrastructure/cache/cache-key-delimiter.ts
|
|
292
411
|
var CACHE_KEY_DELIMITER = "|";
|
|
@@ -1509,4 +1628,4 @@ function createSeoMetadataCommandRepository(prisma) {
|
|
|
1509
1628
|
};
|
|
1510
1629
|
}
|
|
1511
1630
|
|
|
1512
|
-
export { ADMIN_ORDER_BY, ORDER_BY, POST_ORDER_BY, createAdminCommandRepository, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenQueryRepository, createArgon2Service, createCache, createCacheResult, createCookieService, createCryptoService, createFileCommandRepository, createFileQueryRepository, createFolderCommandRepository, createFolderQueryRepository, createIpRateLimiter, createJwtService, createPostCommandRepository, createPostQueryRepository, createR2Service, createSeoMetadataCommandRepository, normalizeCacheKey };
|
|
1631
|
+
export { ADMIN_ORDER_BY, ORDER_BY, POST_ORDER_BY, createAdminCommandRepository, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenQueryRepository, createArgon2Service, createCache, createCacheResult, createCookieService, createCryptoService, createFileCommandRepository, createFileQueryRepository, createFolderCommandRepository, createFolderQueryRepository, createIpRateLimiter, createJwtService, createPostCommandRepository, createPostQueryRepository, createR2Service, createSeoMetadataCommandRepository, createSftpPool, createSftpService, normalizeCacheKey };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yimingliao/cms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"author": "Yiming Liao",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -35,20 +35,31 @@
|
|
|
35
35
|
"ulid": "^3.0.2"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
+
"@aws-sdk/client-s3": "^3.1004.0",
|
|
38
39
|
"@prisma/client": "6.5.0",
|
|
39
40
|
"@types/jsonwebtoken": "^9.0.10",
|
|
40
41
|
"@types/mime-types": "^3.0.1",
|
|
42
|
+
"@types/ssh2-sftp-client": "^9.0.6",
|
|
43
|
+
"generic-pool": "^3.9.0",
|
|
41
44
|
"next": "^16.1.6",
|
|
42
45
|
"prisma": "6.5.0",
|
|
43
46
|
"tsup": "^8.5.1",
|
|
44
47
|
"typescript": "^5.9.3"
|
|
45
48
|
},
|
|
46
49
|
"peerDependencies": {
|
|
47
|
-
"@aws-sdk/client-s3": "^3.0.0"
|
|
50
|
+
"@aws-sdk/client-s3": "^3.0.0",
|
|
51
|
+
"generic-pool": "^3.9.0",
|
|
52
|
+
"ssh2-sftp-client": "^12.1.0"
|
|
48
53
|
},
|
|
49
54
|
"peerDependenciesMeta": {
|
|
50
55
|
"@aws-sdk/client-s3": {
|
|
51
56
|
"optional": true
|
|
57
|
+
},
|
|
58
|
+
"generic-pool": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"ssh2-sftp-client": {
|
|
62
|
+
"optional": true
|
|
52
63
|
}
|
|
53
64
|
}
|
|
54
65
|
}
|