@yimingliao/cms 0.0.14 → 0.0.16
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/{chunk-KEQXXUK2.js → chunk-SEX4DOKX.js} +27 -1
- package/dist/index.d.ts +41 -1
- package/dist/index.js +1 -1
- package/dist/server/index.d.ts +26 -1
- package/dist/server/index.js +113 -3
- package/package.json +4 -1
|
@@ -204,4 +204,30 @@ var formatFileSize = (size, decimals = 2) => {
|
|
|
204
204
|
return `${display} ${units[unitIndex]}`;
|
|
205
205
|
};
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
// src/shared/result/result.ts
|
|
208
|
+
function success({
|
|
209
|
+
message,
|
|
210
|
+
data,
|
|
211
|
+
meta
|
|
212
|
+
}) {
|
|
213
|
+
return {
|
|
214
|
+
success: true,
|
|
215
|
+
...message !== void 0 ? { message } : {},
|
|
216
|
+
...data !== void 0 ? { data } : {},
|
|
217
|
+
...meta !== void 0 ? { meta } : {}
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function error({ message, errors, code }) {
|
|
221
|
+
return {
|
|
222
|
+
success: false,
|
|
223
|
+
...message !== void 0 ? { message } : {},
|
|
224
|
+
...errors !== void 0 ? { errors } : {},
|
|
225
|
+
...code !== void 0 ? { code } : {}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
var result = {
|
|
229
|
+
success,
|
|
230
|
+
error
|
|
231
|
+
};
|
|
232
|
+
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -27,4 +27,44 @@ interface BlobFile extends File {
|
|
|
27
27
|
duration: number | null;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
interface SuccessResult<D = unknown> {
|
|
31
|
+
success: true;
|
|
32
|
+
message?: string;
|
|
33
|
+
data?: D;
|
|
34
|
+
meta?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
interface ErrorResult {
|
|
37
|
+
success: false;
|
|
38
|
+
message?: string;
|
|
39
|
+
errors?: ErrorDetail[];
|
|
40
|
+
code?: string;
|
|
41
|
+
}
|
|
42
|
+
interface ErrorDetail {
|
|
43
|
+
field?: string;
|
|
44
|
+
message?: string;
|
|
45
|
+
code?: string;
|
|
46
|
+
}
|
|
47
|
+
type Result<D = unknown> = SuccessResult<D> | ErrorResult;
|
|
48
|
+
|
|
49
|
+
interface SuccessResultParams<D = unknown> {
|
|
50
|
+
message?: string;
|
|
51
|
+
data?: D;
|
|
52
|
+
meta?: Record<string, unknown>;
|
|
53
|
+
}
|
|
54
|
+
declare function success<D = unknown>({ message, data, meta, }: SuccessResultParams<D>): SuccessResult<D>;
|
|
55
|
+
interface ErrorResultParams {
|
|
56
|
+
message?: string;
|
|
57
|
+
errors?: ErrorDetail[];
|
|
58
|
+
code?: string;
|
|
59
|
+
}
|
|
60
|
+
declare function error({ message, errors, code }: ErrorResultParams): ErrorResult;
|
|
61
|
+
/**
|
|
62
|
+
* Generic result interface for API and action responses.
|
|
63
|
+
* Provides a unified structure for success and error outputs.
|
|
64
|
+
*/
|
|
65
|
+
declare const result: {
|
|
66
|
+
success: typeof success;
|
|
67
|
+
error: typeof error;
|
|
68
|
+
};
|
|
69
|
+
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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 } from './chunk-
|
|
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';
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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';
|
|
5
|
+
import { Logger } from 'logry';
|
|
4
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';
|
|
5
7
|
|
|
6
8
|
interface JwtServiceConfig {
|
|
@@ -84,6 +86,29 @@ declare function createCookieService(nextCookies: () => Promise<Awaited<ReturnTy
|
|
|
84
86
|
}) => Promise<void>;
|
|
85
87
|
};
|
|
86
88
|
|
|
89
|
+
interface CreateServerCacheOptions {
|
|
90
|
+
redisUrl: string;
|
|
91
|
+
namespace: string;
|
|
92
|
+
keyDelimiter?: string;
|
|
93
|
+
}
|
|
94
|
+
declare function createCache({ redisUrl, namespace, keyDelimiter, }: CreateServerCacheOptions): Keyv<unknown>;
|
|
95
|
+
|
|
96
|
+
type RawCacheKey = string | Array<string | number | boolean | undefined | null>;
|
|
97
|
+
|
|
98
|
+
interface CacheResultOptions<T> {
|
|
99
|
+
key: RawCacheKey;
|
|
100
|
+
ttl?: number;
|
|
101
|
+
load: () => Promise<T>;
|
|
102
|
+
}
|
|
103
|
+
declare function createCacheResult(cache: Keyv<unknown>, logger: Logger): <T>({ key: rawKey, ttl, load, }: CacheResultOptions<T>) => Promise<T>;
|
|
104
|
+
|
|
105
|
+
interface RateLimiterOptions {
|
|
106
|
+
key: RawCacheKey;
|
|
107
|
+
maxAttempts?: number;
|
|
108
|
+
timeWindow?: number;
|
|
109
|
+
}
|
|
110
|
+
declare function createIpRateLimiter(cache: Keyv<unknown>, appName: string): ({ key: rawKey, maxAttempts, timeWindow, }: RateLimiterOptions) => Promise<boolean>;
|
|
111
|
+
|
|
87
112
|
interface CreateParams$4 {
|
|
88
113
|
email: string;
|
|
89
114
|
role: AdminRole;
|
|
@@ -585,4 +610,4 @@ declare const POST_ORDER_BY: ({
|
|
|
585
610
|
index: "asc";
|
|
586
611
|
})[];
|
|
587
612
|
|
|
588
|
-
export { ADMIN_ORDER_BY, ORDER_BY, POST_ORDER_BY, createAdminCommandRepository, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenQueryRepository, createArgon2Service, createCookieService, createCryptoService, createFileCommandRepository, createFileQueryRepository, createFolderCommandRepository, createFolderQueryRepository, createJwtService, createPostCommandRepository, createPostQueryRepository, createSeoMetadataCommandRepository };
|
|
613
|
+
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 };
|
package/dist/server/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { ADMIN_ROLES, mimeToExtension, classifyFileType, ROOT_FOLDER_ID } from '../chunk-
|
|
1
|
+
import { ADMIN_ROLES, mimeToExtension, classifyFileType, ROOT_FOLDER_ID } from '../chunk-SEX4DOKX.js';
|
|
2
2
|
import jwt from 'jsonwebtoken';
|
|
3
3
|
import argon2, { argon2id } from 'argon2';
|
|
4
4
|
import crypto, { timingSafeEqual } from 'crypto';
|
|
5
|
-
import 'next/headers';
|
|
5
|
+
import { headers } from 'next/headers';
|
|
6
|
+
import KeyvRedis from '@keyv/redis';
|
|
7
|
+
import Keyv from 'keyv';
|
|
6
8
|
import { ulid } from 'ulid';
|
|
7
9
|
|
|
8
10
|
function createJwtService(config) {
|
|
@@ -194,6 +196,114 @@ function createCookieService(nextCookies, cryptoService) {
|
|
|
194
196
|
};
|
|
195
197
|
}
|
|
196
198
|
|
|
199
|
+
// src/server/infrastructure/cache/cache-key-delimiter.ts
|
|
200
|
+
var CACHE_KEY_DELIMITER = "|";
|
|
201
|
+
|
|
202
|
+
// src/server/infrastructure/cache/create-cache.ts
|
|
203
|
+
function createCache({
|
|
204
|
+
redisUrl,
|
|
205
|
+
namespace,
|
|
206
|
+
keyDelimiter = CACHE_KEY_DELIMITER
|
|
207
|
+
}) {
|
|
208
|
+
const redisStore = new KeyvRedis(redisUrl, {
|
|
209
|
+
keyPrefixSeparator: keyDelimiter
|
|
210
|
+
});
|
|
211
|
+
return new Keyv({
|
|
212
|
+
store: redisStore,
|
|
213
|
+
namespace,
|
|
214
|
+
useKeyPrefix: false
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/server/infrastructure/cache/normalize-cache-key.ts
|
|
219
|
+
var sanitize = (k) => k.replaceAll(/[\u200B-\u200D\uFEFF]/g, "").replaceAll(/[\r\n]/g, "").trim();
|
|
220
|
+
var normalizeCacheKey = (key, delimiter = CACHE_KEY_DELIMITER) => {
|
|
221
|
+
if (!key) return null;
|
|
222
|
+
if (Array.isArray(key)) {
|
|
223
|
+
if (key.length === 0) return null;
|
|
224
|
+
const normalized = key.map((k) => {
|
|
225
|
+
if (k === null) return "__null";
|
|
226
|
+
if (k === void 0) return "__undefined";
|
|
227
|
+
if (typeof k === "boolean") return k ? "__true" : "__false";
|
|
228
|
+
return sanitize(String(k));
|
|
229
|
+
});
|
|
230
|
+
return normalized.join(delimiter);
|
|
231
|
+
}
|
|
232
|
+
if (typeof key === "boolean") return key ? "__true" : "__false";
|
|
233
|
+
return String(key);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/server/infrastructure/cache/create-cache-result.ts
|
|
237
|
+
var DAY = 1e3 * 60 * 60 * 24;
|
|
238
|
+
function createCacheResult(cache, logger) {
|
|
239
|
+
return async function cacheResult({
|
|
240
|
+
key: rawKey,
|
|
241
|
+
ttl = DAY,
|
|
242
|
+
load
|
|
243
|
+
}) {
|
|
244
|
+
const key = normalizeCacheKey(rawKey);
|
|
245
|
+
if (!key) {
|
|
246
|
+
logger.error("Cache skipped due to invalid key", {
|
|
247
|
+
rawKey,
|
|
248
|
+
scope: "cacheResult"
|
|
249
|
+
});
|
|
250
|
+
return load();
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const cached = await cache.get(key);
|
|
254
|
+
if (cached !== void 0) {
|
|
255
|
+
return cached;
|
|
256
|
+
}
|
|
257
|
+
const data = await load();
|
|
258
|
+
if (data !== void 0) {
|
|
259
|
+
await cache.set(key, data, ttl);
|
|
260
|
+
}
|
|
261
|
+
return data;
|
|
262
|
+
} catch (error) {
|
|
263
|
+
logger.error("Cache failure, falling back to loader", {
|
|
264
|
+
key,
|
|
265
|
+
error,
|
|
266
|
+
scope: "cacheResult"
|
|
267
|
+
});
|
|
268
|
+
return load();
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
var DEFAULT_MAX_ATTEMPTS = 10;
|
|
273
|
+
var DEFAULT_TIME_WINDOW = 60;
|
|
274
|
+
var lua = `
|
|
275
|
+
local current = redis.call('INCR', KEYS[1])
|
|
276
|
+
if current == 1 then
|
|
277
|
+
redis.call('EXPIRE', KEYS[1], ARGV[1])
|
|
278
|
+
end
|
|
279
|
+
return current
|
|
280
|
+
`;
|
|
281
|
+
function createIpRateLimiter(cache, appName) {
|
|
282
|
+
return async function ipRateLimiter({
|
|
283
|
+
key: rawKey,
|
|
284
|
+
maxAttempts = DEFAULT_MAX_ATTEMPTS,
|
|
285
|
+
timeWindow = DEFAULT_TIME_WINDOW
|
|
286
|
+
}) {
|
|
287
|
+
const secondaryStore = cache.store;
|
|
288
|
+
const redis = await secondaryStore.getClient();
|
|
289
|
+
if (!redis) return true;
|
|
290
|
+
const headersStore = await headers();
|
|
291
|
+
const ip = headersStore.get("cf-connecting-ip")?.trim() || headersStore.get("x-forwarded-for")?.split(",")[0]?.trim() || "unknown";
|
|
292
|
+
const key = normalizeCacheKey([
|
|
293
|
+
appName,
|
|
294
|
+
"ip-rate-limiter",
|
|
295
|
+
...Array.isArray(rawKey) ? rawKey : [rawKey],
|
|
296
|
+
ip
|
|
297
|
+
]);
|
|
298
|
+
const currentRaw = await redis.eval(lua, {
|
|
299
|
+
keys: [key],
|
|
300
|
+
arguments: [timeWindow.toString()]
|
|
301
|
+
});
|
|
302
|
+
const current = typeof currentRaw === "number" ? currentRaw : Number(currentRaw);
|
|
303
|
+
return current <= maxAttempts;
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
197
307
|
// src/server/infrastructure/database/utils/connect.ts
|
|
198
308
|
var ids = (items) => items.map(({ id }) => ({ id }));
|
|
199
309
|
function connectOne(item) {
|
|
@@ -1307,4 +1417,4 @@ function createSeoMetadataCommandRepository(prisma) {
|
|
|
1307
1417
|
};
|
|
1308
1418
|
}
|
|
1309
1419
|
|
|
1310
|
-
export { ADMIN_ORDER_BY, ORDER_BY, POST_ORDER_BY, createAdminCommandRepository, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenQueryRepository, createArgon2Service, createCookieService, createCryptoService, createFileCommandRepository, createFileQueryRepository, createFolderCommandRepository, createFolderQueryRepository, createJwtService, createPostCommandRepository, createPostQueryRepository, createSeoMetadataCommandRepository };
|
|
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yimingliao/cms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"author": "Yiming Liao",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -26,8 +26,11 @@
|
|
|
26
26
|
"prepublishOnly": "yarn build"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@keyv/redis": "^5.1.6",
|
|
29
30
|
"argon2": "^0.44.0",
|
|
30
31
|
"jsonwebtoken": "^9.0.3",
|
|
32
|
+
"keyv": "^5.6.0",
|
|
33
|
+
"logry": "^2.1.6",
|
|
31
34
|
"mime-types": "^3.0.2",
|
|
32
35
|
"ulid": "^3.0.2"
|
|
33
36
|
},
|