@yimingliao/cms 0.0.19 → 0.0.20

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.
@@ -85,7 +85,7 @@ declare const FILE_TYPES: {
85
85
  };
86
86
  type FileType = (typeof FILE_TYPES)[keyof typeof FILE_TYPES];
87
87
 
88
- interface File$1 {
88
+ interface File {
89
89
  id: string;
90
90
  key: string;
91
91
  checksum: string;
@@ -127,10 +127,10 @@ interface Folder {
127
127
  type FolderFull = Folder & {
128
128
  parentFolder: (Folder & {
129
129
  subFolders: Folder[];
130
- files: File$1[];
130
+ files: File[];
131
131
  }) | null;
132
132
  subFolders: Folder[];
133
- files: File$1[];
133
+ files: File[];
134
134
  };
135
135
 
136
136
  declare const POST_TYPES: {
@@ -242,7 +242,7 @@ type PostListCard = Post & {
242
242
  postsInTopic: Post[];
243
243
  children: Post[];
244
244
  taggedPosts: Post[];
245
- coverImage: File$1 | null;
245
+ coverImage: File | null;
246
246
  translations: PostTranslation[];
247
247
  };
248
248
 
@@ -266,7 +266,7 @@ type PostFull = Post & {
266
266
  translations: PostTranslation[];
267
267
  };
268
268
 
269
- type FileFull = File$1 & {
269
+ type FileFull = File & {
270
270
  folder: Folder | null;
271
271
  adminAsAvatarImage: Admin[];
272
272
  postsAsCoverImage: Post[];
@@ -280,13 +280,13 @@ type FileFull = File$1 & {
280
280
  translations: FileTranslation[];
281
281
  };
282
282
 
283
- type FileCard = File$1 & {
283
+ type FileCard = File & {
284
284
  translations: FileTranslation[];
285
285
  };
286
286
 
287
287
  type AdminFull = AdminSafe & {
288
288
  adminRefreshTokens: AdminRefreshToken[];
289
- avatarImage: (File$1 & {
289
+ avatarImage: (File & {
290
290
  translations: FileTranslation[];
291
291
  }) | null;
292
292
  posts: Post[];
@@ -337,11 +337,4 @@ interface SeoMetadata {
337
337
  updatedAt: Date;
338
338
  }
339
339
 
340
- interface BlobFile extends File {
341
- id: string;
342
- width: number | null;
343
- height: number | null;
344
- duration: number | null;
345
- }
346
-
347
- export { ADMIN_ROLES as A, type BaseTranslation as B, type DeviceInfo as D, type ExternalLink as E, type FolderFull as F, type MultiItems as M, POST_TYPES as P, type SeoMetadata as S, type TocItem as T, type Admin as a, type AdminCard as b, type AdminFull as c, type AdminRefreshToken as d, type AdminRole as e, type AdminSafe as f, type AdminTranslation as g, type Alternate as h, type BlobFile as i, FILE_TYPES as j, type Faq as k, type File$1 as l, type FileCard as m, type FileFull as n, type FileTranslation as o, type FileType as p, type Folder as q, type Post as r, type PostFull as s, type PostListCard as t, type PostTranslation as u, type PostType as v, type SingleItem as w, type Translation as x };
340
+ export { ADMIN_ROLES as A, type BaseTranslation as B, type DeviceInfo as D, type ExternalLink as E, type FolderFull as F, type MultiItems as M, POST_TYPES as P, type SeoMetadata as S, type TocItem as T, type Admin as a, type AdminCard as b, type AdminFull as c, type AdminRefreshToken as d, type AdminRole as e, type AdminSafe as f, type AdminTranslation as g, type Alternate as h, FILE_TYPES as i, type Faq as j, type File as k, type FileCard as l, type FileFull as m, type FileTranslation as n, type FileType as o, type Folder as p, type Post as q, type PostFull as r, type PostListCard as s, type PostTranslation as t, type PostType as u, type SingleItem as v, type Translation as w };
@@ -0,0 +1,20 @@
1
+ import { mimeToExtension } from './chunk-YX7IPIGU.js';
2
+ import path from 'path/posix';
3
+ import { ulid } from 'ulid';
4
+
5
+ function createObjectKey({
6
+ filename,
7
+ mimeType,
8
+ folderKey = ""
9
+ }) {
10
+ const extensionFromFilename = path.extname(filename ?? "").toLowerCase();
11
+ const extensionFromMime = !extensionFromFilename && mimeType ? `.${mimeToExtension(mimeType)}` : "";
12
+ const extension = extensionFromFilename || extensionFromMime;
13
+ const normalizedFolder = folderKey.replace(/^\/+/, "").replace(/\/+$/, "");
14
+ const id = ulid();
15
+ const filenameWithId = id + extension;
16
+ if (!normalizedFolder) return filenameWithId;
17
+ return path.join(normalizedFolder, filenameWithId);
18
+ }
19
+
20
+ export { createObjectKey };
@@ -1,12 +1,5 @@
1
1
  import { extension, lookup } from 'mime-types';
2
2
 
3
- // src/domain/resources/admin/props.ts
4
- var ADMIN_ROLES = {
5
- SUPER_ADMIN: "SUPER_ADMIN",
6
- ADMIN: "ADMIN",
7
- EDITOR: "EDITOR"
8
- };
9
-
10
3
  // src/domain/resources/file/props.ts
11
4
  var FILE_TYPES = {
12
5
  IMAGE: "IMAGE",
@@ -16,44 +9,6 @@ var FILE_TYPES = {
16
9
  ARCHIVE: "ARCHIVE",
17
10
  OTHER: "OTHER"
18
11
  };
19
-
20
- // src/domain/resources/post/props.ts
21
- var POST_TYPES = {
22
- TOPIC: "TOPIC",
23
- CATEGORY: "CATEGORY",
24
- POST: "POST",
25
- TAG: "TAG",
26
- PAGE: "PAGE"
27
- };
28
-
29
- // src/domain/resources/constants.ts
30
- var ROOT_FOLDER_ID = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
31
- var ROOT_FOLDER_NAME = "ROOT";
32
- var ROOT_FOLDER = {
33
- id: ROOT_FOLDER_ID,
34
- // core
35
- name: ROOT_FOLDER_NAME,
36
- key: "",
37
- // states
38
- isLocked: true,
39
- // ---------------------------
40
- // relations: Folder
41
- // ---------------------------
42
- parentFolder: null,
43
- parentFolderId: null,
44
- subFolders: [],
45
- // ---------------------------
46
- // relations: Folder
47
- // ---------------------------
48
- files: [],
49
- // ---------------------------
50
- // timestamps
51
- // ---------------------------
52
- createdAt: "",
53
- updatedAt: ""
54
- };
55
- var SIMPLE_UPLOAD_FOLDER_NAME = "simple-upload";
56
- var SIMPLE_UPLOAD_FOLDER_KEY = `${SIMPLE_UPLOAD_FOLDER_NAME}`;
57
12
  var mimeToExtension = (mimeType) => {
58
13
  if (!mimeType) return "unknown";
59
14
  return (extension(mimeType) || "unknown").toLowerCase();
@@ -230,4 +185,4 @@ var result = {
230
185
  error
231
186
  };
232
187
 
233
- export { ADMIN_ROLES, FILE_TYPES, POST_TYPES, ROOT_FOLDER, ROOT_FOLDER_ID, ROOT_FOLDER_NAME, SIMPLE_UPLOAD_FOLDER_KEY, SIMPLE_UPLOAD_FOLDER_NAME, classifyFileType, formatFileSize, getMediaInfo, mimeToExtension, result };
188
+ export { FILE_TYPES, classifyFileType, formatFileSize, getMediaInfo, mimeToExtension, result };
@@ -0,0 +1,46 @@
1
+ // src/domain/resources/admin/props.ts
2
+ var ADMIN_ROLES = {
3
+ SUPER_ADMIN: "SUPER_ADMIN",
4
+ ADMIN: "ADMIN",
5
+ EDITOR: "EDITOR"
6
+ };
7
+
8
+ // src/domain/resources/post/props.ts
9
+ var POST_TYPES = {
10
+ TOPIC: "TOPIC",
11
+ CATEGORY: "CATEGORY",
12
+ POST: "POST",
13
+ TAG: "TAG",
14
+ PAGE: "PAGE"
15
+ };
16
+
17
+ // src/domain/resources/constants.ts
18
+ var ROOT_FOLDER_ID = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
19
+ var ROOT_FOLDER_NAME = "ROOT";
20
+ var ROOT_FOLDER = {
21
+ id: ROOT_FOLDER_ID,
22
+ // core
23
+ name: ROOT_FOLDER_NAME,
24
+ key: "",
25
+ // states
26
+ isLocked: true,
27
+ // ---------------------------
28
+ // relations: Folder
29
+ // ---------------------------
30
+ parentFolder: null,
31
+ parentFolderId: null,
32
+ subFolders: [],
33
+ // ---------------------------
34
+ // relations: Folder
35
+ // ---------------------------
36
+ files: [],
37
+ // ---------------------------
38
+ // timestamps
39
+ // ---------------------------
40
+ createdAt: "",
41
+ updatedAt: ""
42
+ };
43
+ var SIMPLE_UPLOAD_FOLDER_NAME = "simple-upload";
44
+ var SIMPLE_UPLOAD_FOLDER_KEY = `${SIMPLE_UPLOAD_FOLDER_NAME}`;
45
+
46
+ export { ADMIN_ROLES, POST_TYPES, ROOT_FOLDER, ROOT_FOLDER_ID, ROOT_FOLDER_NAME, SIMPLE_UPLOAD_FOLDER_KEY, SIMPLE_UPLOAD_FOLDER_NAME };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { F as FolderFull } from './types-Bhnz5Z1F.js';
2
- export { A as ADMIN_ROLES, a as Admin, b as AdminCard, c as AdminFull, d as AdminRefreshToken, e as AdminRole, f as AdminSafe, g as AdminTranslation, h as Alternate, B as BaseTranslation, i as BlobFile, D as DeviceInfo, E as ExternalLink, j as FILE_TYPES, k as Faq, l as File, m as FileCard, n as FileFull, o as FileTranslation, p as FileType, q as Folder, M as MultiItems, P as POST_TYPES, r as Post, s as PostFull, t as PostListCard, u as PostTranslation, v as PostType, S as SeoMetadata, w as SingleItem, T as TocItem, x as Translation } from './types-Bhnz5Z1F.js';
1
+ import { F as FolderFull } from './base-DbGnfZr6.js';
2
+ export { A as ADMIN_ROLES, a as Admin, b as AdminCard, c as AdminFull, d as AdminRefreshToken, e as AdminRole, f as AdminSafe, g as AdminTranslation, h as Alternate, B as BaseTranslation, D as DeviceInfo, E as ExternalLink, i as FILE_TYPES, j as Faq, k as File, l as FileCard, m as FileFull, n as FileTranslation, o as FileType, p as Folder, M as MultiItems, P as POST_TYPES, q as Post, r as PostFull, s as PostListCard, t as PostTranslation, u as PostType, S as SeoMetadata, v as SingleItem, T as TocItem, w as Translation } from './base-DbGnfZr6.js';
3
+ export { B as BlobFile } from './types-0oS1A2K5.js';
3
4
 
4
5
  declare const ROOT_FOLDER_ID = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
5
6
  declare const ROOT_FOLDER_NAME = "ROOT";
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
- export { ADMIN_ROLES, FILE_TYPES, POST_TYPES, ROOT_FOLDER, ROOT_FOLDER_ID, ROOT_FOLDER_NAME, SIMPLE_UPLOAD_FOLDER_KEY, SIMPLE_UPLOAD_FOLDER_NAME, classifyFileType, formatFileSize, getMediaInfo, mimeToExtension, result } from './chunk-SEX4DOKX.js';
1
+ export { ADMIN_ROLES, POST_TYPES, ROOT_FOLDER, ROOT_FOLDER_ID, ROOT_FOLDER_NAME, SIMPLE_UPLOAD_FOLDER_KEY, SIMPLE_UPLOAD_FOLDER_NAME } from './chunk-ZCOYQ5BG.js';
2
+ export { FILE_TYPES, classifyFileType, formatFileSize, getMediaInfo, mimeToExtension, result } from './chunk-YX7IPIGU.js';
@@ -1,12 +1,9 @@
1
1
  import jwt from 'jsonwebtoken';
2
2
  import { BinaryLike } from 'node:crypto';
3
3
  import { cookies } from 'next/headers';
4
- import { S3Client } from '@aws-sdk/client-s3';
5
- import { Logger } from 'logry';
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';
9
4
  import Keyv from 'keyv';
5
+ import { Logger } from 'logry';
6
+ import { e as AdminRole, v as SingleItem, B as BaseTranslation, a as Admin, c as AdminFull, f as AdminSafe, D as DeviceInfo, d as AdminRefreshToken, k as File, m as FileFull, o as FileType, p as Folder, F as FolderFull, u as PostType, M as MultiItems, E as ExternalLink, j as Faq, T as TocItem, q as Post, s as PostListCard, t as PostTranslation, r as PostFull, S as SeoMetadata, g as AdminTranslation, n as FileTranslation, h as Alternate } from '../base-DbGnfZr6.js';
10
7
 
11
8
  interface CreateJwtServiceOptions {
12
9
  defaultSecret: string;
@@ -74,40 +71,6 @@ declare function createCookieService(nextCookies: () => Promise<Awaited<ReturnTy
74
71
  }) => Promise<void>;
75
72
  };
76
73
 
77
- interface StorageService {
78
- upload(options: {
79
- blobFile: BlobFile;
80
- folderKey?: string;
81
- }): Promise<string>;
82
- remove(options: {
83
- key: string;
84
- }): Promise<void>;
85
- move(options: {
86
- fromKey: string;
87
- toKey: string;
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>;
110
-
111
74
  interface CreateServerCacheOptions {
112
75
  redisUrl: string;
113
76
  namespace: string;
@@ -633,4 +596,4 @@ declare const POST_ORDER_BY: ({
633
596
  index: "asc";
634
597
  })[];
635
598
 
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 };
599
+ 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, createSeoMetadataCommandRepository, normalizeCacheKey };
@@ -1,15 +1,12 @@
1
- import { ADMIN_ROLES, mimeToExtension, classifyFileType, ROOT_FOLDER_ID } from '../chunk-SEX4DOKX.js';
1
+ import { ADMIN_ROLES, ROOT_FOLDER_ID } from '../chunk-ZCOYQ5BG.js';
2
+ import { mimeToExtension, classifyFileType } from '../chunk-YX7IPIGU.js';
2
3
  import jwt from 'jsonwebtoken';
3
4
  import argon2 from 'argon2';
4
5
  import crypto, { timingSafeEqual } from 'crypto';
5
6
  import { headers } from 'next/headers';
6
- import { PutObjectCommand, DeleteObjectCommand, CopyObjectCommand } from '@aws-sdk/client-s3';
7
- import path2 from 'path/posix';
8
- import { ulid } from 'ulid';
9
- import { createPool } from 'generic-pool';
10
- import SFTPClient from 'ssh2-sftp-client';
11
7
  import KeyvRedis from '@keyv/redis';
12
8
  import Keyv from 'keyv';
9
+ import { ulid } from 'ulid';
13
10
 
14
11
  function createJwtService({
15
12
  defaultSecret,
@@ -202,210 +199,6 @@ function createCookieService(nextCookies, cryptoService) {
202
199
  delete: deleteCokkie
203
200
  };
204
201
  }
205
- function createObjectKey({
206
- filename,
207
- mimeType,
208
- folderKey = ""
209
- }) {
210
- const extensionFromFilename = path2.extname(filename ?? "").toLowerCase();
211
- const extensionFromMime = !extensionFromFilename && mimeType ? `.${mimeToExtension(mimeType)}` : "";
212
- const extension = extensionFromFilename || extensionFromMime;
213
- const normalizedFolder = folderKey.replace(/^\/+/, "").replace(/\/+$/, "");
214
- const id = ulid();
215
- const filenameWithId = id + extension;
216
- if (!normalizedFolder) return filenameWithId;
217
- return path2.join(normalizedFolder, filenameWithId);
218
- }
219
-
220
- // src/server/infrastructure/storage/r2/r2.service.ts
221
- function createR2Service({
222
- r2Client,
223
- bucketName,
224
- logger
225
- }) {
226
- async function upload({
227
- blobFile,
228
- folderKey = ""
229
- }) {
230
- const key = createObjectKey({
231
- filename: blobFile.name,
232
- mimeType: blobFile.type,
233
- folderKey
234
- });
235
- try {
236
- const buffer = Buffer.from(await blobFile.arrayBuffer());
237
- await r2Client.send(
238
- new PutObjectCommand({
239
- Bucket: bucketName,
240
- Key: key,
241
- Body: buffer,
242
- ContentType: blobFile.type || void 0
243
- })
244
- );
245
- logger.debug("R2 upload success", { key });
246
- return key;
247
- } catch (error) {
248
- logger.error("R2 upload failed", { key, error });
249
- throw error;
250
- }
251
- }
252
- async function remove({ key }) {
253
- try {
254
- await r2Client.send(
255
- new DeleteObjectCommand({
256
- Bucket: bucketName,
257
- Key: key
258
- })
259
- );
260
- logger.debug("R2 object removed", { key });
261
- } catch (error) {
262
- logger.error("R2 remove failed", { key, error });
263
- throw error;
264
- }
265
- }
266
- async function move({ fromKey, toKey }) {
267
- if (fromKey === toKey) {
268
- logger.debug("R2 move skipped (same key)", { key: fromKey });
269
- return;
270
- }
271
- try {
272
- await r2Client.send(
273
- new CopyObjectCommand({
274
- Bucket: bucketName,
275
- CopySource: encodeURI(`${bucketName}/${fromKey}`),
276
- Key: toKey
277
- })
278
- );
279
- await remove({ key: fromKey });
280
- logger.debug("R2 object moved", { fromKey, toKey });
281
- } catch (error) {
282
- logger.error("R2 move failed", { fromKey, toKey, error });
283
- throw error;
284
- }
285
- }
286
- return {
287
- upload,
288
- remove,
289
- move
290
- };
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
- };
409
202
 
410
203
  // src/server/infrastructure/cache/cache-key-delimiter.ts
411
204
  var CACHE_KEY_DELIMITER = "|";
@@ -1628,4 +1421,4 @@ function createSeoMetadataCommandRepository(prisma) {
1628
1421
  };
1629
1422
  }
1630
1423
 
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 };
1424
+ 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, createSeoMetadataCommandRepository, normalizeCacheKey };
@@ -0,0 +1,13 @@
1
+ import { S3Client } from '@aws-sdk/client-s3';
2
+ import { Logger } from 'logry';
3
+ import { S as StorageService } from '../../types-J25u1G6t.js';
4
+ import '../../types-0oS1A2K5.js';
5
+
6
+ interface CreateR2ServiceOptions {
7
+ r2Client: S3Client;
8
+ bucketName: string;
9
+ logger: Logger;
10
+ }
11
+ declare function createR2Service({ r2Client, bucketName, logger, }: CreateR2ServiceOptions): StorageService;
12
+
13
+ export { createR2Service };
@@ -0,0 +1,77 @@
1
+ import { createObjectKey } from '../../chunk-BTRPDDZJ.js';
2
+ import '../../chunk-YX7IPIGU.js';
3
+ import { PutObjectCommand, DeleteObjectCommand, CopyObjectCommand } from '@aws-sdk/client-s3';
4
+
5
+ function createR2Service({
6
+ r2Client,
7
+ bucketName,
8
+ logger
9
+ }) {
10
+ async function upload({
11
+ blobFile,
12
+ folderKey = ""
13
+ }) {
14
+ const key = createObjectKey({
15
+ filename: blobFile.name,
16
+ mimeType: blobFile.type,
17
+ folderKey
18
+ });
19
+ try {
20
+ const buffer = Buffer.from(await blobFile.arrayBuffer());
21
+ await r2Client.send(
22
+ new PutObjectCommand({
23
+ Bucket: bucketName,
24
+ Key: key,
25
+ Body: buffer,
26
+ ContentType: blobFile.type || void 0
27
+ })
28
+ );
29
+ logger.debug("R2 upload success", { key });
30
+ return key;
31
+ } catch (error) {
32
+ logger.error("R2 upload failed", { key, error });
33
+ throw error;
34
+ }
35
+ }
36
+ async function remove({ key }) {
37
+ try {
38
+ await r2Client.send(
39
+ new DeleteObjectCommand({
40
+ Bucket: bucketName,
41
+ Key: key
42
+ })
43
+ );
44
+ logger.debug("R2 object removed", { key });
45
+ } catch (error) {
46
+ logger.error("R2 remove failed", { key, error });
47
+ throw error;
48
+ }
49
+ }
50
+ async function move({ fromKey, toKey }) {
51
+ if (fromKey === toKey) {
52
+ logger.debug("R2 move skipped (same key)", { key: fromKey });
53
+ return;
54
+ }
55
+ try {
56
+ await r2Client.send(
57
+ new CopyObjectCommand({
58
+ Bucket: bucketName,
59
+ CopySource: encodeURI(`${bucketName}/${fromKey}`),
60
+ Key: toKey
61
+ })
62
+ );
63
+ await remove({ key: fromKey });
64
+ logger.debug("R2 object moved", { fromKey, toKey });
65
+ } catch (error) {
66
+ logger.error("R2 move failed", { fromKey, toKey, error });
67
+ throw error;
68
+ }
69
+ }
70
+ return {
71
+ upload,
72
+ remove,
73
+ move
74
+ };
75
+ }
76
+
77
+ export { createR2Service };
@@ -0,0 +1,20 @@
1
+ import { Logger } from 'logry';
2
+ import { Pool } from 'generic-pool';
3
+ import SFTPClient from 'ssh2-sftp-client';
4
+ import { S as StorageService } from '../../types-J25u1G6t.js';
5
+ import '../../types-0oS1A2K5.js';
6
+
7
+ interface CreateSftpServiceOptions {
8
+ sftpPool: Pool<SFTPClient>;
9
+ basePath: string;
10
+ logger: Logger;
11
+ }
12
+ declare function createSftpService({ sftpPool, basePath, logger, }: CreateSftpServiceOptions): StorageService;
13
+
14
+ interface CreateSftpPoolOptions {
15
+ sftpClientOptions: SFTPClient.ConnectOptions;
16
+ logger: Logger;
17
+ }
18
+ declare const createSftpPool: ({ sftpClientOptions, logger, }: CreateSftpPoolOptions) => Pool<SFTPClient>;
19
+
20
+ export { createSftpPool, createSftpService };
@@ -0,0 +1,125 @@
1
+ import { createObjectKey } from '../../chunk-BTRPDDZJ.js';
2
+ import '../../chunk-YX7IPIGU.js';
3
+ import path from 'path/posix';
4
+ import { createPool } from 'generic-pool';
5
+ import SFTPClient from 'ssh2-sftp-client';
6
+
7
+ function createSftpService({
8
+ sftpPool,
9
+ basePath,
10
+ logger
11
+ }) {
12
+ async function upload({
13
+ blobFile,
14
+ folderKey = ""
15
+ }) {
16
+ const key = createObjectKey({
17
+ filename: blobFile.name,
18
+ mimeType: blobFile.type,
19
+ folderKey
20
+ });
21
+ const fullPath = path.join(basePath, key);
22
+ const client = await sftpPool.acquire();
23
+ try {
24
+ const buffer = Buffer.from(await blobFile.arrayBuffer());
25
+ await client.put(buffer, fullPath);
26
+ logger.debug("SFTP upload success", { key });
27
+ return key;
28
+ } catch (error) {
29
+ logger.error("SFTP upload failed", { key, error });
30
+ throw error;
31
+ } finally {
32
+ await sftpPool.release(client);
33
+ }
34
+ }
35
+ async function remove({ key }) {
36
+ const fullPath = path.join(basePath, key);
37
+ const client = await sftpPool.acquire();
38
+ try {
39
+ await client.delete(fullPath);
40
+ logger.debug("SFTP object removed", { key });
41
+ } catch (error) {
42
+ if (error instanceof Error && /no such file/i.test(error.message)) {
43
+ logger.debug("SFTP remove skipped (not found)", { key });
44
+ return;
45
+ }
46
+ logger.error("SFTP remove failed", { key, error });
47
+ throw error;
48
+ } finally {
49
+ await sftpPool.release(client);
50
+ }
51
+ }
52
+ async function move({ fromKey, toKey }) {
53
+ if (fromKey === toKey) {
54
+ logger.debug("SFTP move skipped (same key)", { key: fromKey });
55
+ return;
56
+ }
57
+ const fromPath = path.join(basePath, fromKey);
58
+ const toPath = path.join(basePath, toKey);
59
+ const client = await sftpPool.acquire();
60
+ try {
61
+ await client.rename(fromPath, toPath);
62
+ logger.debug("SFTP object moved", { fromKey, toKey });
63
+ } catch (error) {
64
+ logger.error("SFTP move failed", { fromKey, toKey, error });
65
+ throw error;
66
+ } finally {
67
+ await sftpPool.release(client);
68
+ }
69
+ }
70
+ return {
71
+ upload,
72
+ remove,
73
+ move
74
+ };
75
+ }
76
+ var createSftpPool = ({
77
+ sftpClientOptions,
78
+ logger
79
+ }) => {
80
+ return createPool(
81
+ {
82
+ create: async () => {
83
+ const client = new SFTPClient();
84
+ try {
85
+ await client.connect({
86
+ readyTimeout: 2e4,
87
+ keepaliveInterval: 1e4,
88
+ keepaliveCountMax: 3,
89
+ ...sftpClientOptions
90
+ });
91
+ return client;
92
+ } catch (error) {
93
+ logger.error("SFTP connect failed", { error });
94
+ throw error;
95
+ }
96
+ },
97
+ validate: async (client) => {
98
+ try {
99
+ await client.realPath("/");
100
+ return true;
101
+ } catch {
102
+ logger.debug("SFTP session invalid, removing from pool");
103
+ return false;
104
+ }
105
+ },
106
+ destroy: async (client) => {
107
+ try {
108
+ await client.end();
109
+ } catch (error) {
110
+ logger.error("SFTP destroy failed", { error });
111
+ }
112
+ }
113
+ },
114
+ {
115
+ max: 3,
116
+ min: 0,
117
+ idleTimeoutMillis: 6e4,
118
+ acquireTimeoutMillis: 1e4,
119
+ evictionRunIntervalMillis: 3e4,
120
+ testOnBorrow: true
121
+ }
122
+ );
123
+ };
124
+
125
+ export { createSftpPool, createSftpService };
@@ -0,0 +1,8 @@
1
+ interface BlobFile extends File {
2
+ id: string;
3
+ width: number | null;
4
+ height: number | null;
5
+ duration: number | null;
6
+ }
7
+
8
+ export type { BlobFile as B };
@@ -0,0 +1,17 @@
1
+ import { B as BlobFile } from './types-0oS1A2K5.js';
2
+
3
+ interface StorageService {
4
+ upload(options: {
5
+ blobFile: BlobFile;
6
+ folderKey?: string;
7
+ }): Promise<string>;
8
+ remove(options: {
9
+ key: string;
10
+ }): Promise<void>;
11
+ move(options: {
12
+ fromKey: string;
13
+ toKey: string;
14
+ }): Promise<void>;
15
+ }
16
+
17
+ export type { StorageService as S };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yimingliao/cms",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "author": "Yiming Liao",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,6 +14,16 @@
14
14
  "types": "./dist/server/index.d.ts",
15
15
  "import": "./dist/server/index.js",
16
16
  "require": "./dist/server/index.js"
17
+ },
18
+ "./storage/r2": {
19
+ "types": "./dist/storage/r2/index.d.ts",
20
+ "import": "./dist/storage/r2/index.js",
21
+ "require": "./dist/storage/r2/index.js"
22
+ },
23
+ "./storage/sftp": {
24
+ "types": "./dist/storage/sftp/index.d.ts",
25
+ "import": "./dist/storage/sftp/index.js",
26
+ "require": "./dist/storage/sftp/index.js"
17
27
  }
18
28
  },
19
29
  "files": [