@yimingliao/cms 0.0.17 → 0.0.18

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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
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';
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';
3
3
 
4
4
  declare const ROOT_FOLDER_ID = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
5
5
  declare const ROOT_FOLDER_NAME = "ROOT";
@@ -20,13 +20,6 @@ declare const getMediaInfo: (file: Blob) => Promise<MediaInfo>;
20
20
 
21
21
  declare const formatFileSize: (size: number, decimals?: number) => string;
22
22
 
23
- interface BlobFile extends File {
24
- id: string;
25
- width: number | null;
26
- height: number | null;
27
- duration: number | null;
28
- }
29
-
30
23
  interface SuccessResult<D = unknown> {
31
24
  success: true;
32
25
  message?: string;
@@ -67,4 +60,4 @@ declare const result: {
67
60
  error: typeof error;
68
61
  };
69
62
 
70
- export { type BlobFile, type ErrorDetail, type ErrorResult, type ErrorResultParams, FolderFull, ROOT_FOLDER, ROOT_FOLDER_ID, ROOT_FOLDER_NAME, type Result, SIMPLE_UPLOAD_FOLDER_KEY, SIMPLE_UPLOAD_FOLDER_NAME, type SuccessResult, type SuccessResultParams, classifyFileType, formatFileSize, getMediaInfo, mimeToExtension, result };
63
+ export { type ErrorDetail, type ErrorResult, type ErrorResultParams, FolderFull, ROOT_FOLDER, ROOT_FOLDER_ID, ROOT_FOLDER_NAME, type Result, SIMPLE_UPLOAD_FOLDER_KEY, SIMPLE_UPLOAD_FOLDER_NAME, type SuccessResult, type SuccessResultParams, classifyFileType, formatFileSize, getMediaInfo, mimeToExtension, result };
@@ -1,21 +1,17 @@
1
1
  import jwt from 'jsonwebtoken';
2
2
  import { BinaryLike } from 'node:crypto';
3
3
  import { cookies } from 'next/headers';
4
- import Keyv from 'keyv';
4
+ import { S3Client } from '@aws-sdk/client-s3';
5
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';
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 Keyv from 'keyv';
7
8
 
8
- interface JwtServiceConfig {
9
+ interface CreateJwtServiceOptions {
9
10
  defaultSecret: string;
10
11
  issuer?: string;
11
12
  audience?: string;
12
13
  }
13
- /**
14
- * JWT service
15
- *
16
- * Sign, Verify
17
- */
18
- declare function createJwtService(config: JwtServiceConfig): {
14
+ declare function createJwtService({ defaultSecret, ...options }: CreateJwtServiceOptions): {
19
15
  sign: ({ payload, secret, expiresIn, }: {
20
16
  payload: object;
21
17
  secret: string;
@@ -32,15 +28,10 @@ declare function createArgon2Service(): {
32
28
  verify: (hash: string, plain: string) => Promise<boolean>;
33
29
  };
34
30
 
35
- interface CryptoServiceConfig {
31
+ interface CreateCryptoServiceOptions {
36
32
  defaultSecret: string;
37
33
  }
38
- /**
39
- * Crypto service
40
- *
41
- * Encrypt & Decrypt, Calculate checksum, Sign
42
- */
43
- declare function createCryptoService(config: CryptoServiceConfig): {
34
+ declare function createCryptoService({ defaultSecret, }: CreateCryptoServiceOptions): {
44
35
  generateToken: () => string;
45
36
  hash: (value: BinaryLike) => string;
46
37
  hashBuffer: (value: BinaryLike) => Buffer;
@@ -56,11 +47,6 @@ declare function createCryptoService(config: CryptoServiceConfig): {
56
47
  };
57
48
  };
58
49
 
59
- /**
60
- * Cookie Service
61
- *
62
- * Set, Get, Verify, Delete
63
- */
64
50
  declare function createCookieService(nextCookies: () => Promise<Awaited<ReturnType<typeof cookies>>>, cryptoService: ReturnType<typeof createCryptoService>): {
65
51
  set: ({ name, value, expireSeconds, }: {
66
52
  name: string;
@@ -86,6 +72,25 @@ declare function createCookieService(nextCookies: () => Promise<Awaited<ReturnTy
86
72
  }) => Promise<void>;
87
73
  };
88
74
 
75
+ interface CreateR2ServiceOptions {
76
+ r2Client: S3Client;
77
+ bucketName: string;
78
+ logger: Logger;
79
+ }
80
+ declare function createR2Service({ r2Client, bucketName, logger, }: CreateR2ServiceOptions): {
81
+ upload: ({ blobFile, folderKey, }: {
82
+ blobFile: BlobFile;
83
+ folderKey?: string;
84
+ }) => Promise<string>;
85
+ remove: ({ key }: {
86
+ key: string;
87
+ }) => Promise<void>;
88
+ move: ({ fromKey, toKey }: {
89
+ fromKey: string;
90
+ toKey: string;
91
+ }) => Promise<void>;
92
+ };
93
+
89
94
  interface CreateServerCacheOptions {
90
95
  redisUrl: string;
91
96
  namespace: string;
@@ -611,4 +616,4 @@ declare const POST_ORDER_BY: ({
611
616
  index: "asc";
612
617
  })[];
613
618
 
614
- 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 };
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 };
@@ -1,14 +1,18 @@
1
1
  import { ADMIN_ROLES, mimeToExtension, classifyFileType, ROOT_FOLDER_ID } from '../chunk-SEX4DOKX.js';
2
2
  import jwt from 'jsonwebtoken';
3
- import argon2, { argon2id } from 'argon2';
3
+ import argon2 from 'argon2';
4
4
  import crypto, { timingSafeEqual } from 'crypto';
5
5
  import { headers } from 'next/headers';
6
+ import { PutObjectCommand, DeleteObjectCommand, CopyObjectCommand } from '@aws-sdk/client-s3';
7
+ import path from 'path/posix';
8
+ import { ulid } from 'ulid';
6
9
  import KeyvRedis from '@keyv/redis';
7
10
  import Keyv from 'keyv';
8
- import { ulid } from 'ulid';
9
11
 
10
- function createJwtService(config) {
11
- const { defaultSecret, ...options } = config;
12
+ function createJwtService({
13
+ defaultSecret,
14
+ ...options
15
+ }) {
12
16
  function sign({
13
17
  payload = {},
14
18
  secret = defaultSecret,
@@ -38,8 +42,8 @@ function createJwtService(config) {
38
42
  verify
39
43
  };
40
44
  }
41
- var OPTIONS = {
42
- type: argon2id,
45
+ var DEFAULT_OPTIONS = {
46
+ type: argon2.argon2id,
43
47
  memoryCost: 19456,
44
48
  // ~19MB
45
49
  timeCost: 2,
@@ -47,7 +51,7 @@ var OPTIONS = {
47
51
  };
48
52
  function createArgon2Service() {
49
53
  async function hash(password) {
50
- return await argon2.hash(password, OPTIONS);
54
+ return await argon2.hash(password, DEFAULT_OPTIONS);
51
55
  }
52
56
  async function verify(hash2, plain) {
53
57
  return await argon2.verify(hash2, plain);
@@ -57,8 +61,9 @@ function createArgon2Service() {
57
61
  verify
58
62
  };
59
63
  }
60
- function createCryptoService(config) {
61
- const { defaultSecret } = config;
64
+ function createCryptoService({
65
+ defaultSecret
66
+ }) {
62
67
  const SECRET = crypto.createHash("sha256").update(defaultSecret).digest();
63
68
  const ALGORITHM = "aes-256-gcm";
64
69
  const IV_LENGTH = 12;
@@ -118,7 +123,7 @@ function createCryptoService(config) {
118
123
  sign
119
124
  };
120
125
  }
121
- var DEFAULTS = {
126
+ var DEFAULT_OPTIONS2 = {
122
127
  httpOnly: true,
123
128
  secure: process.env["NODE_ENV"] === "production",
124
129
  sameSite: "strict",
@@ -131,7 +136,7 @@ function createCookieService(nextCookies, cryptoService) {
131
136
  expireSeconds
132
137
  }) {
133
138
  const cookieStore = await nextCookies();
134
- cookieStore.set(name, value, { ...DEFAULTS, maxAge: expireSeconds });
139
+ cookieStore.set(name, value, { ...DEFAULT_OPTIONS2, maxAge: expireSeconds });
135
140
  }
136
141
  async function setSignedCookie({
137
142
  name,
@@ -195,6 +200,93 @@ function createCookieService(nextCookies, cryptoService) {
195
200
  delete: deleteCokkie
196
201
  };
197
202
  }
203
+ function createObjectKey({
204
+ filename,
205
+ mimeType,
206
+ folderKey = ""
207
+ }) {
208
+ const extensionFromFilename = path.extname(filename ?? "").toLowerCase();
209
+ const extensionFromMime = !extensionFromFilename && mimeType ? `.${mimeToExtension(mimeType)}` : "";
210
+ const extension = extensionFromFilename || extensionFromMime;
211
+ const normalizedFolder = folderKey.replace(/^\/+/, "").replace(/\/+$/, "");
212
+ const id = ulid();
213
+ const filenameWithId = id + extension;
214
+ if (!normalizedFolder) return filenameWithId;
215
+ return path.join(normalizedFolder, filenameWithId);
216
+ }
217
+
218
+ // src/server/infrastructure/r2/r2.service.ts
219
+ function createR2Service({
220
+ r2Client,
221
+ bucketName,
222
+ logger
223
+ }) {
224
+ async function upload({
225
+ blobFile,
226
+ folderKey = ""
227
+ }) {
228
+ const key = createObjectKey({
229
+ filename: blobFile.name,
230
+ mimeType: blobFile.type,
231
+ folderKey
232
+ });
233
+ try {
234
+ const buffer = Buffer.from(await blobFile.arrayBuffer());
235
+ await r2Client.send(
236
+ new PutObjectCommand({
237
+ Bucket: bucketName,
238
+ Key: key,
239
+ Body: buffer,
240
+ ContentType: blobFile.type || void 0
241
+ })
242
+ );
243
+ logger.debug("R2 upload success", { key });
244
+ return key;
245
+ } catch (error) {
246
+ logger.error("upload failed", { key, error });
247
+ throw error;
248
+ }
249
+ }
250
+ async function remove({ key }) {
251
+ try {
252
+ await r2Client.send(
253
+ new DeleteObjectCommand({
254
+ Bucket: bucketName,
255
+ Key: key
256
+ })
257
+ );
258
+ logger.debug("R2 object removed", { key });
259
+ } catch (error) {
260
+ logger.error("Remove failed", { key, error });
261
+ throw error;
262
+ }
263
+ }
264
+ async function move({ fromKey, toKey }) {
265
+ if (fromKey === toKey) {
266
+ logger.debug("R2 move skipped (same key)", { key: fromKey });
267
+ return;
268
+ }
269
+ try {
270
+ await r2Client.send(
271
+ new CopyObjectCommand({
272
+ Bucket: bucketName,
273
+ CopySource: encodeURI(`${bucketName}/${fromKey}`),
274
+ Key: toKey
275
+ })
276
+ );
277
+ await remove({ key: fromKey });
278
+ logger.debug("R2 object moved", { fromKey, toKey });
279
+ } catch (error) {
280
+ logger.error("Move failed", { fromKey, toKey, error });
281
+ throw error;
282
+ }
283
+ }
284
+ return {
285
+ upload,
286
+ remove,
287
+ move
288
+ };
289
+ }
198
290
 
199
291
  // src/server/infrastructure/cache/cache-key-delimiter.ts
200
292
  var CACHE_KEY_DELIMITER = "|";
@@ -1417,4 +1509,4 @@ function createSeoMetadataCommandRepository(prisma) {
1417
1509
  };
1418
1510
  }
1419
1511
 
1420
- 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 };
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 };
@@ -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 {
88
+ interface File$1 {
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[];
130
+ files: File$1[];
131
131
  }) | null;
132
132
  subFolders: Folder[];
133
- files: File[];
133
+ files: File$1[];
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 | null;
245
+ coverImage: File$1 | 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 & {
269
+ type FileFull = File$1 & {
270
270
  folder: Folder | null;
271
271
  adminAsAvatarImage: Admin[];
272
272
  postsAsCoverImage: Post[];
@@ -280,13 +280,13 @@ type FileFull = File & {
280
280
  translations: FileTranslation[];
281
281
  };
282
282
 
283
- type FileCard = File & {
283
+ type FileCard = File$1 & {
284
284
  translations: FileTranslation[];
285
285
  };
286
286
 
287
287
  type AdminFull = AdminSafe & {
288
288
  adminRefreshTokens: AdminRefreshToken[];
289
- avatarImage: (File & {
289
+ avatarImage: (File$1 & {
290
290
  translations: FileTranslation[];
291
291
  }) | null;
292
292
  posts: Post[];
@@ -337,4 +337,11 @@ interface SeoMetadata {
337
337
  updatedAt: Date;
338
338
  }
339
339
 
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 };
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yimingliao/cms",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "author": "Yiming Liao",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -42,5 +42,13 @@
42
42
  "prisma": "6.5.0",
43
43
  "tsup": "^8.5.1",
44
44
  "typescript": "^5.9.3"
45
+ },
46
+ "peerDependencies": {
47
+ "@aws-sdk/client-s3": "^3.0.0"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "@aws-sdk/client-s3": {
51
+ "optional": true
52
+ }
45
53
  }
46
54
  }
Binary file