@yimingliao/cms 0.0.8 → 0.0.10

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,1447 +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
- var ROOT_FOLDER_NAME = "ROOT";
338
- var ROOT_FOLDER = {
339
- id: ROOT_FOLDER_ID,
340
- // core
341
- name: ROOT_FOLDER_NAME,
342
- key: "",
343
- // states
344
- isLocked: true,
345
- // ---------------------------
346
- // relations: Folder
347
- // ---------------------------
348
- parentFolder: null,
349
- parentFolderId: null,
350
- subFolders: [],
351
- // ---------------------------
352
- // relations: Folder
353
- // ---------------------------
354
- files: [],
355
- // ---------------------------
356
- // timestamps
357
- // ---------------------------
358
- createdAt: "",
359
- updatedAt: ""
360
- };
361
- var SIMPLE_UPLOAD_FOLDER_NAME = "simple-upload";
362
- var SIMPLE_UPLOAD_FOLDER_KEY = `${SIMPLE_UPLOAD_FOLDER_NAME}`;
363
-
364
- // src/server/infrastructure/database/admin/include.ts
365
- var ADMIN_FULL_INCLUDE = {
366
- // ---------------------------
367
- // relations: AdminRefreshToken
368
- // ---------------------------
369
- adminRefreshTokens: true,
370
- // ---------------------------
371
- // relations: File
372
- // ---------------------------
373
- avatarImage: { include: { translations: true } },
374
- // ---------------------------
375
- // relations: Post
376
- // ---------------------------
377
- posts: true,
378
- // ---------------------------
379
- // translation
380
- // ---------------------------
381
- translations: true
382
- };
383
-
384
- // src/server/infrastructure/database/utils/create-search.ts
385
- function buildContainsOr(fields, value) {
386
- return fields.map((field) => ({
387
- [field]: { contains: value, mode: "insensitive" }
388
- }));
389
- }
390
- function buildTranslationSearch(locale, fields, value) {
391
- return {
392
- translations: {
393
- some: { locale, OR: buildContainsOr(fields, value) }
394
- }
395
- };
396
- }
397
- function createSearch({
398
- locale,
399
- searchString,
400
- rootFields = [],
401
- translationFields = []
402
- }) {
403
- if (!searchString) return {};
404
- const conditions = [];
405
- if (rootFields.length > 0) {
406
- conditions.push(...buildContainsOr(rootFields, searchString));
407
- }
408
- if (translationFields.length > 0 && locale) {
409
- conditions.push(
410
- buildTranslationSearch(locale, translationFields, searchString)
411
- );
412
- }
413
- return conditions.length > 0 ? { OR: conditions } : {};
414
- }
415
-
416
- // src/server/infrastructure/database/utils/create-pagination.ts
417
- function createPagination(page, pageSize) {
418
- if (!page || !pageSize) return {};
419
- return {
420
- skip: (page - 1) * pageSize,
421
- take: pageSize
422
- };
423
- }
424
-
425
- // src/server/infrastructure/database/admin/query/create-admin-query-repository.ts
426
- var OMIT_PASSWORD = { omit: { passwordHash: true } };
427
- function buildWhere(params) {
428
- if (params.id) return { id: params.id };
429
- if (params.email) return { email: params.email };
430
- return;
431
- }
432
- function createAdminQueryRepository(prisma) {
433
- async function findListCards({
434
- locale,
435
- // search
436
- searchString,
437
- role,
438
- adminIds,
439
- // pagination
440
- page,
441
- pageSize
442
- }) {
443
- const where = {
444
- // search
445
- ...createSearch({
446
- ...searchString !== void 0 ? { searchString } : {},
447
- locale,
448
- translationFields: ["name"]
449
- }),
450
- // role
451
- ...role !== ADMIN_ROLES.SUPER_ADMIN ? { role: { notIn: [ADMIN_ROLES.SUPER_ADMIN] } } : {},
452
- // Specified ids
453
- ...adminIds?.length ? { id: { in: adminIds } } : {}
454
- };
455
- const [items, total] = await prisma.$transaction([
456
- prisma.admin.findMany({
457
- where,
458
- orderBy: ADMIN_ORDER_BY,
459
- include: ADMIN_FULL_INCLUDE,
460
- ...createPagination(page, pageSize),
461
- ...OMIT_PASSWORD
462
- }),
463
- prisma.admin.count({ where })
464
- ]);
465
- return { items, total };
466
- }
467
- async function find(params) {
468
- const where = buildWhere(params);
469
- if (!where) return null;
470
- return prisma.admin.findUnique({
471
- where,
472
- ...OMIT_PASSWORD
473
- });
474
- }
475
- async function findFull(params) {
476
- const where = buildWhere(params);
477
- if (!where) return null;
478
- return await prisma.admin.findUnique({
479
- where,
480
- include: ADMIN_FULL_INCLUDE,
481
- ...OMIT_PASSWORD
482
- });
483
- }
484
- async function findWithPasswordHash(params) {
485
- const where = buildWhere(params);
486
- if (!where) return null;
487
- return prisma.admin.findUnique({
488
- where
489
- });
490
- }
491
- return {
492
- findListCards,
493
- find,
494
- findFull,
495
- findWithPasswordHash
496
- };
497
- }
498
-
499
- // src/server/infrastructure/database/admin-refresh-token/command/create-admin-refresh-token-command-repository.ts
500
- function createAdminRefreshTokenCommandRepository(prisma) {
501
- async function create({
502
- adminId,
503
- ...params
504
- }) {
505
- const created = await prisma.adminRefreshToken.create({
506
- data: {
507
- ...params,
508
- admin: { connect: { id: adminId } },
509
- deviceInfo: params.deviceInfo
510
- }
511
- });
512
- return created;
513
- }
514
- async function _delete({ id, tokenHash }) {
515
- const where = id ? { id } : tokenHash ? { tokenHash } : void 0;
516
- if (where) {
517
- await prisma.adminRefreshToken.delete({
518
- where
519
- });
520
- }
521
- }
522
- async function deleteManyByExpired() {
523
- const { count } = await prisma.adminRefreshToken.deleteMany({
524
- where: { expiresAt: { lt: /* @__PURE__ */ new Date() } }
525
- });
526
- return count;
527
- }
528
- return {
529
- create,
530
- delete: _delete,
531
- deleteManyByExpired
532
- };
533
- }
534
-
535
- // src/server/infrastructure/database/admin-refresh-token/query/create-admin-refresh-token-query-repository.ts
536
- function createAdminRefreshTokenQueryRepository(prisma) {
537
- async function findManyByAdminId({
538
- adminId
539
- }) {
540
- return await prisma.adminRefreshToken.findMany({
541
- where: { adminId },
542
- orderBy: [{ createdAt: "desc" }, { id: "asc" }]
543
- });
544
- }
545
- async function findByToken({
546
- tokenHash
547
- }) {
548
- return await prisma.adminRefreshToken.findUnique({
549
- where: { tokenHash }
550
- });
551
- }
552
- return {
553
- findManyByAdminId,
554
- findByToken
555
- };
556
- }
557
-
558
- // src/server/infrastructure/database/file/include.ts
559
- var FILE_FULL_INCLUDE = {
560
- // ---------------------------
561
- // relations: Folder
562
- // ---------------------------
563
- folder: true,
564
- // ---------------------------
565
- // relations: Admin
566
- // ---------------------------
567
- adminAsAvatarImage: true,
568
- // ---------------------------
569
- // relations: Post
570
- // ---------------------------
571
- postsAsCoverImage: true,
572
- postsAsContentImage: true,
573
- // ---------------------------
574
- // --- custom fields
575
- // ---------------------------
576
- postsAsImages1: true,
577
- postsAsImages2: true,
578
- postsAsImage1: true,
579
- postsAsImage2: true,
580
- postsAsImage3: true,
581
- postsAsImage4: true,
582
- // ---------------------------
583
- // translation
584
- // ---------------------------
585
- translations: true
586
- };
587
- var mimeToExtension = (mimeType) => {
588
- if (!mimeType) return "unknown";
589
- return (extension(mimeType) || "unknown").toLowerCase();
590
- };
591
- var ARCHIVE_MIME = /* @__PURE__ */ new Set([
592
- "application/zip",
593
- "application/x-rar-compressed",
594
- "application/x-7z-compressed",
595
- "application/gzip",
596
- "application/x-tar"
597
- ]);
598
- var classifyFileType = (mimeType, extension2) => {
599
- if (!mimeType && extension2) {
600
- mimeType = lookup(extension2) || void 0;
601
- }
602
- if (!mimeType) return FILE_TYPES.OTHER;
603
- if (mimeType.startsWith("image/")) return FILE_TYPES.IMAGE;
604
- if (mimeType.startsWith("video/")) return FILE_TYPES.VIDEO;
605
- if (mimeType.startsWith("audio/")) return FILE_TYPES.AUDIO;
606
- if (ARCHIVE_MIME.has(mimeType)) return FILE_TYPES.ARCHIVE;
607
- if (mimeType.startsWith("text/") || mimeType === "application/pdf" || mimeType === "application/json" || mimeType === "application/xml") {
608
- return FILE_TYPES.DOCUMENT;
609
- }
610
- if (mimeType.startsWith("application/")) return FILE_TYPES.DOCUMENT;
611
- return FILE_TYPES.OTHER;
612
- };
613
-
614
- // src/shared/blob-file/get-media-info/get-audio-info.ts
615
- function getAudioInfo(file) {
616
- return new Promise((resolve, reject) => {
617
- const audio = document.createElement("audio");
618
- const objectUrl = URL.createObjectURL(file);
619
- audio.preload = "metadata";
620
- const cleanup = () => {
621
- audio.removeEventListener("loadedmetadata", onLoadedMetadata);
622
- audio.removeEventListener("error", onError);
623
- URL.revokeObjectURL(objectUrl);
624
- };
625
- const onLoadedMetadata = () => {
626
- const duration = audio.duration;
627
- cleanup();
628
- resolve({ duration });
629
- };
630
- const onError = () => {
631
- cleanup();
632
- reject(new Error("Failed to load audio metadata."));
633
- };
634
- audio.addEventListener("loadedmetadata", onLoadedMetadata, { once: true });
635
- audio.addEventListener("error", onError, { once: true });
636
- audio.src = objectUrl;
637
- });
638
- }
639
-
640
- // src/shared/blob-file/get-media-info/get-image-info.ts
641
- function getImageInfo(file) {
642
- return new Promise((resolve, reject) => {
643
- const img = new Image();
644
- const objectUrl = URL.createObjectURL(file);
645
- const cleanup = () => {
646
- img.removeEventListener("load", onLoad);
647
- img.removeEventListener("error", onError);
648
- URL.revokeObjectURL(objectUrl);
649
- };
650
- const onLoad = () => {
651
- const width = img.naturalWidth;
652
- const height = img.naturalHeight;
653
- cleanup();
654
- resolve({ width, height });
655
- };
656
- const onError = () => {
657
- cleanup();
658
- reject(new Error("Failed to load image for size detection."));
659
- };
660
- img.addEventListener("load", onLoad, { once: true });
661
- img.addEventListener("error", onError, { once: true });
662
- img.src = objectUrl;
663
- });
664
- }
665
-
666
- // src/shared/blob-file/get-media-info/get-video-info.ts
667
- function getVideoInfo(file) {
668
- return new Promise((resolve, reject) => {
669
- const video = document.createElement("video");
670
- const objectUrl = URL.createObjectURL(file);
671
- video.preload = "metadata";
672
- const cleanup = () => {
673
- video.removeEventListener("loadedmetadata", onLoadedMetadata);
674
- video.removeEventListener("error", onError);
675
- video.src = "";
676
- URL.revokeObjectURL(objectUrl);
677
- };
678
- const onLoadedMetadata = () => {
679
- const info = {
680
- width: video.videoWidth,
681
- height: video.videoHeight,
682
- duration: video.duration
683
- };
684
- cleanup();
685
- resolve(info);
686
- };
687
- const onError = () => {
688
- cleanup();
689
- reject(new Error("Failed to load video metadata."));
690
- };
691
- video.addEventListener("loadedmetadata", onLoadedMetadata, { once: true });
692
- video.addEventListener("error", onError, { once: true });
693
- video.src = objectUrl;
694
- });
695
- }
696
-
697
- // src/shared/blob-file/get-media-info/get-media-info.ts
698
- var IMAGE_EXT = /\.(jpe?g|png|gif|webp|bmp|avif)$/i;
699
- var VIDEO_EXT = /\.(mp4|webm|mov|mkv)$/i;
700
- var AUDIO_EXT = /\.(mp3|wav|ogg|m4a)$/i;
701
- var getMediaInfo = async (file) => {
702
- const fallback = { width: null, height: null, duration: null };
703
- const mime = file.type || "";
704
- const name = file instanceof File ? file.name.toLowerCase() : "";
705
- try {
706
- if (mime.startsWith("image/") || IMAGE_EXT.test(name)) {
707
- const { width, height } = await getImageInfo(file);
708
- return { width, height, duration: null };
709
- }
710
- if (mime.startsWith("video/") || VIDEO_EXT.test(name)) {
711
- const { width, height, duration } = await getVideoInfo(file);
712
- return { width, height, duration };
713
- }
714
- if (mime.startsWith("audio/") || AUDIO_EXT.test(name)) {
715
- const { duration } = await getAudioInfo(file);
716
- return { width: null, height: null, duration };
717
- }
718
- return fallback;
719
- } catch {
720
- return fallback;
721
- }
722
- };
723
-
724
- // src/shared/blob-file/format-file-size.ts
725
- var formatFileSize = (size, decimals = 2) => {
726
- const units = ["B", "KB", "MB", "GB", "TB"];
727
- let value = size;
728
- let unitIndex = 0;
729
- while (value >= 1024 && unitIndex < units.length - 1) {
730
- value /= 1024;
731
- unitIndex++;
732
- }
733
- const display = unitIndex === 0 ? value.toString() : value.toFixed(decimals);
734
- return `${display} ${units[unitIndex]}`;
735
- };
736
-
737
- // src/server/infrastructure/database/file/command/create-file-command-repository.ts
738
- function createFileCommandRepository(prisma) {
739
- async function create({
740
- // meta
741
- fileMeta,
742
- // ------------------------------------
743
- // relations
744
- // ------------------------------------
745
- // Folder
746
- folder,
747
- // ------------------------------------
748
- // translation
749
- // ------------------------------------
750
- translations,
751
- // rest
752
- ...params
753
- }) {
754
- const extension2 = mimeToExtension(fileMeta.type);
755
- const created = await prisma.file.create({
756
- data: {
757
- ...params,
758
- // derived
759
- originalName: fileMeta.name || "unknown",
760
- size: fileMeta.size ?? 0,
761
- extension: extension2,
762
- mimeType: fileMeta.type || "unknown",
763
- type: classifyFileType(fileMeta.type, extension2),
764
- // ------------------------------------------------------------------------
765
- // relations
766
- // ------------------------------------------------------------------------
767
- // Folder
768
- folder: connectOne(folder),
769
- // ------------------------------------------------------------------------
770
- // translation
771
- // ------------------------------------------------------------------------
772
- translations: {
773
- create: translations.map((t) => ({
774
- ...t,
775
- name: t.name ?? fileMeta.name ?? "unknown"
776
- }))
777
- }
778
- },
779
- include: FILE_FULL_INCLUDE
780
- });
781
- return created;
782
- }
783
- async function update({
784
- file,
785
- // Original file (File)
786
- id,
787
- // meta
788
- fileMeta,
789
- // ------------------------------------
790
- // relations
791
- // ------------------------------------
792
- // Folder
793
- folder,
794
- // ------------------------------------
795
- // translation
796
- // ------------------------------------
797
- translations,
798
- // rest
799
- ...params
800
- }) {
801
- const extension2 = mimeToExtension(fileMeta.type);
802
- const updated = await prisma.file.update({
803
- where: { id: file.id },
804
- data: {
805
- ...params,
806
- // derived
807
- size: fileMeta.size ?? file.size,
808
- extension: fileMeta ? extension2 : file.extension,
809
- mimeType: fileMeta.type || file.mimeType,
810
- type: fileMeta.type ? classifyFileType(fileMeta.type, extension2) : file.type,
811
- // ------------------------------------------------------------------------
812
- // relations
813
- // ------------------------------------------------------------------------
814
- // Folder
815
- folder: updateOne(folder),
816
- // ------------------------------------------------------------------------
817
- // translation
818
- // ------------------------------------------------------------------------
819
- translations: {
820
- upsert: translations.map((t) => ({
821
- where: { fileId_locale: { fileId: id, locale: t.locale } },
822
- update: t,
823
- create: t
824
- }))
825
- }
826
- }
827
- });
828
- return updated;
829
- }
830
- async function softDelete({ id }) {
831
- const updated = await prisma.file.update({
832
- where: { id, isLocked: false },
833
- data: { deletedAt: /* @__PURE__ */ new Date() }
834
- });
835
- return updated;
836
- }
837
- async function softDeleteMany({ ids: ids2 }) {
838
- const { count } = await prisma.file.updateMany({
839
- where: { id: { in: ids2 }, isLocked: false },
840
- data: { deletedAt: /* @__PURE__ */ new Date() }
841
- });
842
- return count;
843
- }
844
- async function restoreMany({ ids: ids2 }) {
845
- const { count } = await prisma.file.updateMany({
846
- where: { id: { in: ids2 } },
847
- data: { deletedAt: null }
848
- });
849
- return count;
850
- }
851
- async function _delete({ id }) {
852
- const deleted = await prisma.file.delete({
853
- where: { id }
854
- });
855
- return deleted;
856
- }
857
- return {
858
- create,
859
- update,
860
- softDelete,
861
- softDeleteMany,
862
- restoreMany,
863
- delete: _delete
864
- };
865
- }
866
-
867
- // src/server/infrastructure/database/utils/build-file-usage.ts
868
- function buildFileUsage(isLocked) {
869
- const relations = [
870
- "adminAsAvatarImage",
871
- "postsAsCoverImage",
872
- "postsAsContentImage",
873
- "postsAsImages1",
874
- "postsAsImages2",
875
- "postsAsImage1",
876
- "postsAsImage2",
877
- "postsAsImage3",
878
- "postsAsImage4"
879
- ];
880
- if (isLocked === void 0 || isLocked === null) return {};
881
- const condition = relations.map((r) => ({
882
- [r]: { [isLocked ? "some" : "none"]: {} }
883
- }));
884
- return isLocked ? { OR: condition } : { AND: condition };
885
- }
886
-
887
- // src/server/infrastructure/database/file/query/create-file-query-repository.ts
888
- function createFileQueryRepository(prisma) {
889
- async function findListCards({
890
- locale,
891
- // pagination
892
- page,
893
- pageSize,
894
- // search
895
- searchString,
896
- type,
897
- folderId,
898
- isLocked,
899
- isDeleted = false,
900
- fileIds
901
- }) {
902
- const where = {
903
- // search
904
- ...createSearch({
905
- ...searchString !== void 0 ? { searchString } : {},
906
- locale,
907
- translationFields: ["name"]
908
- }),
909
- // type
910
- ...type ? { type } : {},
911
- // state: isLocked
912
- ...buildFileUsage(isLocked),
913
- // state: deleteAt
914
- deletedAt: isDeleted ? { not: null } : null,
915
- // Find deleted files in trash page
916
- // relations: Folder
917
- ...folderId ? { folderId: folderId === ROOT_FOLDER_ID ? null : folderId } : {},
918
- // Specified ids
919
- ...fileIds ? { id: { in: fileIds } } : {}
920
- };
921
- const [items, total] = await prisma.$transaction([
922
- prisma.file.findMany({
923
- where,
924
- orderBy: [...ORDER_BY],
925
- include: FILE_FULL_INCLUDE,
926
- ...createPagination(page, pageSize)
927
- }),
928
- prisma.file.count({ where })
929
- ]);
930
- return { items, total };
931
- }
932
- async function findManyByIds({
933
- ids: ids2
934
- }) {
935
- return await prisma.file.findMany({
936
- where: { id: { in: ids2 } },
937
- include: FILE_FULL_INCLUDE
938
- });
939
- }
940
- async function findFull({ id }) {
941
- return await prisma.file.findUnique({
942
- where: { id },
943
- include: FILE_FULL_INCLUDE
944
- });
945
- }
946
- return {
947
- findListCards,
948
- findManyByIds,
949
- findFull
950
- };
951
- }
952
-
953
- // src/server/infrastructure/database/folder/command/create-folder-command-repository.ts
954
- function createFolderCommandRepository(prisma) {
955
- async function create({
956
- // ------------------------------------
957
- // relations
958
- // ------------------------------------
959
- // Folder
960
- parentFolder,
961
- // rest
962
- ...params
963
- }) {
964
- const created = await prisma.folder.create({
965
- data: {
966
- ...params,
967
- // ------------------------------------------------------------------------
968
- // relations
969
- // ------------------------------------------------------------------------
970
- // Folder
971
- parentFolder: connectOne(parentFolder)
972
- }
973
- });
974
- return created;
975
- }
976
- async function update({
977
- id,
978
- // ------------------------------------
979
- // relations
980
- // ------------------------------------
981
- // Folder
982
- parentFolder,
983
- // rest
984
- ...params
985
- }) {
986
- const updated = await prisma.folder.update({
987
- where: { id },
988
- data: {
989
- ...params,
990
- // ------------------------------------------------------------------------
991
- // relations
992
- // ------------------------------------------------------------------------
993
- // Folder
994
- parentFolder: updateOne(parentFolder)
995
- }
996
- });
997
- return updated;
998
- }
999
- async function _delete({ id }) {
1000
- const deleted = await prisma.folder.delete({
1001
- where: { id }
1002
- });
1003
- return deleted;
1004
- }
1005
- return {
1006
- create,
1007
- update,
1008
- delete: _delete
1009
- };
1010
- }
1011
-
1012
- // src/server/infrastructure/database/folder/include.ts
1013
- var FOLDER_FULL_INCLUDE = {
1014
- // ---------------------------
1015
- // relations: Folder
1016
- // ---------------------------
1017
- parentFolder: { include: { subFolders: true, files: true } },
1018
- subFolders: true,
1019
- // ---------------------------
1020
- // relations: File
1021
- // ---------------------------
1022
- files: true
1023
- };
1024
-
1025
- // src/server/infrastructure/database/folder/query/create-folder-query-repository.ts
1026
- function buildWhere2(params) {
1027
- if (params.id) return { id: params.id };
1028
- if (params.key) return { key: params.key };
1029
- return;
1030
- }
1031
- function createFolderQueryRepository(prisma) {
1032
- async function findListCards({
1033
- // search
1034
- searchString,
1035
- parentFolderId,
1036
- folderIds,
1037
- // pagination
1038
- page,
1039
- pageSize
1040
- }) {
1041
- const where = {
1042
- // search
1043
- ...searchString ? { name: { contains: searchString, mode: "insensitive" } } : {},
1044
- // relations: Folder
1045
- ...parentFolderId ? parentFolderId === ROOT_FOLDER_ID ? { parentFolderId: null } : { parentFolderId } : {},
1046
- // Specified ids
1047
- ...folderIds ? { id: { in: folderIds } } : {}
1048
- };
1049
- const [items, total] = await prisma.$transaction([
1050
- prisma.folder.findMany({
1051
- where,
1052
- orderBy: [...ORDER_BY],
1053
- include: FOLDER_FULL_INCLUDE,
1054
- ...createPagination(page, pageSize)
1055
- }),
1056
- prisma.folder.count({ where })
1057
- ]);
1058
- return { items, total };
1059
- }
1060
- async function findFull(params) {
1061
- const where = buildWhere2(params);
1062
- if (!where) return null;
1063
- return await prisma.folder.findUnique({
1064
- where,
1065
- include: FOLDER_FULL_INCLUDE
1066
- });
1067
- }
1068
- return {
1069
- findListCards,
1070
- findFull
1071
- };
1072
- }
1073
- function createPostCommandRepository(prisma) {
1074
- async function create({
1075
- slug,
1076
- // ------------------------------------
1077
- // relations
1078
- // ------------------------------------
1079
- // Admin
1080
- author,
1081
- // Post
1082
- topicId,
1083
- parents,
1084
- tags,
1085
- relatedPosts,
1086
- // File
1087
- coverImage,
1088
- contentImageIds,
1089
- // ------------------------------------
1090
- // --- custom fields
1091
- // ------------------------------------
1092
- // multi images
1093
- images1,
1094
- images2,
1095
- // single images
1096
- image1,
1097
- image2,
1098
- image3,
1099
- image4,
1100
- // ------------------------------------
1101
- // translation
1102
- // ------------------------------------
1103
- translations,
1104
- // rest
1105
- ...params
1106
- }) {
1107
- const id = ulid();
1108
- const created = await prisma.post.create({
1109
- data: {
1110
- ...params,
1111
- id,
1112
- // ------------------------------------------------------------------------
1113
- // states
1114
- // ------------------------------------------------------------------------
1115
- slug: slug?.trim() || id,
1116
- // Use id when slug is null or empty string
1117
- // ------------------------------------------------------------------------
1118
- // relations
1119
- // ------------------------------------------------------------------------
1120
- // Admin
1121
- author: connectOne(author),
1122
- // Post
1123
- topic: connectOne(topicId ? { id: topicId } : null),
1124
- parents: connectMany(parents),
1125
- tags: connectMany(tags),
1126
- relatedPosts: connectMany(relatedPosts),
1127
- // File
1128
- coverImage: connectOne(coverImage),
1129
- contentImages: connectMany(contentImageIds.map((id2) => ({ id: id2 }))),
1130
- // ------------------------------------------------------------------------
1131
- // --- custom fields
1132
- // ------------------------------------------------------------------------
1133
- // multi images
1134
- images1: connectMany(images1),
1135
- images2: connectMany(images2),
1136
- // single images
1137
- image1: connectOne(image1),
1138
- image2: connectOne(image2),
1139
- image3: connectOne(image3),
1140
- image4: connectOne(image4),
1141
- // ------------------------------------------------------------------------
1142
- // translation
1143
- // ------------------------------------------------------------------------
1144
- translations: {
1145
- create: translations.map((t) => ({
1146
- ...t,
1147
- externalLinks: t.externalLinks,
1148
- faq: t.faq,
1149
- toc: t.toc
1150
- }))
1151
- }
1152
- }
1153
- });
1154
- return created;
1155
- }
1156
- async function update({
1157
- id,
1158
- slug,
1159
- // ------------------------------------
1160
- // relations
1161
- // ------------------------------------
1162
- // Admin
1163
- author,
1164
- // Post
1165
- topicId,
1166
- parents,
1167
- tags,
1168
- relatedPosts,
1169
- // File
1170
- coverImage,
1171
- contentImageIds,
1172
- // ------------------------------------
1173
- // --- custom fields
1174
- // ------------------------------------
1175
- // multi images
1176
- images1,
1177
- images2,
1178
- // single images
1179
- image1,
1180
- image2,
1181
- image3,
1182
- image4,
1183
- // ------------------------------------
1184
- // translation
1185
- // ------------------------------------
1186
- translations,
1187
- // rest
1188
- ...params
1189
- }) {
1190
- const updated = await prisma.post.update({
1191
- where: { id },
1192
- data: {
1193
- ...params,
1194
- // ------------------------------------------------------------------------
1195
- // states
1196
- // ------------------------------------------------------------------------
1197
- slug: slug?.trim() || id,
1198
- // Use id when slug is null or empty string
1199
- // ------------------------------------------------------------------------
1200
- // relations
1201
- // ------------------------------------------------------------------------
1202
- // Admin
1203
- author: updateOne(author),
1204
- // Post
1205
- topic: updateOne(topicId ? { id: topicId } : null),
1206
- parents: updateMany(parents),
1207
- tags: updateMany(tags),
1208
- relatedPosts: updateMany(relatedPosts),
1209
- // File
1210
- coverImage: updateOne(coverImage),
1211
- contentImages: updateMany(contentImageIds.map((id2) => ({ id: id2 }))),
1212
- // ------------------------------------------------------------------------
1213
- // --- custom fields
1214
- // ------------------------------------------------------------------------
1215
- // multi images
1216
- images1: updateMany(images1),
1217
- images2: updateMany(images2),
1218
- // single images
1219
- image1: updateOne(image1),
1220
- image2: updateOne(image2),
1221
- image3: updateOne(image3),
1222
- image4: updateOne(image4),
1223
- // ------------------------------------------------------------------------
1224
- // translation
1225
- // ------------------------------------------------------------------------
1226
- translations: {
1227
- upsert: translations.map((t) => ({
1228
- where: { postId_locale: { postId: id, locale: t.locale } },
1229
- update: {
1230
- ...t,
1231
- externalLinks: t.externalLinks,
1232
- faq: t.faq,
1233
- toc: t.toc
1234
- },
1235
- create: {
1236
- ...t,
1237
- externalLinks: t.externalLinks,
1238
- faq: t.faq,
1239
- toc: t.toc
1240
- }
1241
- }))
1242
- }
1243
- }
1244
- });
1245
- return updated;
1246
- }
1247
- async function _delete({ id }) {
1248
- const deleted = await prisma.post.delete({
1249
- where: { id }
1250
- });
1251
- return deleted;
1252
- }
1253
- return {
1254
- create,
1255
- update,
1256
- delete: _delete
1257
- };
1258
- }
1259
-
1260
- // src/server/infrastructure/database/post/include.ts
1261
- var POST_LIST_CARD_INCLUDE = {
1262
- // ---------------------------
1263
- // relations: Post
1264
- // ---------------------------
1265
- topic: { include: { translations: true } },
1266
- // Use as post, category
1267
- postsInTopic: true,
1268
- // Use as topic
1269
- children: true,
1270
- // Use as category
1271
- taggedPosts: true,
1272
- // Use as tag
1273
- // ---------------------------
1274
- // relations: File
1275
- // ---------------------------
1276
- coverImage: true,
1277
- // ---------------------------
1278
- // translation
1279
- // ---------------------------
1280
- translations: true
1281
- };
1282
- var POST_FULL_INCLUDE = {
1283
- // ---------------------------
1284
- // relations: Admin
1285
- // ---------------------------
1286
- author: { include: { translations: true } },
1287
- // ---------------------------
1288
- // relations: Post
1289
- // ---------------------------
1290
- topic: { include: POST_LIST_CARD_INCLUDE },
1291
- postsInTopic: { include: POST_LIST_CARD_INCLUDE },
1292
- parents: { include: POST_LIST_CARD_INCLUDE },
1293
- children: { include: POST_LIST_CARD_INCLUDE },
1294
- tags: { include: POST_LIST_CARD_INCLUDE },
1295
- taggedPosts: { include: POST_LIST_CARD_INCLUDE },
1296
- relatedPosts: { include: POST_LIST_CARD_INCLUDE },
1297
- referencingPosts: { include: POST_LIST_CARD_INCLUDE },
1298
- // ---------------------------
1299
- // relations: File
1300
- // ---------------------------
1301
- coverImage: { include: { translations: true } },
1302
- // ---------------------------
1303
- // --- custom fields
1304
- // ---------------------------
1305
- images1: { include: { translations: true } },
1306
- images2: { include: { translations: true } },
1307
- image1: { include: { translations: true } },
1308
- image2: { include: { translations: true } },
1309
- image3: { include: { translations: true } },
1310
- image4: { include: { translations: true } },
1311
- // ---------------------------
1312
- // translation
1313
- // ---------------------------
1314
- translations: true
1315
- };
1316
-
1317
- // src/server/infrastructure/database/post/query/create-post-query-repository.ts
1318
- function buildWhere3(params) {
1319
- if (params.id) return { id: params.id };
1320
- if (params.type && params.slug)
1321
- return { type: params.type, slug: params.slug };
1322
- return;
1323
- }
1324
- function createPostQueryRepository(prisma) {
1325
- async function findListCards({
1326
- locale,
1327
- // search
1328
- searchString,
1329
- type,
1330
- topicId,
1331
- postIds,
1332
- isActive,
1333
- isIndexActive,
1334
- isSlugActive,
1335
- isFeatured,
1336
- isShownOnHome,
1337
- // pagination
1338
- page,
1339
- pageSize
1340
- }) {
1341
- const where = {
1342
- // search
1343
- ...createSearch({
1344
- ...searchString ? { searchString } : {},
1345
- locale,
1346
- rootFields: ["slug"],
1347
- translationFields: ["title", "summary", "description", "content"]
1348
- }),
1349
- // type
1350
- ...type ? { type } : {},
1351
- // states
1352
- ...isActive ? { isActive: true } : {},
1353
- ...isIndexActive ? { isIndexActive: true } : {},
1354
- ...isSlugActive ? { isSlugActive: true } : {},
1355
- ...isFeatured ? { isFeatured: true } : {},
1356
- ...isShownOnHome ? { isShownOnHome: true } : {},
1357
- // relations: Post
1358
- ...topicId ? { topicId } : {},
1359
- // Specified ids
1360
- ...postIds ? { id: { in: postIds } } : {}
1361
- };
1362
- const [items, total] = await prisma.$transaction([
1363
- prisma.post.findMany({
1364
- where,
1365
- orderBy: POST_ORDER_BY,
1366
- include: POST_LIST_CARD_INCLUDE,
1367
- ...createPagination(page, pageSize)
1368
- }),
1369
- prisma.post.count({ where })
1370
- ]);
1371
- return { items, total };
1372
- }
1373
- async function find(params) {
1374
- const where = buildWhere3(params);
1375
- if (!where) return null;
1376
- return prisma.post.findFirst({
1377
- where,
1378
- include: { translations: true }
1379
- });
1380
- }
1381
- async function findMany({
1382
- type,
1383
- topicId
1384
- }) {
1385
- return await prisma.post.findMany({
1386
- where: { type, ...topicId ? { topicId } : {} },
1387
- orderBy: POST_ORDER_BY,
1388
- include: { translations: true }
1389
- });
1390
- }
1391
- async function findFull(params) {
1392
- const where = buildWhere3(params);
1393
- if (!where) return null;
1394
- return prisma.post.findFirst({
1395
- where,
1396
- include: POST_FULL_INCLUDE
1397
- });
1398
- }
1399
- async function findWithSeoMetadata(params) {
1400
- const where = buildWhere3(params);
1401
- if (!where) return null;
1402
- return await prisma.post.findFirst({
1403
- where,
1404
- include: { translations: true, seoMetadatas: true }
1405
- });
1406
- }
1407
- return {
1408
- findListCards,
1409
- findMany,
1410
- find,
1411
- findFull,
1412
- findWithSeoMetadata
1413
- };
1414
- }
1415
-
1416
- // src/server/infrastructure/database/seo-metadata/command/create-seo-metadata-command-repository.ts
1417
- function createSeoMetadataCommandRepository(prisma) {
1418
- async function upsert({
1419
- // ------------------------------------
1420
- // relations
1421
- // ------------------------------------
1422
- // Post
1423
- postId,
1424
- // ------------------------------------
1425
- // translation
1426
- // ------------------------------------
1427
- translations
1428
- }) {
1429
- await prisma.post.update({
1430
- where: { id: postId },
1431
- data: {
1432
- seoMetadatas: {
1433
- upsert: translations.map((t) => ({
1434
- where: { postId_locale: { postId, locale: t.locale } },
1435
- update: t,
1436
- create: t
1437
- }))
1438
- }
1439
- }
1440
- });
1441
- }
1442
- return {
1443
- upsert
1444
- };
1445
- }
1446
-
1447
- export { ADMIN_ORDER_BY, ADMIN_ROLES, FILE_TYPES, ORDER_BY, POST_ORDER_BY, POST_TYPES, ROOT_FOLDER, ROOT_FOLDER_ID, ROOT_FOLDER_NAME, SIMPLE_UPLOAD_FOLDER_KEY, SIMPLE_UPLOAD_FOLDER_NAME, 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';