@yimingliao/cms 0.0.1

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 ADDED
@@ -0,0 +1,1264 @@
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/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/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/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/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/admin/props.ts
310
+ var ADMIN_ROLES = {
311
+ SUPER_ADMIN: "SUPER_ADMIN"};
312
+
313
+ // src/database/admin/include.ts
314
+ var ADMIN_FULL_INCLUDE = {
315
+ // ---------------------------
316
+ // relations: AdminRefreshToken
317
+ // ---------------------------
318
+ adminRefreshTokens: true,
319
+ // ---------------------------
320
+ // relations: File
321
+ // ---------------------------
322
+ avatarImage: { include: { translations: true } },
323
+ // ---------------------------
324
+ // relations: Post
325
+ // ---------------------------
326
+ posts: true,
327
+ // ---------------------------
328
+ // translation
329
+ // ---------------------------
330
+ translations: true
331
+ };
332
+
333
+ // src/database/utils/create-search.ts
334
+ function buildContainsOr(fields, value) {
335
+ return fields.map((field) => ({
336
+ [field]: { contains: value, mode: "insensitive" }
337
+ }));
338
+ }
339
+ function buildTranslationSearch(locale, fields, value) {
340
+ return {
341
+ translations: {
342
+ some: { locale, OR: buildContainsOr(fields, value) }
343
+ }
344
+ };
345
+ }
346
+ function createSearch({
347
+ locale,
348
+ searchString,
349
+ rootFields = [],
350
+ translationFields = []
351
+ }) {
352
+ if (!searchString) return {};
353
+ const conditions = [];
354
+ if (rootFields.length > 0) {
355
+ conditions.push(...buildContainsOr(rootFields, searchString));
356
+ }
357
+ if (translationFields.length > 0 && locale) {
358
+ conditions.push(
359
+ buildTranslationSearch(locale, translationFields, searchString)
360
+ );
361
+ }
362
+ return conditions.length > 0 ? { OR: conditions } : {};
363
+ }
364
+
365
+ // src/database/utils/create-pagination.ts
366
+ function createPagination(page, pageSize) {
367
+ if (!page || !pageSize) return {};
368
+ return {
369
+ skip: (page - 1) * pageSize,
370
+ take: pageSize
371
+ };
372
+ }
373
+
374
+ // src/database/admin/query/create-admin-query-repository.ts
375
+ var OMIT_PASSWORD = { omit: { passwordHash: true } };
376
+ function buildWhere(params) {
377
+ if (params.id) return { id: params.id };
378
+ if (params.email) return { email: params.email };
379
+ return;
380
+ }
381
+ function createAdminQueryRepository(prisma) {
382
+ async function findListCards({
383
+ locale,
384
+ // search
385
+ searchString,
386
+ role,
387
+ adminIds,
388
+ // pagination
389
+ page,
390
+ pageSize
391
+ }) {
392
+ const where = {
393
+ // search
394
+ ...createSearch({
395
+ ...searchString !== void 0 ? { searchString } : {},
396
+ locale,
397
+ translationFields: ["name"]
398
+ }),
399
+ // role
400
+ ...role !== ADMIN_ROLES.SUPER_ADMIN ? { role: { notIn: [ADMIN_ROLES.SUPER_ADMIN] } } : {},
401
+ // Specified ids
402
+ ...adminIds?.length ? { id: { in: adminIds } } : {}
403
+ };
404
+ const [items, total] = await prisma.$transaction([
405
+ prisma.admin.findMany({
406
+ where,
407
+ orderBy: ADMIN_ORDER_BY,
408
+ include: ADMIN_FULL_INCLUDE,
409
+ ...createPagination(page, pageSize),
410
+ ...OMIT_PASSWORD
411
+ }),
412
+ prisma.admin.count({ where })
413
+ ]);
414
+ return { items, total };
415
+ }
416
+ async function find(params) {
417
+ const where = buildWhere(params);
418
+ if (!where) return null;
419
+ return prisma.admin.findUnique({
420
+ where,
421
+ ...OMIT_PASSWORD
422
+ });
423
+ }
424
+ async function findFull(params) {
425
+ const where = buildWhere(params);
426
+ if (!where) return null;
427
+ return await prisma.admin.findUnique({
428
+ where,
429
+ include: ADMIN_FULL_INCLUDE,
430
+ ...OMIT_PASSWORD
431
+ });
432
+ }
433
+ async function findWithPasswordHash(params) {
434
+ const where = buildWhere(params);
435
+ if (!where) return null;
436
+ return prisma.admin.findUnique({
437
+ where
438
+ });
439
+ }
440
+ return {
441
+ findListCards,
442
+ find,
443
+ findFull,
444
+ findWithPasswordHash
445
+ };
446
+ }
447
+
448
+ // src/database/admin-refresh-token/command/create-admin-refresh-token-command-repository.ts
449
+ function createAdminRefreshTokenCommandRepository(prisma) {
450
+ async function create({
451
+ adminId,
452
+ ...params
453
+ }) {
454
+ const created = await prisma.adminRefreshToken.create({
455
+ data: {
456
+ ...params,
457
+ admin: { connect: { id: adminId } },
458
+ deviceInfo: params.deviceInfo
459
+ }
460
+ });
461
+ return created;
462
+ }
463
+ async function _delete({ id, tokenHash }) {
464
+ const where = id ? { id } : tokenHash ? { tokenHash } : void 0;
465
+ if (where) {
466
+ await prisma.adminRefreshToken.delete({
467
+ where
468
+ });
469
+ }
470
+ }
471
+ async function deleteManyByExpired() {
472
+ const { count } = await prisma.adminRefreshToken.deleteMany({
473
+ where: { expiresAt: { lt: /* @__PURE__ */ new Date() } }
474
+ });
475
+ return count;
476
+ }
477
+ return {
478
+ create,
479
+ delete: _delete,
480
+ deleteManyByExpired
481
+ };
482
+ }
483
+
484
+ // src/database/admin-refresh-token/query/create-admin-refresh-token-query-repository.ts
485
+ function createAdminRefreshTokenQueryRepository(prisma) {
486
+ async function findManyByAdminId({
487
+ adminId
488
+ }) {
489
+ return await prisma.adminRefreshToken.findMany({
490
+ where: { adminId },
491
+ orderBy: [{ createdAt: "desc" }, { id: "asc" }]
492
+ });
493
+ }
494
+ async function findByToken({
495
+ tokenHash
496
+ }) {
497
+ return await prisma.adminRefreshToken.findUnique({
498
+ where: { tokenHash }
499
+ });
500
+ }
501
+ return {
502
+ findManyByAdminId,
503
+ findByToken
504
+ };
505
+ }
506
+
507
+ // src/database/file/include.ts
508
+ var FILE_FULL_INCLUDE = {
509
+ // ---------------------------
510
+ // relations: Folder
511
+ // ---------------------------
512
+ folder: true,
513
+ // ---------------------------
514
+ // relations: Admin
515
+ // ---------------------------
516
+ adminAsAvatarImage: true,
517
+ // ---------------------------
518
+ // relations: Post
519
+ // ---------------------------
520
+ postsAsCoverImage: true,
521
+ postsAsContentImage: true,
522
+ // ---------------------------
523
+ // --- custom fields
524
+ // ---------------------------
525
+ postsAsImages1: true,
526
+ postsAsImages2: true,
527
+ postsAsImage1: true,
528
+ postsAsImage2: true,
529
+ postsAsImage3: true,
530
+ postsAsImage4: true,
531
+ // ---------------------------
532
+ // translation
533
+ // ---------------------------
534
+ translations: true
535
+ };
536
+
537
+ // src/domain/file/props.ts
538
+ var FILE_TYPES = {
539
+ IMAGE: "IMAGE",
540
+ AUDIO: "AUDIO",
541
+ VIDEO: "VIDEO",
542
+ DOCUMENT: "DOCUMENT",
543
+ ARCHIVE: "ARCHIVE",
544
+ OTHER: "OTHER"
545
+ };
546
+
547
+ // src/domain/file/utils/get-file-type.ts
548
+ var getFileType = (mimeType, extension2) => {
549
+ if (!mimeType && extension2) {
550
+ mimeType = lookup(extension2) || "unknown";
551
+ }
552
+ if (!mimeType) {
553
+ return FILE_TYPES.OTHER;
554
+ }
555
+ if (mimeType.startsWith("image/")) {
556
+ return FILE_TYPES.IMAGE;
557
+ }
558
+ if (mimeType.startsWith("video/")) {
559
+ return FILE_TYPES.VIDEO;
560
+ }
561
+ if (mimeType.startsWith("audio/")) {
562
+ return FILE_TYPES.AUDIO;
563
+ }
564
+ if (mimeType.startsWith("text/") || mimeType.startsWith("application/")) {
565
+ return FILE_TYPES.DOCUMENT;
566
+ }
567
+ if (mimeType === "application/zip" || mimeType === "application/x-rar-compressed") {
568
+ return FILE_TYPES.ARCHIVE;
569
+ }
570
+ if (mimeType.startsWith("application/x-") || mimeType === "application/octet-stream") {
571
+ return FILE_TYPES.OTHER;
572
+ }
573
+ return FILE_TYPES.OTHER;
574
+ };
575
+ var getExtension = (fileType) => {
576
+ if (typeof fileType !== "string") {
577
+ return "unknown";
578
+ }
579
+ return (extension(fileType) || "unknown").toLowerCase();
580
+ };
581
+
582
+ // src/database/file/command/create-file-command-repository.ts
583
+ function createFileCommandRepository(prisma) {
584
+ async function create({
585
+ // meta
586
+ fileMeta,
587
+ // ------------------------------------
588
+ // relations
589
+ // ------------------------------------
590
+ // Folder
591
+ folder,
592
+ // ------------------------------------
593
+ // translation
594
+ // ------------------------------------
595
+ translations,
596
+ // rest
597
+ ...params
598
+ }) {
599
+ const extension2 = getExtension(fileMeta.type);
600
+ const created = await prisma.file.create({
601
+ data: {
602
+ ...params,
603
+ // derived
604
+ originalName: fileMeta.name || "unknown",
605
+ size: fileMeta.size ?? 0,
606
+ extension: extension2,
607
+ mimeType: fileMeta.type || "unknown",
608
+ type: getFileType(fileMeta.type, extension2),
609
+ // ------------------------------------------------------------------------
610
+ // relations
611
+ // ------------------------------------------------------------------------
612
+ // Folder
613
+ folder: connectOne(folder),
614
+ // ------------------------------------------------------------------------
615
+ // translation
616
+ // ------------------------------------------------------------------------
617
+ translations: {
618
+ create: translations.map((t) => ({
619
+ ...t,
620
+ name: t.name ?? fileMeta.name ?? "unknown"
621
+ }))
622
+ }
623
+ },
624
+ include: FILE_FULL_INCLUDE
625
+ });
626
+ return created;
627
+ }
628
+ async function update({
629
+ file,
630
+ // Original file (File)
631
+ id,
632
+ // meta
633
+ fileMeta,
634
+ // ------------------------------------
635
+ // relations
636
+ // ------------------------------------
637
+ // Folder
638
+ folder,
639
+ // ------------------------------------
640
+ // translation
641
+ // ------------------------------------
642
+ translations,
643
+ // rest
644
+ ...params
645
+ }) {
646
+ const extension2 = getExtension(fileMeta.type);
647
+ const updated = await prisma.file.update({
648
+ where: { id: file.id },
649
+ data: {
650
+ ...params,
651
+ // derived
652
+ size: fileMeta.size ?? file.size,
653
+ extension: fileMeta ? extension2 : file.extension,
654
+ mimeType: fileMeta.type || file.mimeType,
655
+ type: fileMeta.type ? getFileType(fileMeta.type, extension2) : file.type,
656
+ // ------------------------------------------------------------------------
657
+ // relations
658
+ // ------------------------------------------------------------------------
659
+ // Folder
660
+ folder: updateOne(folder),
661
+ // ------------------------------------------------------------------------
662
+ // translation
663
+ // ------------------------------------------------------------------------
664
+ translations: {
665
+ upsert: translations.map((t) => ({
666
+ where: { fileId_locale: { fileId: id, locale: t.locale } },
667
+ update: t,
668
+ create: t
669
+ }))
670
+ }
671
+ }
672
+ });
673
+ return updated;
674
+ }
675
+ async function softDelete({ id }) {
676
+ const updated = await prisma.file.update({
677
+ where: { id, isLocked: false },
678
+ data: { deletedAt: /* @__PURE__ */ new Date() }
679
+ });
680
+ return updated;
681
+ }
682
+ async function softDeleteMany({ ids: ids2 }) {
683
+ const { count } = await prisma.file.updateMany({
684
+ where: { id: { in: ids2 }, isLocked: false },
685
+ data: { deletedAt: /* @__PURE__ */ new Date() }
686
+ });
687
+ return count;
688
+ }
689
+ async function restoreMany({ ids: ids2 }) {
690
+ const { count } = await prisma.file.updateMany({
691
+ where: { id: { in: ids2 } },
692
+ data: { deletedAt: null }
693
+ });
694
+ return count;
695
+ }
696
+ async function _delete({ id }) {
697
+ const deleted = await prisma.file.delete({
698
+ where: { id }
699
+ });
700
+ return deleted;
701
+ }
702
+ return {
703
+ create,
704
+ update,
705
+ softDelete,
706
+ softDeleteMany,
707
+ restoreMany,
708
+ delete: _delete
709
+ };
710
+ }
711
+
712
+ // src/database/utils/build-file-usage.ts
713
+ function buildFileUsage(isLocked) {
714
+ const relations = [
715
+ "adminAsAvatarImage",
716
+ "postsAsCoverImage",
717
+ "postsAsContentImage",
718
+ "postsAsImages1",
719
+ "postsAsImages2",
720
+ "postsAsImage1",
721
+ "postsAsImage2",
722
+ "postsAsImage3",
723
+ "postsAsImage4"
724
+ ];
725
+ if (isLocked === void 0 || isLocked === null) return {};
726
+ const condition = relations.map((r) => ({
727
+ [r]: { [isLocked ? "some" : "none"]: {} }
728
+ }));
729
+ return isLocked ? { OR: condition } : { AND: condition };
730
+ }
731
+
732
+ // src/domain/folder/constants/root-folder.ts
733
+ var ROOT_FOLDER_ID = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
734
+
735
+ // src/database/file/query/create-file-query-repository.ts
736
+ function createFileQueryRepository(prisma) {
737
+ async function findListCards({
738
+ locale,
739
+ // pagination
740
+ page,
741
+ pageSize,
742
+ // search
743
+ searchString,
744
+ type,
745
+ folderId,
746
+ isLocked,
747
+ isDeleted = false,
748
+ fileIds
749
+ }) {
750
+ const where = {
751
+ // search
752
+ ...createSearch({
753
+ ...searchString !== void 0 ? { searchString } : {},
754
+ locale,
755
+ translationFields: ["name"]
756
+ }),
757
+ // type
758
+ ...type ? { type } : {},
759
+ // state: isLocked
760
+ ...buildFileUsage(isLocked),
761
+ // state: deleteAt
762
+ deletedAt: isDeleted ? { not: null } : null,
763
+ // Find deleted files in trash page
764
+ // relations: Folder
765
+ ...folderId ? { folderId: folderId === ROOT_FOLDER_ID ? null : folderId } : {},
766
+ // Specified ids
767
+ ...fileIds ? { id: { in: fileIds } } : {}
768
+ };
769
+ const [items, total] = await prisma.$transaction([
770
+ prisma.file.findMany({
771
+ where,
772
+ orderBy: [...ORDER_BY],
773
+ include: FILE_FULL_INCLUDE,
774
+ ...createPagination(page, pageSize)
775
+ }),
776
+ prisma.file.count({ where })
777
+ ]);
778
+ return { items, total };
779
+ }
780
+ async function findManyByIds({
781
+ ids: ids2
782
+ }) {
783
+ return await prisma.file.findMany({
784
+ where: { id: { in: ids2 } },
785
+ include: FILE_FULL_INCLUDE
786
+ });
787
+ }
788
+ async function findFull({ id }) {
789
+ return await prisma.file.findUnique({
790
+ where: { id },
791
+ include: FILE_FULL_INCLUDE
792
+ });
793
+ }
794
+ return {
795
+ findListCards,
796
+ findManyByIds,
797
+ findFull
798
+ };
799
+ }
800
+
801
+ // src/database/folder/command/create-folder-command-repository.ts
802
+ function createFolderCommandRepository(prisma) {
803
+ async function create({
804
+ // ------------------------------------
805
+ // relations
806
+ // ------------------------------------
807
+ // Folder
808
+ parentFolder,
809
+ // rest
810
+ ...params
811
+ }) {
812
+ const created = await prisma.folder.create({
813
+ data: {
814
+ ...params,
815
+ // ------------------------------------------------------------------------
816
+ // relations
817
+ // ------------------------------------------------------------------------
818
+ // Folder
819
+ parentFolder: connectOne(parentFolder)
820
+ }
821
+ });
822
+ return created;
823
+ }
824
+ async function update({
825
+ id,
826
+ // ------------------------------------
827
+ // relations
828
+ // ------------------------------------
829
+ // Folder
830
+ parentFolder,
831
+ // rest
832
+ ...params
833
+ }) {
834
+ const updated = await prisma.folder.update({
835
+ where: { id },
836
+ data: {
837
+ ...params,
838
+ // ------------------------------------------------------------------------
839
+ // relations
840
+ // ------------------------------------------------------------------------
841
+ // Folder
842
+ parentFolder: updateOne(parentFolder)
843
+ }
844
+ });
845
+ return updated;
846
+ }
847
+ async function _delete({ id }) {
848
+ const deleted = await prisma.folder.delete({
849
+ where: { id }
850
+ });
851
+ return deleted;
852
+ }
853
+ return {
854
+ create,
855
+ update,
856
+ delete: _delete
857
+ };
858
+ }
859
+
860
+ // src/database/folder/include.ts
861
+ var FOLDER_FULL_INCLUDE = {
862
+ // ---------------------------
863
+ // relations: Folder
864
+ // ---------------------------
865
+ parentFolder: { include: { subFolders: true, files: true } },
866
+ subFolders: true,
867
+ // ---------------------------
868
+ // relations: File
869
+ // ---------------------------
870
+ files: true
871
+ };
872
+
873
+ // src/database/folder/query/create-folder-query-repository.ts
874
+ function buildWhere2(params) {
875
+ if (params.id) return { id: params.id };
876
+ if (params.key) return { key: params.key };
877
+ return;
878
+ }
879
+ function createFolderQueryRepository(prisma) {
880
+ async function findListCards({
881
+ // search
882
+ searchString,
883
+ parentFolderId,
884
+ folderIds,
885
+ // pagination
886
+ page,
887
+ pageSize
888
+ }) {
889
+ const where = {
890
+ // search
891
+ ...searchString ? { name: { contains: searchString, mode: "insensitive" } } : {},
892
+ // relations: Folder
893
+ ...parentFolderId ? parentFolderId === ROOT_FOLDER_ID ? { parentFolderId: null } : { parentFolderId } : {},
894
+ // Specified ids
895
+ ...folderIds ? { id: { in: folderIds } } : {}
896
+ };
897
+ const [items, total] = await prisma.$transaction([
898
+ prisma.folder.findMany({
899
+ where,
900
+ orderBy: [...ORDER_BY],
901
+ include: FOLDER_FULL_INCLUDE,
902
+ ...createPagination(page, pageSize)
903
+ }),
904
+ prisma.folder.count({ where })
905
+ ]);
906
+ return { items, total };
907
+ }
908
+ async function findFull(params) {
909
+ const where = buildWhere2(params);
910
+ if (!where) return null;
911
+ return await prisma.folder.findUnique({
912
+ where,
913
+ include: FOLDER_FULL_INCLUDE
914
+ });
915
+ }
916
+ return {
917
+ findListCards,
918
+ findFull
919
+ };
920
+ }
921
+ function createPostCommandRepository(prisma) {
922
+ async function create({
923
+ slug,
924
+ // ------------------------------------
925
+ // relations
926
+ // ------------------------------------
927
+ // Admin
928
+ author,
929
+ // Post
930
+ topicId,
931
+ parents,
932
+ tags,
933
+ relatedPosts,
934
+ // File
935
+ coverImage,
936
+ contentImageIds,
937
+ // ------------------------------------
938
+ // --- custom fields
939
+ // ------------------------------------
940
+ // multi images
941
+ images1,
942
+ images2,
943
+ // single images
944
+ image1,
945
+ image2,
946
+ image3,
947
+ image4,
948
+ // ------------------------------------
949
+ // translation
950
+ // ------------------------------------
951
+ translations,
952
+ // rest
953
+ ...params
954
+ }) {
955
+ const id = ulid();
956
+ const created = await prisma.post.create({
957
+ data: {
958
+ ...params,
959
+ id,
960
+ // ------------------------------------------------------------------------
961
+ // states
962
+ // ------------------------------------------------------------------------
963
+ slug: slug?.trim() || id,
964
+ // Use id when slug is null or empty string
965
+ // ------------------------------------------------------------------------
966
+ // relations
967
+ // ------------------------------------------------------------------------
968
+ // Admin
969
+ author: connectOne(author),
970
+ // Post
971
+ topic: connectOne(topicId ? { id: topicId } : null),
972
+ parents: connectMany(parents),
973
+ tags: connectMany(tags),
974
+ relatedPosts: connectMany(relatedPosts),
975
+ // File
976
+ coverImage: connectOne(coverImage),
977
+ contentImages: connectMany(contentImageIds.map((id2) => ({ id: id2 }))),
978
+ // ------------------------------------------------------------------------
979
+ // --- custom fields
980
+ // ------------------------------------------------------------------------
981
+ // multi images
982
+ images1: connectMany(images1),
983
+ images2: connectMany(images2),
984
+ // single images
985
+ image1: connectOne(image1),
986
+ image2: connectOne(image2),
987
+ image3: connectOne(image3),
988
+ image4: connectOne(image4),
989
+ // ------------------------------------------------------------------------
990
+ // translation
991
+ // ------------------------------------------------------------------------
992
+ translations: {
993
+ create: translations.map((t) => ({
994
+ ...t,
995
+ externalLinks: t.externalLinks,
996
+ faq: t.faq,
997
+ toc: t.toc
998
+ }))
999
+ }
1000
+ }
1001
+ });
1002
+ return created;
1003
+ }
1004
+ async function update({
1005
+ id,
1006
+ slug,
1007
+ // ------------------------------------
1008
+ // relations
1009
+ // ------------------------------------
1010
+ // Admin
1011
+ author,
1012
+ // Post
1013
+ topicId,
1014
+ parents,
1015
+ tags,
1016
+ relatedPosts,
1017
+ // File
1018
+ coverImage,
1019
+ contentImageIds,
1020
+ // ------------------------------------
1021
+ // --- custom fields
1022
+ // ------------------------------------
1023
+ // multi images
1024
+ images1,
1025
+ images2,
1026
+ // single images
1027
+ image1,
1028
+ image2,
1029
+ image3,
1030
+ image4,
1031
+ // ------------------------------------
1032
+ // translation
1033
+ // ------------------------------------
1034
+ translations,
1035
+ // rest
1036
+ ...params
1037
+ }) {
1038
+ const updated = await prisma.post.update({
1039
+ where: { id },
1040
+ data: {
1041
+ ...params,
1042
+ // ------------------------------------------------------------------------
1043
+ // states
1044
+ // ------------------------------------------------------------------------
1045
+ slug: slug?.trim() || id,
1046
+ // Use id when slug is null or empty string
1047
+ // ------------------------------------------------------------------------
1048
+ // relations
1049
+ // ------------------------------------------------------------------------
1050
+ // Admin
1051
+ author: updateOne(author),
1052
+ // Post
1053
+ topic: updateOne(topicId ? { id: topicId } : null),
1054
+ parents: updateMany(parents),
1055
+ tags: updateMany(tags),
1056
+ relatedPosts: updateMany(relatedPosts),
1057
+ // File
1058
+ coverImage: updateOne(coverImage),
1059
+ contentImages: updateMany(contentImageIds.map((id2) => ({ id: id2 }))),
1060
+ // ------------------------------------------------------------------------
1061
+ // --- custom fields
1062
+ // ------------------------------------------------------------------------
1063
+ // multi images
1064
+ images1: updateMany(images1),
1065
+ images2: updateMany(images2),
1066
+ // single images
1067
+ image1: updateOne(image1),
1068
+ image2: updateOne(image2),
1069
+ image3: updateOne(image3),
1070
+ image4: updateOne(image4),
1071
+ // ------------------------------------------------------------------------
1072
+ // translation
1073
+ // ------------------------------------------------------------------------
1074
+ translations: {
1075
+ upsert: translations.map((t) => ({
1076
+ where: { postId_locale: { postId: id, locale: t.locale } },
1077
+ update: {
1078
+ ...t,
1079
+ externalLinks: t.externalLinks,
1080
+ faq: t.faq,
1081
+ toc: t.toc
1082
+ },
1083
+ create: {
1084
+ ...t,
1085
+ externalLinks: t.externalLinks,
1086
+ faq: t.faq,
1087
+ toc: t.toc
1088
+ }
1089
+ }))
1090
+ }
1091
+ }
1092
+ });
1093
+ return updated;
1094
+ }
1095
+ async function _delete({ id }) {
1096
+ const deleted = await prisma.post.delete({
1097
+ where: { id }
1098
+ });
1099
+ return deleted;
1100
+ }
1101
+ return {
1102
+ create,
1103
+ update,
1104
+ delete: _delete
1105
+ };
1106
+ }
1107
+
1108
+ // src/database/post/include.ts
1109
+ var POST_LIST_CARD_INCLUDE = {
1110
+ // ---------------------------
1111
+ // relations: Post
1112
+ // ---------------------------
1113
+ topic: { include: { translations: true } },
1114
+ // Use as post, category
1115
+ postsInTopic: true,
1116
+ // Use as topic
1117
+ children: true,
1118
+ // Use as category
1119
+ taggedPosts: true,
1120
+ // Use as tag
1121
+ // ---------------------------
1122
+ // relations: File
1123
+ // ---------------------------
1124
+ coverImage: true,
1125
+ // ---------------------------
1126
+ // translation
1127
+ // ---------------------------
1128
+ translations: true
1129
+ };
1130
+ var POST_FULL_INCLUDE = {
1131
+ // ---------------------------
1132
+ // relations: Admin
1133
+ // ---------------------------
1134
+ author: { include: { translations: true } },
1135
+ // ---------------------------
1136
+ // relations: Post
1137
+ // ---------------------------
1138
+ topic: { include: POST_LIST_CARD_INCLUDE },
1139
+ postsInTopic: { include: POST_LIST_CARD_INCLUDE },
1140
+ parents: { include: POST_LIST_CARD_INCLUDE },
1141
+ children: { include: POST_LIST_CARD_INCLUDE },
1142
+ tags: { include: POST_LIST_CARD_INCLUDE },
1143
+ taggedPosts: { include: POST_LIST_CARD_INCLUDE },
1144
+ relatedPosts: { include: POST_LIST_CARD_INCLUDE },
1145
+ referencingPosts: { include: POST_LIST_CARD_INCLUDE },
1146
+ // ---------------------------
1147
+ // relations: File
1148
+ // ---------------------------
1149
+ coverImage: { include: { translations: true } },
1150
+ // ---------------------------
1151
+ // --- custom fields
1152
+ // ---------------------------
1153
+ images1: { include: { translations: true } },
1154
+ images2: { include: { translations: true } },
1155
+ image1: { include: { translations: true } },
1156
+ image2: { include: { translations: true } },
1157
+ image3: { include: { translations: true } },
1158
+ image4: { include: { translations: true } },
1159
+ // ---------------------------
1160
+ // translation
1161
+ // ---------------------------
1162
+ translations: true
1163
+ };
1164
+
1165
+ // src/database/post/query/create-post-query-repository.ts
1166
+ function buildWhere3(params) {
1167
+ if (params.id) return { id: params.id };
1168
+ if (params.type && params.slug)
1169
+ return { type: params.type, slug: params.slug };
1170
+ return;
1171
+ }
1172
+ function createPostQueryRepository(prisma) {
1173
+ async function findListCards({
1174
+ locale,
1175
+ // search
1176
+ searchString,
1177
+ type,
1178
+ topicId,
1179
+ postIds,
1180
+ isActive,
1181
+ isIndexActive,
1182
+ isSlugActive,
1183
+ isFeatured,
1184
+ isShownOnHome,
1185
+ // pagination
1186
+ page,
1187
+ pageSize
1188
+ }) {
1189
+ const where = {
1190
+ // search
1191
+ ...createSearch({
1192
+ ...searchString ? { searchString } : {},
1193
+ locale,
1194
+ rootFields: ["slug"],
1195
+ translationFields: ["title", "summary", "description", "content"]
1196
+ }),
1197
+ // type
1198
+ ...type ? { type } : {},
1199
+ // states
1200
+ ...isActive ? { isActive: true } : {},
1201
+ ...isIndexActive ? { isIndexActive: true } : {},
1202
+ ...isSlugActive ? { isSlugActive: true } : {},
1203
+ ...isFeatured ? { isFeatured: true } : {},
1204
+ ...isShownOnHome ? { isShownOnHome: true } : {},
1205
+ // relations: Post
1206
+ ...topicId ? { topicId } : {},
1207
+ // Specified ids
1208
+ ...postIds ? { id: { in: postIds } } : {}
1209
+ };
1210
+ const [items, total] = await prisma.$transaction([
1211
+ prisma.post.findMany({
1212
+ where,
1213
+ orderBy: POST_ORDER_BY,
1214
+ include: POST_LIST_CARD_INCLUDE,
1215
+ ...createPagination(page, pageSize)
1216
+ }),
1217
+ prisma.post.count({ where })
1218
+ ]);
1219
+ return { items, total };
1220
+ }
1221
+ async function find(params) {
1222
+ const where = buildWhere3(params);
1223
+ if (!where) return null;
1224
+ return prisma.post.findFirst({
1225
+ where,
1226
+ include: { translations: true }
1227
+ });
1228
+ }
1229
+ async function findMany({
1230
+ type,
1231
+ topicId
1232
+ }) {
1233
+ return await prisma.post.findMany({
1234
+ where: { type, ...topicId ? { topicId } : {} },
1235
+ orderBy: POST_ORDER_BY,
1236
+ include: { translations: true }
1237
+ });
1238
+ }
1239
+ async function findFull(params) {
1240
+ const where = buildWhere3(params);
1241
+ if (!where) return null;
1242
+ return prisma.post.findFirst({
1243
+ where,
1244
+ include: POST_FULL_INCLUDE
1245
+ });
1246
+ }
1247
+ async function findWithSeoMetadata(params) {
1248
+ const where = buildWhere3(params);
1249
+ if (!where) return null;
1250
+ return await prisma.post.findFirst({
1251
+ where,
1252
+ include: { translations: true, seoMetadatas: true }
1253
+ });
1254
+ }
1255
+ return {
1256
+ findListCards,
1257
+ findMany,
1258
+ find,
1259
+ findFull,
1260
+ findWithSeoMetadata
1261
+ };
1262
+ }
1263
+
1264
+ export { createAdminCommandRepository, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenQueryRepository, createArgon2Service, createCookieService, createCryptoService, createFileCommandRepository, createFileQueryRepository, createFolderCommandRepository, createFolderQueryRepository, createJwtService, createPostCommandRepository, createPostQueryRepository };