@yimingliao/cms 0.0.7 → 0.0.9

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.js CHANGED
@@ -1,1421 +1 @@
1
- import jwt from 'jsonwebtoken';
2
- import argon2, { argon2id } from 'argon2';
3
- import crypto, { timingSafeEqual } from 'crypto';
4
- import 'next/headers';
5
- import { extension, lookup } from 'mime-types';
6
- import { ulid } from 'ulid';
7
-
8
- // src/server/infrastructure/jwt/jwt.service.ts
9
- function createJwtService(config) {
10
- const { defaultSecret, ...options } = config;
11
- function sign({
12
- payload = {},
13
- secret = defaultSecret,
14
- expiresIn = 60 * 60
15
- }) {
16
- return jwt.sign(payload, secret, {
17
- algorithm: "HS256",
18
- expiresIn,
19
- ...options
20
- });
21
- }
22
- function verify({
23
- token,
24
- secret = defaultSecret
25
- }) {
26
- const payload = jwt.verify(token, secret, {
27
- algorithms: ["HS256"],
28
- ...options
29
- });
30
- if (typeof payload === "string") {
31
- throw new TypeError("Invalid JWT payload");
32
- }
33
- return payload;
34
- }
35
- return {
36
- sign,
37
- verify
38
- };
39
- }
40
- var OPTIONS = {
41
- type: argon2id,
42
- memoryCost: 19456,
43
- // ~19MB
44
- timeCost: 2,
45
- parallelism: 1
46
- };
47
- function createArgon2Service() {
48
- async function hash(password) {
49
- return await argon2.hash(password, OPTIONS);
50
- }
51
- async function verify(hash2, plain) {
52
- return await argon2.verify(hash2, plain);
53
- }
54
- return {
55
- hash,
56
- verify
57
- };
58
- }
59
- function createCryptoService(config) {
60
- const { defaultSecret } = config;
61
- const SECRET = crypto.createHash("sha256").update(defaultSecret).digest();
62
- const ALGORITHM = "aes-256-gcm";
63
- const IV_LENGTH = 12;
64
- function generateToken() {
65
- return crypto.randomBytes(32).toString("base64url");
66
- }
67
- function hash(value) {
68
- return crypto.createHash("sha256").update(value).digest("hex");
69
- }
70
- function hashBuffer(value) {
71
- return crypto.createHash("sha256").update(value).digest();
72
- }
73
- function encrypt(data) {
74
- const iv = crypto.randomBytes(IV_LENGTH);
75
- const cipher = crypto.createCipheriv(ALGORITHM, SECRET, iv);
76
- const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
77
- const tag = cipher.getAuthTag();
78
- return [
79
- iv.toString("hex"),
80
- encrypted.toString("hex"),
81
- tag.toString("hex")
82
- ].join(":");
83
- }
84
- function decrypt(data) {
85
- const parts = data.split(":");
86
- if (parts.length !== 3 || !parts.every(Boolean)) {
87
- throw new Error("Invalid encrypted payload");
88
- }
89
- const [ivHex, encryptedHex, tagHex] = parts;
90
- const iv = Buffer.from(ivHex, "hex");
91
- const encrypted = Buffer.from(encryptedHex, "hex");
92
- const tag = Buffer.from(tagHex, "hex");
93
- const decipher = crypto.createDecipheriv(ALGORITHM, SECRET, iv);
94
- decipher.setAuthTag(tag);
95
- const decrypted = Buffer.concat([
96
- decipher.update(encrypted),
97
- decipher.final()
98
- ]);
99
- return decrypted.toString("utf8");
100
- }
101
- function sign({
102
- value,
103
- expireSeconds,
104
- createdAt
105
- }) {
106
- const payload = `${value}|${expireSeconds}|${createdAt ?? Date.now()}`;
107
- const signature = crypto.createHmac("sha256", SECRET).update(payload).digest("hex");
108
- const signed = `${payload}|${signature}`;
109
- return { signed, signature };
110
- }
111
- return {
112
- generateToken,
113
- hash,
114
- hashBuffer,
115
- encrypt,
116
- decrypt,
117
- sign
118
- };
119
- }
120
- var DEFAULTS = {
121
- httpOnly: true,
122
- secure: process.env["NODE_ENV"] === "production",
123
- sameSite: "strict",
124
- path: "/"
125
- };
126
- function createCookieService(nextCookies, cryptoService) {
127
- async function set({
128
- name,
129
- value,
130
- expireSeconds
131
- }) {
132
- const cookieStore = await nextCookies();
133
- cookieStore.set(name, value, { ...DEFAULTS, maxAge: expireSeconds });
134
- }
135
- async function setSignedCookie({
136
- name,
137
- value,
138
- expireSeconds
139
- }) {
140
- const createdAt = Date.now();
141
- const { signed } = cryptoService.sign({
142
- value,
143
- expireSeconds,
144
- createdAt
145
- });
146
- await set({ name, value: signed, expireSeconds });
147
- }
148
- async function get({ name }) {
149
- const cookieStore = await nextCookies();
150
- const cookieValue = cookieStore.get(name)?.value;
151
- return cookieValue;
152
- }
153
- async function getSignedCookie({ name }) {
154
- const signed = await get({ name });
155
- const value = verifySignedCookie({ signed });
156
- return value;
157
- }
158
- function verifySignedCookie({ signed }) {
159
- if (!signed) throw new Error("Invalid cookie");
160
- const parts = signed.split("|");
161
- if (parts.length !== 4) throw new Error("Invalid cookie");
162
- const [value, expireSecondsStr, createdAtStr, signature] = parts;
163
- const expireSeconds = Number.parseInt(expireSecondsStr, 10);
164
- const createdAt = Number.parseInt(createdAtStr, 10);
165
- if (Number.isNaN(expireSeconds) || Number.isNaN(createdAt)) {
166
- throw new TypeError("Invalid cookie");
167
- }
168
- const now = Date.now();
169
- if (now > createdAt + expireSeconds * 1e3) {
170
- throw new Error("Cookie expired");
171
- }
172
- const { signature: generatedSignature } = cryptoService.sign({
173
- value,
174
- expireSeconds,
175
- createdAt
176
- });
177
- const sig = Buffer.from(signature);
178
- const gen = Buffer.from(generatedSignature);
179
- if (sig.length !== gen.length || !timingSafeEqual(sig, gen)) {
180
- throw new Error("Invalid cookie signature");
181
- }
182
- return value;
183
- }
184
- async function deleteCokkie({ name }) {
185
- const cookieStore = await nextCookies();
186
- cookieStore.delete(name);
187
- }
188
- return {
189
- set,
190
- setSignedCookie,
191
- get,
192
- getSignedCookie,
193
- verifySignedCookie,
194
- delete: deleteCokkie
195
- };
196
- }
197
-
198
- // src/server/infrastructure/database/utils/connect.ts
199
- var ids = (items) => items.map(({ id }) => ({ id }));
200
- function connectOne(item) {
201
- return item ? { connect: { id: item.id } } : {};
202
- }
203
- function connectMany(items) {
204
- return { connect: ids(items) };
205
- }
206
- function updateOne(item) {
207
- return item ? { connect: { id: item.id } } : { disconnect: true };
208
- }
209
- function updateMany(items) {
210
- return { set: ids(items) };
211
- }
212
-
213
- // src/server/infrastructure/database/admin/command/create-admin-command-repository.ts
214
- function createAdminCommandRepository(prisma) {
215
- async function create({
216
- // ------------------------------------
217
- // relations
218
- // ------------------------------------
219
- // File
220
- avatarImage,
221
- // ------------------------------------
222
- // translation
223
- // ------------------------------------
224
- translations,
225
- // rest
226
- ...params
227
- }) {
228
- const created = await prisma.admin.create({
229
- data: {
230
- ...params,
231
- // ------------------------------------------------------------------------
232
- // relations
233
- // ------------------------------------------------------------------------
234
- // File
235
- avatarImage: connectOne(avatarImage),
236
- // ------------------------------------------------------------------------
237
- // translation
238
- // ------------------------------------------------------------------------
239
- translations: { create: translations }
240
- }
241
- });
242
- return created;
243
- }
244
- async function update({
245
- id,
246
- // ------------------------------------
247
- // relations
248
- // ------------------------------------
249
- // File
250
- avatarImage,
251
- // ------------------------------------
252
- // translation
253
- // ------------------------------------
254
- translations,
255
- // rest
256
- ...params
257
- }) {
258
- const updated = await prisma.admin.update({
259
- where: { id },
260
- data: {
261
- ...params,
262
- // ------------------------------------------------------------------------
263
- // relations
264
- // ------------------------------------------------------------------------
265
- // File
266
- avatarImage: updateOne(avatarImage),
267
- // ------------------------------------------------------------------------
268
- // translation
269
- // ------------------------------------------------------------------------
270
- translations: {
271
- upsert: translations.map((t) => ({
272
- where: { adminId_locale: { adminId: id, locale: t.locale } },
273
- update: t,
274
- create: t
275
- }))
276
- }
277
- }
278
- });
279
- return updated;
280
- }
281
- async function _delete({ id }) {
282
- const deleted = await prisma.admin.delete({
283
- where: { id }
284
- });
285
- return deleted;
286
- }
287
- return {
288
- create,
289
- update,
290
- delete: _delete
291
- };
292
- }
293
-
294
- // src/server/infrastructure/database/constants.ts
295
- var ORDER_BY = [
296
- { updatedAt: "desc" },
297
- { createdAt: "desc" },
298
- { id: "asc" }
299
- ];
300
- var ADMIN_ORDER_BY = [
301
- { role: "asc" },
302
- ...ORDER_BY
303
- ];
304
- var POST_ORDER_BY = [
305
- { index: "asc" },
306
- ...ORDER_BY
307
- ];
308
-
309
- // src/domain/resources/admin/props.ts
310
- var ADMIN_ROLES = {
311
- SUPER_ADMIN: "SUPER_ADMIN",
312
- ADMIN: "ADMIN",
313
- EDITOR: "EDITOR"
314
- };
315
-
316
- // src/domain/resources/file/props.ts
317
- var FILE_TYPES = {
318
- IMAGE: "IMAGE",
319
- AUDIO: "AUDIO",
320
- VIDEO: "VIDEO",
321
- DOCUMENT: "DOCUMENT",
322
- ARCHIVE: "ARCHIVE",
323
- OTHER: "OTHER"
324
- };
325
-
326
- // src/domain/resources/post/props.ts
327
- var POST_TYPES = {
328
- TOPIC: "TOPIC",
329
- CATEGORY: "CATEGORY",
330
- POST: "POST",
331
- TAG: "TAG",
332
- PAGE: "PAGE"
333
- };
334
-
335
- // src/domain/resources/constants.ts
336
- var ROOT_FOLDER_ID = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
337
-
338
- // src/server/infrastructure/database/admin/include.ts
339
- var ADMIN_FULL_INCLUDE = {
340
- // ---------------------------
341
- // relations: AdminRefreshToken
342
- // ---------------------------
343
- adminRefreshTokens: true,
344
- // ---------------------------
345
- // relations: File
346
- // ---------------------------
347
- avatarImage: { include: { translations: true } },
348
- // ---------------------------
349
- // relations: Post
350
- // ---------------------------
351
- posts: true,
352
- // ---------------------------
353
- // translation
354
- // ---------------------------
355
- translations: true
356
- };
357
-
358
- // src/server/infrastructure/database/utils/create-search.ts
359
- function buildContainsOr(fields, value) {
360
- return fields.map((field) => ({
361
- [field]: { contains: value, mode: "insensitive" }
362
- }));
363
- }
364
- function buildTranslationSearch(locale, fields, value) {
365
- return {
366
- translations: {
367
- some: { locale, OR: buildContainsOr(fields, value) }
368
- }
369
- };
370
- }
371
- function createSearch({
372
- locale,
373
- searchString,
374
- rootFields = [],
375
- translationFields = []
376
- }) {
377
- if (!searchString) return {};
378
- const conditions = [];
379
- if (rootFields.length > 0) {
380
- conditions.push(...buildContainsOr(rootFields, searchString));
381
- }
382
- if (translationFields.length > 0 && locale) {
383
- conditions.push(
384
- buildTranslationSearch(locale, translationFields, searchString)
385
- );
386
- }
387
- return conditions.length > 0 ? { OR: conditions } : {};
388
- }
389
-
390
- // src/server/infrastructure/database/utils/create-pagination.ts
391
- function createPagination(page, pageSize) {
392
- if (!page || !pageSize) return {};
393
- return {
394
- skip: (page - 1) * pageSize,
395
- take: pageSize
396
- };
397
- }
398
-
399
- // src/server/infrastructure/database/admin/query/create-admin-query-repository.ts
400
- var OMIT_PASSWORD = { omit: { passwordHash: true } };
401
- function buildWhere(params) {
402
- if (params.id) return { id: params.id };
403
- if (params.email) return { email: params.email };
404
- return;
405
- }
406
- function createAdminQueryRepository(prisma) {
407
- async function findListCards({
408
- locale,
409
- // search
410
- searchString,
411
- role,
412
- adminIds,
413
- // pagination
414
- page,
415
- pageSize
416
- }) {
417
- const where = {
418
- // search
419
- ...createSearch({
420
- ...searchString !== void 0 ? { searchString } : {},
421
- locale,
422
- translationFields: ["name"]
423
- }),
424
- // role
425
- ...role !== ADMIN_ROLES.SUPER_ADMIN ? { role: { notIn: [ADMIN_ROLES.SUPER_ADMIN] } } : {},
426
- // Specified ids
427
- ...adminIds?.length ? { id: { in: adminIds } } : {}
428
- };
429
- const [items, total] = await prisma.$transaction([
430
- prisma.admin.findMany({
431
- where,
432
- orderBy: ADMIN_ORDER_BY,
433
- include: ADMIN_FULL_INCLUDE,
434
- ...createPagination(page, pageSize),
435
- ...OMIT_PASSWORD
436
- }),
437
- prisma.admin.count({ where })
438
- ]);
439
- return { items, total };
440
- }
441
- async function find(params) {
442
- const where = buildWhere(params);
443
- if (!where) return null;
444
- return prisma.admin.findUnique({
445
- where,
446
- ...OMIT_PASSWORD
447
- });
448
- }
449
- async function findFull(params) {
450
- const where = buildWhere(params);
451
- if (!where) return null;
452
- return await prisma.admin.findUnique({
453
- where,
454
- include: ADMIN_FULL_INCLUDE,
455
- ...OMIT_PASSWORD
456
- });
457
- }
458
- async function findWithPasswordHash(params) {
459
- const where = buildWhere(params);
460
- if (!where) return null;
461
- return prisma.admin.findUnique({
462
- where
463
- });
464
- }
465
- return {
466
- findListCards,
467
- find,
468
- findFull,
469
- findWithPasswordHash
470
- };
471
- }
472
-
473
- // src/server/infrastructure/database/admin-refresh-token/command/create-admin-refresh-token-command-repository.ts
474
- function createAdminRefreshTokenCommandRepository(prisma) {
475
- async function create({
476
- adminId,
477
- ...params
478
- }) {
479
- const created = await prisma.adminRefreshToken.create({
480
- data: {
481
- ...params,
482
- admin: { connect: { id: adminId } },
483
- deviceInfo: params.deviceInfo
484
- }
485
- });
486
- return created;
487
- }
488
- async function _delete({ id, tokenHash }) {
489
- const where = id ? { id } : tokenHash ? { tokenHash } : void 0;
490
- if (where) {
491
- await prisma.adminRefreshToken.delete({
492
- where
493
- });
494
- }
495
- }
496
- async function deleteManyByExpired() {
497
- const { count } = await prisma.adminRefreshToken.deleteMany({
498
- where: { expiresAt: { lt: /* @__PURE__ */ new Date() } }
499
- });
500
- return count;
501
- }
502
- return {
503
- create,
504
- delete: _delete,
505
- deleteManyByExpired
506
- };
507
- }
508
-
509
- // src/server/infrastructure/database/admin-refresh-token/query/create-admin-refresh-token-query-repository.ts
510
- function createAdminRefreshTokenQueryRepository(prisma) {
511
- async function findManyByAdminId({
512
- adminId
513
- }) {
514
- return await prisma.adminRefreshToken.findMany({
515
- where: { adminId },
516
- orderBy: [{ createdAt: "desc" }, { id: "asc" }]
517
- });
518
- }
519
- async function findByToken({
520
- tokenHash
521
- }) {
522
- return await prisma.adminRefreshToken.findUnique({
523
- where: { tokenHash }
524
- });
525
- }
526
- return {
527
- findManyByAdminId,
528
- findByToken
529
- };
530
- }
531
-
532
- // src/server/infrastructure/database/file/include.ts
533
- var FILE_FULL_INCLUDE = {
534
- // ---------------------------
535
- // relations: Folder
536
- // ---------------------------
537
- folder: true,
538
- // ---------------------------
539
- // relations: Admin
540
- // ---------------------------
541
- adminAsAvatarImage: true,
542
- // ---------------------------
543
- // relations: Post
544
- // ---------------------------
545
- postsAsCoverImage: true,
546
- postsAsContentImage: true,
547
- // ---------------------------
548
- // --- custom fields
549
- // ---------------------------
550
- postsAsImages1: true,
551
- postsAsImages2: true,
552
- postsAsImage1: true,
553
- postsAsImage2: true,
554
- postsAsImage3: true,
555
- postsAsImage4: true,
556
- // ---------------------------
557
- // translation
558
- // ---------------------------
559
- translations: true
560
- };
561
- var mimeToExtension = (mimeType) => {
562
- if (!mimeType) return "unknown";
563
- return (extension(mimeType) || "unknown").toLowerCase();
564
- };
565
- var ARCHIVE_MIME = /* @__PURE__ */ new Set([
566
- "application/zip",
567
- "application/x-rar-compressed",
568
- "application/x-7z-compressed",
569
- "application/gzip",
570
- "application/x-tar"
571
- ]);
572
- var classifyFileType = (mimeType, extension2) => {
573
- if (!mimeType && extension2) {
574
- mimeType = lookup(extension2) || void 0;
575
- }
576
- if (!mimeType) return FILE_TYPES.OTHER;
577
- if (mimeType.startsWith("image/")) return FILE_TYPES.IMAGE;
578
- if (mimeType.startsWith("video/")) return FILE_TYPES.VIDEO;
579
- if (mimeType.startsWith("audio/")) return FILE_TYPES.AUDIO;
580
- if (ARCHIVE_MIME.has(mimeType)) return FILE_TYPES.ARCHIVE;
581
- if (mimeType.startsWith("text/") || mimeType === "application/pdf" || mimeType === "application/json" || mimeType === "application/xml") {
582
- return FILE_TYPES.DOCUMENT;
583
- }
584
- if (mimeType.startsWith("application/")) return FILE_TYPES.DOCUMENT;
585
- return FILE_TYPES.OTHER;
586
- };
587
-
588
- // src/shared/blob-file/get-media-info/get-audio-info.ts
589
- function getAudioInfo(file) {
590
- return new Promise((resolve, reject) => {
591
- const audio = document.createElement("audio");
592
- const objectUrl = URL.createObjectURL(file);
593
- audio.preload = "metadata";
594
- const cleanup = () => {
595
- audio.removeEventListener("loadedmetadata", onLoadedMetadata);
596
- audio.removeEventListener("error", onError);
597
- URL.revokeObjectURL(objectUrl);
598
- };
599
- const onLoadedMetadata = () => {
600
- const duration = audio.duration;
601
- cleanup();
602
- resolve({ duration });
603
- };
604
- const onError = () => {
605
- cleanup();
606
- reject(new Error("Failed to load audio metadata."));
607
- };
608
- audio.addEventListener("loadedmetadata", onLoadedMetadata, { once: true });
609
- audio.addEventListener("error", onError, { once: true });
610
- audio.src = objectUrl;
611
- });
612
- }
613
-
614
- // src/shared/blob-file/get-media-info/get-image-info.ts
615
- function getImageInfo(file) {
616
- return new Promise((resolve, reject) => {
617
- const img = new Image();
618
- const objectUrl = URL.createObjectURL(file);
619
- const cleanup = () => {
620
- img.removeEventListener("load", onLoad);
621
- img.removeEventListener("error", onError);
622
- URL.revokeObjectURL(objectUrl);
623
- };
624
- const onLoad = () => {
625
- const width = img.naturalWidth;
626
- const height = img.naturalHeight;
627
- cleanup();
628
- resolve({ width, height });
629
- };
630
- const onError = () => {
631
- cleanup();
632
- reject(new Error("Failed to load image for size detection."));
633
- };
634
- img.addEventListener("load", onLoad, { once: true });
635
- img.addEventListener("error", onError, { once: true });
636
- img.src = objectUrl;
637
- });
638
- }
639
-
640
- // src/shared/blob-file/get-media-info/get-video-info.ts
641
- function getVideoInfo(file) {
642
- return new Promise((resolve, reject) => {
643
- const video = document.createElement("video");
644
- const objectUrl = URL.createObjectURL(file);
645
- video.preload = "metadata";
646
- const cleanup = () => {
647
- video.removeEventListener("loadedmetadata", onLoadedMetadata);
648
- video.removeEventListener("error", onError);
649
- video.src = "";
650
- URL.revokeObjectURL(objectUrl);
651
- };
652
- const onLoadedMetadata = () => {
653
- const info = {
654
- width: video.videoWidth,
655
- height: video.videoHeight,
656
- duration: video.duration
657
- };
658
- cleanup();
659
- resolve(info);
660
- };
661
- const onError = () => {
662
- cleanup();
663
- reject(new Error("Failed to load video metadata."));
664
- };
665
- video.addEventListener("loadedmetadata", onLoadedMetadata, { once: true });
666
- video.addEventListener("error", onError, { once: true });
667
- video.src = objectUrl;
668
- });
669
- }
670
-
671
- // src/shared/blob-file/get-media-info/get-media-info.ts
672
- var IMAGE_EXT = /\.(jpe?g|png|gif|webp|bmp|avif)$/i;
673
- var VIDEO_EXT = /\.(mp4|webm|mov|mkv)$/i;
674
- var AUDIO_EXT = /\.(mp3|wav|ogg|m4a)$/i;
675
- var getMediaInfo = async (file) => {
676
- const fallback = { width: null, height: null, duration: null };
677
- const mime = file.type || "";
678
- const name = file instanceof File ? file.name.toLowerCase() : "";
679
- try {
680
- if (mime.startsWith("image/") || IMAGE_EXT.test(name)) {
681
- const { width, height } = await getImageInfo(file);
682
- return { width, height, duration: null };
683
- }
684
- if (mime.startsWith("video/") || VIDEO_EXT.test(name)) {
685
- const { width, height, duration } = await getVideoInfo(file);
686
- return { width, height, duration };
687
- }
688
- if (mime.startsWith("audio/") || AUDIO_EXT.test(name)) {
689
- const { duration } = await getAudioInfo(file);
690
- return { width: null, height: null, duration };
691
- }
692
- return fallback;
693
- } catch {
694
- return fallback;
695
- }
696
- };
697
-
698
- // src/shared/blob-file/format-file-size.ts
699
- var formatFileSize = (size, decimals = 2) => {
700
- const units = ["B", "KB", "MB", "GB", "TB"];
701
- let value = size;
702
- let unitIndex = 0;
703
- while (value >= 1024 && unitIndex < units.length - 1) {
704
- value /= 1024;
705
- unitIndex++;
706
- }
707
- const display = unitIndex === 0 ? value.toString() : value.toFixed(decimals);
708
- return `${display} ${units[unitIndex]}`;
709
- };
710
-
711
- // src/server/infrastructure/database/file/command/create-file-command-repository.ts
712
- function createFileCommandRepository(prisma) {
713
- async function create({
714
- // meta
715
- fileMeta,
716
- // ------------------------------------
717
- // relations
718
- // ------------------------------------
719
- // Folder
720
- folder,
721
- // ------------------------------------
722
- // translation
723
- // ------------------------------------
724
- translations,
725
- // rest
726
- ...params
727
- }) {
728
- const extension2 = mimeToExtension(fileMeta.type);
729
- const created = await prisma.file.create({
730
- data: {
731
- ...params,
732
- // derived
733
- originalName: fileMeta.name || "unknown",
734
- size: fileMeta.size ?? 0,
735
- extension: extension2,
736
- mimeType: fileMeta.type || "unknown",
737
- type: classifyFileType(fileMeta.type, extension2),
738
- // ------------------------------------------------------------------------
739
- // relations
740
- // ------------------------------------------------------------------------
741
- // Folder
742
- folder: connectOne(folder),
743
- // ------------------------------------------------------------------------
744
- // translation
745
- // ------------------------------------------------------------------------
746
- translations: {
747
- create: translations.map((t) => ({
748
- ...t,
749
- name: t.name ?? fileMeta.name ?? "unknown"
750
- }))
751
- }
752
- },
753
- include: FILE_FULL_INCLUDE
754
- });
755
- return created;
756
- }
757
- async function update({
758
- file,
759
- // Original file (File)
760
- id,
761
- // meta
762
- fileMeta,
763
- // ------------------------------------
764
- // relations
765
- // ------------------------------------
766
- // Folder
767
- folder,
768
- // ------------------------------------
769
- // translation
770
- // ------------------------------------
771
- translations,
772
- // rest
773
- ...params
774
- }) {
775
- const extension2 = mimeToExtension(fileMeta.type);
776
- const updated = await prisma.file.update({
777
- where: { id: file.id },
778
- data: {
779
- ...params,
780
- // derived
781
- size: fileMeta.size ?? file.size,
782
- extension: fileMeta ? extension2 : file.extension,
783
- mimeType: fileMeta.type || file.mimeType,
784
- type: fileMeta.type ? classifyFileType(fileMeta.type, extension2) : file.type,
785
- // ------------------------------------------------------------------------
786
- // relations
787
- // ------------------------------------------------------------------------
788
- // Folder
789
- folder: updateOne(folder),
790
- // ------------------------------------------------------------------------
791
- // translation
792
- // ------------------------------------------------------------------------
793
- translations: {
794
- upsert: translations.map((t) => ({
795
- where: { fileId_locale: { fileId: id, locale: t.locale } },
796
- update: t,
797
- create: t
798
- }))
799
- }
800
- }
801
- });
802
- return updated;
803
- }
804
- async function softDelete({ id }) {
805
- const updated = await prisma.file.update({
806
- where: { id, isLocked: false },
807
- data: { deletedAt: /* @__PURE__ */ new Date() }
808
- });
809
- return updated;
810
- }
811
- async function softDeleteMany({ ids: ids2 }) {
812
- const { count } = await prisma.file.updateMany({
813
- where: { id: { in: ids2 }, isLocked: false },
814
- data: { deletedAt: /* @__PURE__ */ new Date() }
815
- });
816
- return count;
817
- }
818
- async function restoreMany({ ids: ids2 }) {
819
- const { count } = await prisma.file.updateMany({
820
- where: { id: { in: ids2 } },
821
- data: { deletedAt: null }
822
- });
823
- return count;
824
- }
825
- async function _delete({ id }) {
826
- const deleted = await prisma.file.delete({
827
- where: { id }
828
- });
829
- return deleted;
830
- }
831
- return {
832
- create,
833
- update,
834
- softDelete,
835
- softDeleteMany,
836
- restoreMany,
837
- delete: _delete
838
- };
839
- }
840
-
841
- // src/server/infrastructure/database/utils/build-file-usage.ts
842
- function buildFileUsage(isLocked) {
843
- const relations = [
844
- "adminAsAvatarImage",
845
- "postsAsCoverImage",
846
- "postsAsContentImage",
847
- "postsAsImages1",
848
- "postsAsImages2",
849
- "postsAsImage1",
850
- "postsAsImage2",
851
- "postsAsImage3",
852
- "postsAsImage4"
853
- ];
854
- if (isLocked === void 0 || isLocked === null) return {};
855
- const condition = relations.map((r) => ({
856
- [r]: { [isLocked ? "some" : "none"]: {} }
857
- }));
858
- return isLocked ? { OR: condition } : { AND: condition };
859
- }
860
-
861
- // src/server/infrastructure/database/file/query/create-file-query-repository.ts
862
- function createFileQueryRepository(prisma) {
863
- async function findListCards({
864
- locale,
865
- // pagination
866
- page,
867
- pageSize,
868
- // search
869
- searchString,
870
- type,
871
- folderId,
872
- isLocked,
873
- isDeleted = false,
874
- fileIds
875
- }) {
876
- const where = {
877
- // search
878
- ...createSearch({
879
- ...searchString !== void 0 ? { searchString } : {},
880
- locale,
881
- translationFields: ["name"]
882
- }),
883
- // type
884
- ...type ? { type } : {},
885
- // state: isLocked
886
- ...buildFileUsage(isLocked),
887
- // state: deleteAt
888
- deletedAt: isDeleted ? { not: null } : null,
889
- // Find deleted files in trash page
890
- // relations: Folder
891
- ...folderId ? { folderId: folderId === ROOT_FOLDER_ID ? null : folderId } : {},
892
- // Specified ids
893
- ...fileIds ? { id: { in: fileIds } } : {}
894
- };
895
- const [items, total] = await prisma.$transaction([
896
- prisma.file.findMany({
897
- where,
898
- orderBy: [...ORDER_BY],
899
- include: FILE_FULL_INCLUDE,
900
- ...createPagination(page, pageSize)
901
- }),
902
- prisma.file.count({ where })
903
- ]);
904
- return { items, total };
905
- }
906
- async function findManyByIds({
907
- ids: ids2
908
- }) {
909
- return await prisma.file.findMany({
910
- where: { id: { in: ids2 } },
911
- include: FILE_FULL_INCLUDE
912
- });
913
- }
914
- async function findFull({ id }) {
915
- return await prisma.file.findUnique({
916
- where: { id },
917
- include: FILE_FULL_INCLUDE
918
- });
919
- }
920
- return {
921
- findListCards,
922
- findManyByIds,
923
- findFull
924
- };
925
- }
926
-
927
- // src/server/infrastructure/database/folder/command/create-folder-command-repository.ts
928
- function createFolderCommandRepository(prisma) {
929
- async function create({
930
- // ------------------------------------
931
- // relations
932
- // ------------------------------------
933
- // Folder
934
- parentFolder,
935
- // rest
936
- ...params
937
- }) {
938
- const created = await prisma.folder.create({
939
- data: {
940
- ...params,
941
- // ------------------------------------------------------------------------
942
- // relations
943
- // ------------------------------------------------------------------------
944
- // Folder
945
- parentFolder: connectOne(parentFolder)
946
- }
947
- });
948
- return created;
949
- }
950
- async function update({
951
- id,
952
- // ------------------------------------
953
- // relations
954
- // ------------------------------------
955
- // Folder
956
- parentFolder,
957
- // rest
958
- ...params
959
- }) {
960
- const updated = await prisma.folder.update({
961
- where: { id },
962
- data: {
963
- ...params,
964
- // ------------------------------------------------------------------------
965
- // relations
966
- // ------------------------------------------------------------------------
967
- // Folder
968
- parentFolder: updateOne(parentFolder)
969
- }
970
- });
971
- return updated;
972
- }
973
- async function _delete({ id }) {
974
- const deleted = await prisma.folder.delete({
975
- where: { id }
976
- });
977
- return deleted;
978
- }
979
- return {
980
- create,
981
- update,
982
- delete: _delete
983
- };
984
- }
985
-
986
- // src/server/infrastructure/database/folder/include.ts
987
- var FOLDER_FULL_INCLUDE = {
988
- // ---------------------------
989
- // relations: Folder
990
- // ---------------------------
991
- parentFolder: { include: { subFolders: true, files: true } },
992
- subFolders: true,
993
- // ---------------------------
994
- // relations: File
995
- // ---------------------------
996
- files: true
997
- };
998
-
999
- // src/server/infrastructure/database/folder/query/create-folder-query-repository.ts
1000
- function buildWhere2(params) {
1001
- if (params.id) return { id: params.id };
1002
- if (params.key) return { key: params.key };
1003
- return;
1004
- }
1005
- function createFolderQueryRepository(prisma) {
1006
- async function findListCards({
1007
- // search
1008
- searchString,
1009
- parentFolderId,
1010
- folderIds,
1011
- // pagination
1012
- page,
1013
- pageSize
1014
- }) {
1015
- const where = {
1016
- // search
1017
- ...searchString ? { name: { contains: searchString, mode: "insensitive" } } : {},
1018
- // relations: Folder
1019
- ...parentFolderId ? parentFolderId === ROOT_FOLDER_ID ? { parentFolderId: null } : { parentFolderId } : {},
1020
- // Specified ids
1021
- ...folderIds ? { id: { in: folderIds } } : {}
1022
- };
1023
- const [items, total] = await prisma.$transaction([
1024
- prisma.folder.findMany({
1025
- where,
1026
- orderBy: [...ORDER_BY],
1027
- include: FOLDER_FULL_INCLUDE,
1028
- ...createPagination(page, pageSize)
1029
- }),
1030
- prisma.folder.count({ where })
1031
- ]);
1032
- return { items, total };
1033
- }
1034
- async function findFull(params) {
1035
- const where = buildWhere2(params);
1036
- if (!where) return null;
1037
- return await prisma.folder.findUnique({
1038
- where,
1039
- include: FOLDER_FULL_INCLUDE
1040
- });
1041
- }
1042
- return {
1043
- findListCards,
1044
- findFull
1045
- };
1046
- }
1047
- function createPostCommandRepository(prisma) {
1048
- async function create({
1049
- slug,
1050
- // ------------------------------------
1051
- // relations
1052
- // ------------------------------------
1053
- // Admin
1054
- author,
1055
- // Post
1056
- topicId,
1057
- parents,
1058
- tags,
1059
- relatedPosts,
1060
- // File
1061
- coverImage,
1062
- contentImageIds,
1063
- // ------------------------------------
1064
- // --- custom fields
1065
- // ------------------------------------
1066
- // multi images
1067
- images1,
1068
- images2,
1069
- // single images
1070
- image1,
1071
- image2,
1072
- image3,
1073
- image4,
1074
- // ------------------------------------
1075
- // translation
1076
- // ------------------------------------
1077
- translations,
1078
- // rest
1079
- ...params
1080
- }) {
1081
- const id = ulid();
1082
- const created = await prisma.post.create({
1083
- data: {
1084
- ...params,
1085
- id,
1086
- // ------------------------------------------------------------------------
1087
- // states
1088
- // ------------------------------------------------------------------------
1089
- slug: slug?.trim() || id,
1090
- // Use id when slug is null or empty string
1091
- // ------------------------------------------------------------------------
1092
- // relations
1093
- // ------------------------------------------------------------------------
1094
- // Admin
1095
- author: connectOne(author),
1096
- // Post
1097
- topic: connectOne(topicId ? { id: topicId } : null),
1098
- parents: connectMany(parents),
1099
- tags: connectMany(tags),
1100
- relatedPosts: connectMany(relatedPosts),
1101
- // File
1102
- coverImage: connectOne(coverImage),
1103
- contentImages: connectMany(contentImageIds.map((id2) => ({ id: id2 }))),
1104
- // ------------------------------------------------------------------------
1105
- // --- custom fields
1106
- // ------------------------------------------------------------------------
1107
- // multi images
1108
- images1: connectMany(images1),
1109
- images2: connectMany(images2),
1110
- // single images
1111
- image1: connectOne(image1),
1112
- image2: connectOne(image2),
1113
- image3: connectOne(image3),
1114
- image4: connectOne(image4),
1115
- // ------------------------------------------------------------------------
1116
- // translation
1117
- // ------------------------------------------------------------------------
1118
- translations: {
1119
- create: translations.map((t) => ({
1120
- ...t,
1121
- externalLinks: t.externalLinks,
1122
- faq: t.faq,
1123
- toc: t.toc
1124
- }))
1125
- }
1126
- }
1127
- });
1128
- return created;
1129
- }
1130
- async function update({
1131
- id,
1132
- slug,
1133
- // ------------------------------------
1134
- // relations
1135
- // ------------------------------------
1136
- // Admin
1137
- author,
1138
- // Post
1139
- topicId,
1140
- parents,
1141
- tags,
1142
- relatedPosts,
1143
- // File
1144
- coverImage,
1145
- contentImageIds,
1146
- // ------------------------------------
1147
- // --- custom fields
1148
- // ------------------------------------
1149
- // multi images
1150
- images1,
1151
- images2,
1152
- // single images
1153
- image1,
1154
- image2,
1155
- image3,
1156
- image4,
1157
- // ------------------------------------
1158
- // translation
1159
- // ------------------------------------
1160
- translations,
1161
- // rest
1162
- ...params
1163
- }) {
1164
- const updated = await prisma.post.update({
1165
- where: { id },
1166
- data: {
1167
- ...params,
1168
- // ------------------------------------------------------------------------
1169
- // states
1170
- // ------------------------------------------------------------------------
1171
- slug: slug?.trim() || id,
1172
- // Use id when slug is null or empty string
1173
- // ------------------------------------------------------------------------
1174
- // relations
1175
- // ------------------------------------------------------------------------
1176
- // Admin
1177
- author: updateOne(author),
1178
- // Post
1179
- topic: updateOne(topicId ? { id: topicId } : null),
1180
- parents: updateMany(parents),
1181
- tags: updateMany(tags),
1182
- relatedPosts: updateMany(relatedPosts),
1183
- // File
1184
- coverImage: updateOne(coverImage),
1185
- contentImages: updateMany(contentImageIds.map((id2) => ({ id: id2 }))),
1186
- // ------------------------------------------------------------------------
1187
- // --- custom fields
1188
- // ------------------------------------------------------------------------
1189
- // multi images
1190
- images1: updateMany(images1),
1191
- images2: updateMany(images2),
1192
- // single images
1193
- image1: updateOne(image1),
1194
- image2: updateOne(image2),
1195
- image3: updateOne(image3),
1196
- image4: updateOne(image4),
1197
- // ------------------------------------------------------------------------
1198
- // translation
1199
- // ------------------------------------------------------------------------
1200
- translations: {
1201
- upsert: translations.map((t) => ({
1202
- where: { postId_locale: { postId: id, locale: t.locale } },
1203
- update: {
1204
- ...t,
1205
- externalLinks: t.externalLinks,
1206
- faq: t.faq,
1207
- toc: t.toc
1208
- },
1209
- create: {
1210
- ...t,
1211
- externalLinks: t.externalLinks,
1212
- faq: t.faq,
1213
- toc: t.toc
1214
- }
1215
- }))
1216
- }
1217
- }
1218
- });
1219
- return updated;
1220
- }
1221
- async function _delete({ id }) {
1222
- const deleted = await prisma.post.delete({
1223
- where: { id }
1224
- });
1225
- return deleted;
1226
- }
1227
- return {
1228
- create,
1229
- update,
1230
- delete: _delete
1231
- };
1232
- }
1233
-
1234
- // src/server/infrastructure/database/post/include.ts
1235
- var POST_LIST_CARD_INCLUDE = {
1236
- // ---------------------------
1237
- // relations: Post
1238
- // ---------------------------
1239
- topic: { include: { translations: true } },
1240
- // Use as post, category
1241
- postsInTopic: true,
1242
- // Use as topic
1243
- children: true,
1244
- // Use as category
1245
- taggedPosts: true,
1246
- // Use as tag
1247
- // ---------------------------
1248
- // relations: File
1249
- // ---------------------------
1250
- coverImage: true,
1251
- // ---------------------------
1252
- // translation
1253
- // ---------------------------
1254
- translations: true
1255
- };
1256
- var POST_FULL_INCLUDE = {
1257
- // ---------------------------
1258
- // relations: Admin
1259
- // ---------------------------
1260
- author: { include: { translations: true } },
1261
- // ---------------------------
1262
- // relations: Post
1263
- // ---------------------------
1264
- topic: { include: POST_LIST_CARD_INCLUDE },
1265
- postsInTopic: { include: POST_LIST_CARD_INCLUDE },
1266
- parents: { include: POST_LIST_CARD_INCLUDE },
1267
- children: { include: POST_LIST_CARD_INCLUDE },
1268
- tags: { include: POST_LIST_CARD_INCLUDE },
1269
- taggedPosts: { include: POST_LIST_CARD_INCLUDE },
1270
- relatedPosts: { include: POST_LIST_CARD_INCLUDE },
1271
- referencingPosts: { include: POST_LIST_CARD_INCLUDE },
1272
- // ---------------------------
1273
- // relations: File
1274
- // ---------------------------
1275
- coverImage: { include: { translations: true } },
1276
- // ---------------------------
1277
- // --- custom fields
1278
- // ---------------------------
1279
- images1: { include: { translations: true } },
1280
- images2: { include: { translations: true } },
1281
- image1: { include: { translations: true } },
1282
- image2: { include: { translations: true } },
1283
- image3: { include: { translations: true } },
1284
- image4: { include: { translations: true } },
1285
- // ---------------------------
1286
- // translation
1287
- // ---------------------------
1288
- translations: true
1289
- };
1290
-
1291
- // src/server/infrastructure/database/post/query/create-post-query-repository.ts
1292
- function buildWhere3(params) {
1293
- if (params.id) return { id: params.id };
1294
- if (params.type && params.slug)
1295
- return { type: params.type, slug: params.slug };
1296
- return;
1297
- }
1298
- function createPostQueryRepository(prisma) {
1299
- async function findListCards({
1300
- locale,
1301
- // search
1302
- searchString,
1303
- type,
1304
- topicId,
1305
- postIds,
1306
- isActive,
1307
- isIndexActive,
1308
- isSlugActive,
1309
- isFeatured,
1310
- isShownOnHome,
1311
- // pagination
1312
- page,
1313
- pageSize
1314
- }) {
1315
- const where = {
1316
- // search
1317
- ...createSearch({
1318
- ...searchString ? { searchString } : {},
1319
- locale,
1320
- rootFields: ["slug"],
1321
- translationFields: ["title", "summary", "description", "content"]
1322
- }),
1323
- // type
1324
- ...type ? { type } : {},
1325
- // states
1326
- ...isActive ? { isActive: true } : {},
1327
- ...isIndexActive ? { isIndexActive: true } : {},
1328
- ...isSlugActive ? { isSlugActive: true } : {},
1329
- ...isFeatured ? { isFeatured: true } : {},
1330
- ...isShownOnHome ? { isShownOnHome: true } : {},
1331
- // relations: Post
1332
- ...topicId ? { topicId } : {},
1333
- // Specified ids
1334
- ...postIds ? { id: { in: postIds } } : {}
1335
- };
1336
- const [items, total] = await prisma.$transaction([
1337
- prisma.post.findMany({
1338
- where,
1339
- orderBy: POST_ORDER_BY,
1340
- include: POST_LIST_CARD_INCLUDE,
1341
- ...createPagination(page, pageSize)
1342
- }),
1343
- prisma.post.count({ where })
1344
- ]);
1345
- return { items, total };
1346
- }
1347
- async function find(params) {
1348
- const where = buildWhere3(params);
1349
- if (!where) return null;
1350
- return prisma.post.findFirst({
1351
- where,
1352
- include: { translations: true }
1353
- });
1354
- }
1355
- async function findMany({
1356
- type,
1357
- topicId
1358
- }) {
1359
- return await prisma.post.findMany({
1360
- where: { type, ...topicId ? { topicId } : {} },
1361
- orderBy: POST_ORDER_BY,
1362
- include: { translations: true }
1363
- });
1364
- }
1365
- async function findFull(params) {
1366
- const where = buildWhere3(params);
1367
- if (!where) return null;
1368
- return prisma.post.findFirst({
1369
- where,
1370
- include: POST_FULL_INCLUDE
1371
- });
1372
- }
1373
- async function findWithSeoMetadata(params) {
1374
- const where = buildWhere3(params);
1375
- if (!where) return null;
1376
- return await prisma.post.findFirst({
1377
- where,
1378
- include: { translations: true, seoMetadatas: true }
1379
- });
1380
- }
1381
- return {
1382
- findListCards,
1383
- findMany,
1384
- find,
1385
- findFull,
1386
- findWithSeoMetadata
1387
- };
1388
- }
1389
-
1390
- // src/server/infrastructure/database/seo-metadata/command/create-seo-metadata-command-repository.ts
1391
- function createSeoMetadataCommandRepository(prisma) {
1392
- async function upsert({
1393
- // ------------------------------------
1394
- // relations
1395
- // ------------------------------------
1396
- // Post
1397
- postId,
1398
- // ------------------------------------
1399
- // translation
1400
- // ------------------------------------
1401
- translations
1402
- }) {
1403
- await prisma.post.update({
1404
- where: { id: postId },
1405
- data: {
1406
- seoMetadatas: {
1407
- upsert: translations.map((t) => ({
1408
- where: { postId_locale: { postId, locale: t.locale } },
1409
- update: t,
1410
- create: t
1411
- }))
1412
- }
1413
- }
1414
- });
1415
- }
1416
- return {
1417
- upsert
1418
- };
1419
- }
1420
-
1421
- export { ADMIN_ORDER_BY, ADMIN_ROLES, FILE_TYPES, ORDER_BY, POST_ORDER_BY, POST_TYPES, classifyFileType, createAdminCommandRepository, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenQueryRepository, createArgon2Service, createCookieService, createCryptoService, createFileCommandRepository, createFileQueryRepository, createFolderCommandRepository, createFolderQueryRepository, createJwtService, createPostCommandRepository, createPostQueryRepository, createSeoMetadataCommandRepository, formatFileSize, getMediaInfo, mimeToExtension };
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-KEQXXUK2.js';