@yimingliao/cms 0.0.60 → 0.0.62

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.
@@ -0,0 +1,4733 @@
1
+ import { extension, lookup } from 'mime-types';
2
+ import jwt from 'jsonwebtoken';
3
+ import argon2 from 'argon2';
4
+ import crypto, { timingSafeEqual } from 'crypto';
5
+ import 'next/headers';
6
+ import KeyvRedis from '@keyv/redis';
7
+ import Keyv from 'keyv';
8
+ import { z, ZodError } from 'zod';
9
+ import nodemailer from 'nodemailer';
10
+ import { readFile } from 'fs/promises';
11
+ import path from 'path';
12
+ import { ulid } from 'ulid';
13
+ import { NextResponse } from 'next/server';
14
+ import path2 from 'path/posix';
15
+
16
+ // src/domain/resources/admin/props.ts
17
+ var ADMIN_ROLES = {
18
+ SUPER_ADMIN: "SUPER_ADMIN",
19
+ ADMIN: "ADMIN",
20
+ EDITOR: "EDITOR"
21
+ };
22
+
23
+ // src/domain/resources/file/props.ts
24
+ var FILE_TYPES = {
25
+ IMAGE: "IMAGE",
26
+ AUDIO: "AUDIO",
27
+ VIDEO: "VIDEO",
28
+ DOCUMENT: "DOCUMENT",
29
+ ARCHIVE: "ARCHIVE",
30
+ OTHER: "OTHER"
31
+ };
32
+
33
+ // src/domain/resources/file/utils/is-file-locked.ts
34
+ var isFileLocked = (file) => {
35
+ if (!file) return false;
36
+ if (!("adminAsAvatarImage" in file && "postsAsCoverImage" in file && "postsAsContentImage" in file && "postsAsImages1" in file && "postsAsImages2" in file && "postsAsImage1" in file && "postsAsImage2" in file && "postsAsImage3" in file && "postsAsImage4" in file)) {
37
+ return false;
38
+ }
39
+ const locked = file.adminAsAvatarImage.length > 0 || file.postsAsCoverImage.length > 0 || file.postsAsContentImage.length > 0 || file.postsAsImages1.length > 0 || file.postsAsImages2.length > 0 || file.postsAsImage1.length > 0 || file.postsAsImage2.length > 0 || file.postsAsImage3.length > 0 || file.postsAsImage4.length > 0;
40
+ return locked;
41
+ };
42
+
43
+ // src/domain/resources/folder/utils/is-folder-locked.ts
44
+ var isFolderLocked = (folder) => {
45
+ if (!folder) return true;
46
+ if (!("subFolders" in folder)) return false;
47
+ const locked = folder.subFolders?.length > 0 || folder.files?.length > 0;
48
+ return locked;
49
+ };
50
+
51
+ // src/domain/resources/post/props.ts
52
+ var POST_TYPES = {
53
+ TOPIC: "TOPIC",
54
+ CATEGORY: "CATEGORY",
55
+ POST: "POST",
56
+ TAG: "TAG",
57
+ PAGE: "PAGE"
58
+ };
59
+
60
+ // src/domain/resources/constants.ts
61
+ var ROOT_FOLDER_ID = "01ARZ3NDEKTSV4RRFFQ69G5FAV";
62
+ var ROOT_FOLDER_NAME = "ROOT";
63
+ var ROOT_FOLDER = {
64
+ id: ROOT_FOLDER_ID,
65
+ // core
66
+ name: ROOT_FOLDER_NAME,
67
+ key: "",
68
+ // states
69
+ isLocked: true,
70
+ // ---------------------------
71
+ // relations: Folder
72
+ // ---------------------------
73
+ parentFolder: null,
74
+ parentFolderId: null,
75
+ subFolders: [],
76
+ // ---------------------------
77
+ // relations: Folder
78
+ // ---------------------------
79
+ files: [],
80
+ // ---------------------------
81
+ // timestamps
82
+ // ---------------------------
83
+ createdAt: "",
84
+ updatedAt: ""
85
+ };
86
+ var SIMPLE_UPLOAD_FOLDER_NAME = "simple-upload";
87
+ var SIMPLE_UPLOAD_FOLDER_KEY = `${SIMPLE_UPLOAD_FOLDER_NAME}`;
88
+ var mimeToExtension = (mimeType) => {
89
+ if (!mimeType) return "unknown";
90
+ return (extension(mimeType) || "unknown").toLowerCase();
91
+ };
92
+ var ARCHIVE_MIME = /* @__PURE__ */ new Set([
93
+ "application/zip",
94
+ "application/x-rar-compressed",
95
+ "application/x-7z-compressed",
96
+ "application/gzip",
97
+ "application/x-tar"
98
+ ]);
99
+ var classifyFileType = (mimeType, extension2) => {
100
+ if (!mimeType && extension2) {
101
+ mimeType = lookup(extension2) || void 0;
102
+ }
103
+ if (!mimeType) return FILE_TYPES.OTHER;
104
+ if (mimeType.startsWith("image/")) return FILE_TYPES.IMAGE;
105
+ if (mimeType.startsWith("video/")) return FILE_TYPES.VIDEO;
106
+ if (mimeType.startsWith("audio/")) return FILE_TYPES.AUDIO;
107
+ if (ARCHIVE_MIME.has(mimeType)) return FILE_TYPES.ARCHIVE;
108
+ if (mimeType.startsWith("text/") || mimeType === "application/pdf" || mimeType === "application/json" || mimeType === "application/xml") {
109
+ return FILE_TYPES.DOCUMENT;
110
+ }
111
+ if (mimeType.startsWith("application/")) return FILE_TYPES.DOCUMENT;
112
+ return FILE_TYPES.OTHER;
113
+ };
114
+
115
+ // src/shared/blob-file/get-media-info/get-audio-info.ts
116
+ function getAudioInfo(file) {
117
+ return new Promise((resolve, reject) => {
118
+ const audio = document.createElement("audio");
119
+ const objectUrl = URL.createObjectURL(file);
120
+ audio.preload = "metadata";
121
+ const cleanup = () => {
122
+ audio.removeEventListener("loadedmetadata", onLoadedMetadata);
123
+ audio.removeEventListener("error", onError);
124
+ URL.revokeObjectURL(objectUrl);
125
+ };
126
+ const onLoadedMetadata = () => {
127
+ const duration = audio.duration;
128
+ cleanup();
129
+ resolve({ duration });
130
+ };
131
+ const onError = () => {
132
+ cleanup();
133
+ reject(new Error("Failed to load audio metadata."));
134
+ };
135
+ audio.addEventListener("loadedmetadata", onLoadedMetadata, { once: true });
136
+ audio.addEventListener("error", onError, { once: true });
137
+ audio.src = objectUrl;
138
+ });
139
+ }
140
+
141
+ // src/shared/blob-file/get-media-info/get-image-info.ts
142
+ function getImageInfo(file) {
143
+ return new Promise((resolve, reject) => {
144
+ const img = new Image();
145
+ const objectUrl = URL.createObjectURL(file);
146
+ const cleanup = () => {
147
+ img.removeEventListener("load", onLoad);
148
+ img.removeEventListener("error", onError);
149
+ URL.revokeObjectURL(objectUrl);
150
+ };
151
+ const onLoad = () => {
152
+ const width = img.naturalWidth;
153
+ const height = img.naturalHeight;
154
+ cleanup();
155
+ resolve({ width, height });
156
+ };
157
+ const onError = () => {
158
+ cleanup();
159
+ reject(new Error("Failed to load image for size detection."));
160
+ };
161
+ img.addEventListener("load", onLoad, { once: true });
162
+ img.addEventListener("error", onError, { once: true });
163
+ img.src = objectUrl;
164
+ });
165
+ }
166
+
167
+ // src/shared/blob-file/get-media-info/get-video-info.ts
168
+ function getVideoInfo(file) {
169
+ return new Promise((resolve, reject) => {
170
+ const video = document.createElement("video");
171
+ const objectUrl = URL.createObjectURL(file);
172
+ video.preload = "metadata";
173
+ const cleanup = () => {
174
+ video.removeEventListener("loadedmetadata", onLoadedMetadata);
175
+ video.removeEventListener("error", onError);
176
+ video.src = "";
177
+ URL.revokeObjectURL(objectUrl);
178
+ };
179
+ const onLoadedMetadata = () => {
180
+ const info = {
181
+ width: video.videoWidth,
182
+ height: video.videoHeight,
183
+ duration: video.duration
184
+ };
185
+ cleanup();
186
+ resolve(info);
187
+ };
188
+ const onError = () => {
189
+ cleanup();
190
+ reject(new Error("Failed to load video metadata."));
191
+ };
192
+ video.addEventListener("loadedmetadata", onLoadedMetadata, { once: true });
193
+ video.addEventListener("error", onError, { once: true });
194
+ video.src = objectUrl;
195
+ });
196
+ }
197
+
198
+ // src/shared/blob-file/get-media-info/get-media-info.ts
199
+ var IMAGE_EXT = /\.(jpe?g|png|gif|webp|bmp|avif)$/i;
200
+ var VIDEO_EXT = /\.(mp4|webm|mov|mkv)$/i;
201
+ var AUDIO_EXT = /\.(mp3|wav|ogg|m4a)$/i;
202
+ var getMediaInfo = async (file) => {
203
+ const fallback = { width: null, height: null, duration: null };
204
+ const mime = file.type || "";
205
+ const name = file instanceof File ? file.name.toLowerCase() : "";
206
+ try {
207
+ if (mime.startsWith("image/") || IMAGE_EXT.test(name)) {
208
+ const { width, height } = await getImageInfo(file);
209
+ return { width, height, duration: null };
210
+ }
211
+ if (mime.startsWith("video/") || VIDEO_EXT.test(name)) {
212
+ const { width, height, duration } = await getVideoInfo(file);
213
+ return { width, height, duration };
214
+ }
215
+ if (mime.startsWith("audio/") || AUDIO_EXT.test(name)) {
216
+ const { duration } = await getAudioInfo(file);
217
+ return { width: null, height: null, duration };
218
+ }
219
+ return fallback;
220
+ } catch {
221
+ return fallback;
222
+ }
223
+ };
224
+
225
+ // src/shared/blob-file/format-file-size.ts
226
+ var formatFileSize = (size, decimals = 2) => {
227
+ const units = ["B", "KB", "MB", "GB", "TB"];
228
+ let value = size;
229
+ let unitIndex = 0;
230
+ while (value >= 1024 && unitIndex < units.length - 1) {
231
+ value /= 1024;
232
+ unitIndex++;
233
+ }
234
+ const display = unitIndex === 0 ? value.toString() : value.toFixed(decimals);
235
+ return `${display} ${units[unitIndex]}`;
236
+ };
237
+
238
+ // src/shared/result/result.ts
239
+ function success({
240
+ message,
241
+ data,
242
+ meta
243
+ }) {
244
+ return {
245
+ success: true,
246
+ ...message !== void 0 ? { message } : {},
247
+ ...data !== void 0 ? { data } : {},
248
+ ...meta !== void 0 ? { meta } : {}
249
+ };
250
+ }
251
+ function error({ message, errors, code }) {
252
+ return {
253
+ success: false,
254
+ ...message !== void 0 ? { message } : {},
255
+ ...errors !== void 0 ? { errors } : {},
256
+ ...code !== void 0 ? { code } : {}
257
+ };
258
+ }
259
+ var result = {
260
+ success,
261
+ error
262
+ };
263
+
264
+ // src/shared/seo-metadata/standards.ts
265
+ var OG_TYPE_ARRAY = [
266
+ "article",
267
+ "book",
268
+ "music.song",
269
+ "music.album",
270
+ "music.playlist",
271
+ "music.radio_station",
272
+ "profile",
273
+ "website",
274
+ "video.tv_show",
275
+ "video.other",
276
+ "video.movie",
277
+ "video.episode"
278
+ ];
279
+ var TWITTER_CARD_ARRAY = [
280
+ "summary",
281
+ "summary_large_image",
282
+ "player",
283
+ "app"
284
+ ];
285
+
286
+ // src/shared/translation/find-translation.ts
287
+ function findTranslation(translations, locale, defaultValue) {
288
+ const fallback = translations?.[0];
289
+ const found = translations?.find((t) => t.locale === locale) ?? fallback;
290
+ if (!found) return { locale: locale ?? "" };
291
+ const result2 = { ...found };
292
+ for (const key of Object.keys(result2)) {
293
+ if (key === "locale") continue;
294
+ if (result2[key] == null) result2[key] = defaultValue;
295
+ }
296
+ return result2;
297
+ }
298
+
299
+ // src/shared/translation/create-build-translations.ts
300
+ function createBuildTranslations(localArray) {
301
+ return function buildTranslations(fields) {
302
+ const entries = fields.map((f) => [f.key, f.value]);
303
+ return localArray.map((locale) => ({
304
+ locale,
305
+ ...Object.fromEntries(entries)
306
+ }));
307
+ };
308
+ }
309
+
310
+ // src/shared/seo-metadata/utils/build-alternate-map.ts
311
+ var buildAlternateMap = (alternates = []) => {
312
+ const result2 = {};
313
+ for (const { hreflang, href } of alternates) {
314
+ if (!href) continue;
315
+ result2[hreflang] = href;
316
+ }
317
+ return result2;
318
+ };
319
+
320
+ // src/shared/seo-metadata/utils/ensure-og-type.ts
321
+ var ensureOgType = (fallback, ogType) => {
322
+ if (typeof ogType !== "string") return fallback;
323
+ const isValid = OG_TYPE_ARRAY.includes(ogType);
324
+ return isValid ? ogType : fallback;
325
+ };
326
+ var buildOgImages = (url, alt, type, width, height) => {
327
+ if (!url) return void 0;
328
+ const mimeType = lookup(url);
329
+ const validType = typeof mimeType === "string" && mimeType.startsWith("image/") ? mimeType : void 0;
330
+ return [
331
+ {
332
+ url,
333
+ alt: alt ?? void 0,
334
+ type: type ?? validType,
335
+ width: width ?? 1200,
336
+ height: height ?? 630
337
+ }
338
+ ];
339
+ };
340
+
341
+ // src/shared/seo-metadata/utils/sanitize-string-array.ts
342
+ var sanitizeStringArray = (array) => {
343
+ if (!Array.isArray(array)) return void 0;
344
+ return array.filter(
345
+ (a) => typeof a === "string" && a.trim() !== ""
346
+ );
347
+ };
348
+
349
+ // src/shared/seo-metadata/utils/ensure-twitter-card.ts
350
+ var ensureTwitterCard = (fallback, twitterCard) => {
351
+ if (typeof twitterCard !== "string") return fallback;
352
+ const isValid = TWITTER_CARD_ARRAY.includes(twitterCard);
353
+ return isValid ? twitterCard : fallback;
354
+ };
355
+
356
+ // src/shared/seo-metadata/build-website-metadata.ts
357
+ function createBuildWebsiteMetadata({
358
+ defaults
359
+ }) {
360
+ return function buildWebsiteMetadata(post, locale) {
361
+ const seoMetadata = findTranslation(post?.seoMetadatas, locale);
362
+ return {
363
+ metadataBase: defaults.metadataBase,
364
+ // -----------------------------------------------------------------------
365
+ // Basic
366
+ // -----------------------------------------------------------------------
367
+ title: seoMetadata?.title || defaults.title,
368
+ description: seoMetadata?.description,
369
+ // identity
370
+ authors: [{ name: seoMetadata?.author || defaults.author }],
371
+ // url
372
+ alternates: {
373
+ canonical: seoMetadata?.canonical,
374
+ languages: buildAlternateMap(seoMetadata?.alternate)
375
+ },
376
+ // robots
377
+ robots: seoMetadata?.robots || defaults.robots,
378
+ // -----------------------------------------------------------------------
379
+ // Open Graph
380
+ // -----------------------------------------------------------------------
381
+ openGraph: {
382
+ title: seoMetadata?.ogTitle || void 0,
383
+ description: seoMetadata?.ogDescription || void 0,
384
+ // url
385
+ url: seoMetadata?.ogUrl || seoMetadata?.canonical || void 0,
386
+ // identity
387
+ type: ensureOgType("website", seoMetadata?.ogType),
388
+ siteName: seoMetadata?.ogSiteName || defaults.ogSiteName,
389
+ // image
390
+ images: buildOgImages(
391
+ seoMetadata?.ogImage,
392
+ seoMetadata?.ogImageAlt,
393
+ seoMetadata?.ogImageType,
394
+ seoMetadata?.ogImageWidth,
395
+ seoMetadata?.ogImageHeight
396
+ ),
397
+ // locale
398
+ locale: seoMetadata?.ogLocale || void 0,
399
+ alternateLocale: sanitizeStringArray(seoMetadata?.ogLocaleAlternate)
400
+ },
401
+ // -----------------------------------------------------------------------
402
+ // Twitter
403
+ // -----------------------------------------------------------------------
404
+ twitter: {
405
+ card: ensureTwitterCard(defaults.twitterCard, seoMetadata?.twitterCard),
406
+ // identity
407
+ site: seoMetadata?.twitterSite || void 0,
408
+ creator: seoMetadata?.twitterCreator || void 0
409
+ }
410
+ };
411
+ };
412
+ }
413
+
414
+ // src/shared/seo-metadata/utils/to-iso-time.ts
415
+ var toIsoTime = (date) => {
416
+ if (!date) return void 0;
417
+ return date.toISOString();
418
+ };
419
+
420
+ // src/shared/seo-metadata/build-article-metadata.ts
421
+ function createBuildArticleMetadata({
422
+ defaults,
423
+ storageUrl
424
+ }) {
425
+ return function buildArticleMetadata(post, locale) {
426
+ const seoMetadata = findTranslation(post?.seoMetadatas, locale);
427
+ const authorT = findTranslation(post?.author?.translations, locale);
428
+ const coverImageT = findTranslation(post?.coverImage?.translations, locale);
429
+ return {
430
+ metadataBase: defaults.metadataBase,
431
+ // -----------------------------------------------------------------------
432
+ // Basic
433
+ // -----------------------------------------------------------------------
434
+ title: seoMetadata?.title || defaults.title,
435
+ description: seoMetadata?.description,
436
+ // identity
437
+ authors: [
438
+ {
439
+ name: seoMetadata?.author || authorT.authorName || defaults.author,
440
+ url: authorT.url || void 0
441
+ }
442
+ ],
443
+ // url
444
+ alternates: {
445
+ canonical: seoMetadata?.canonical,
446
+ languages: buildAlternateMap(seoMetadata?.alternate)
447
+ },
448
+ // robots
449
+ robots: seoMetadata?.robots || defaults.robots,
450
+ // -----------------------------------------------------------------------
451
+ // Open Graph
452
+ // -----------------------------------------------------------------------
453
+ openGraph: {
454
+ title: seoMetadata?.ogTitle || void 0,
455
+ description: seoMetadata?.ogDescription || void 0,
456
+ // url
457
+ url: seoMetadata?.ogUrl || seoMetadata?.canonical || void 0,
458
+ // identity
459
+ type: ensureOgType("article", seoMetadata?.ogType),
460
+ siteName: seoMetadata?.ogSiteName || defaults.ogSiteName,
461
+ // image
462
+ images: buildOgImages(
463
+ seoMetadata?.ogImage || `${storageUrl}/${post?.coverImage?.key}`,
464
+ seoMetadata?.ogImageAlt || coverImageT.alt,
465
+ seoMetadata?.ogImageType,
466
+ seoMetadata?.ogImageWidth,
467
+ seoMetadata?.ogImageHeight
468
+ ),
469
+ // locale
470
+ locale: seoMetadata?.ogLocale || void 0,
471
+ alternateLocale: sanitizeStringArray(seoMetadata?.ogLocaleAlternate),
472
+ // article
473
+ publishedTime: toIsoTime(seoMetadata?.ogArticlePublishedTime),
474
+ modifiedTime: toIsoTime(seoMetadata?.ogArticleModifiedTime),
475
+ authors: [
476
+ seoMetadata?.ogArticleAuthor || authorT.authorName || defaults.ogSiteName
477
+ ],
478
+ section: seoMetadata?.ogArticleSection,
479
+ tags: sanitizeStringArray(seoMetadata?.ogArticleTag)
480
+ },
481
+ // -----------------------------------------------------------------------
482
+ // Twitter
483
+ // -----------------------------------------------------------------------
484
+ twitter: {
485
+ card: ensureTwitterCard(defaults.twitterCard, seoMetadata?.twitterCard),
486
+ // identity
487
+ site: seoMetadata?.twitterSite || void 0,
488
+ creator: seoMetadata?.twitterCreator || void 0
489
+ }
490
+ };
491
+ };
492
+ }
493
+
494
+ // src/shared/seo-metadata/serialize-json-ld.ts
495
+ var serializeJsonLd = (data) => {
496
+ if (data == null) return "";
497
+ return JSON.stringify(data).replaceAll("<", String.raw`\u003c`).replaceAll(">", String.raw`\u003e`).replaceAll("&", String.raw`\u0026`);
498
+ };
499
+
500
+ // src/shared/form/normalizers/datetime-to-db.ts
501
+ var datetimeToDb = (date) => {
502
+ if (date == null) return null;
503
+ const d = typeof date === "string" ? new Date(date) : date;
504
+ if (Number.isNaN(d.getTime())) return null;
505
+ return d.toISOString();
506
+ };
507
+
508
+ // src/shared/form/normalizers/datetime-to-ui.ts
509
+ var datetimeToUi = (date) => {
510
+ if (date == null) return null;
511
+ const d = typeof date === "string" ? new Date(date) : date;
512
+ if (Number.isNaN(d.getTime())) return null;
513
+ const year = d.getFullYear();
514
+ const month = String(d.getMonth() + 1).padStart(2, "0");
515
+ const day = String(d.getDate()).padStart(2, "0");
516
+ const hours = String(d.getHours()).padStart(2, "0");
517
+ const minutes = String(d.getMinutes()).padStart(2, "0");
518
+ return `${year}-${month}-${day}T${hours}:${minutes}`;
519
+ };
520
+ function createJwtService({
521
+ defaultSecret,
522
+ ...options
523
+ }) {
524
+ function sign({
525
+ payload = {},
526
+ secret = defaultSecret,
527
+ expiresIn = 60 * 60
528
+ }) {
529
+ return jwt.sign(payload, secret, {
530
+ algorithm: "HS256",
531
+ expiresIn,
532
+ ...options
533
+ });
534
+ }
535
+ function verify({
536
+ token,
537
+ secret = defaultSecret
538
+ }) {
539
+ const payload = jwt.verify(token, secret, {
540
+ algorithms: ["HS256"],
541
+ ...options
542
+ });
543
+ if (typeof payload === "string") {
544
+ throw new TypeError("Invalid JWT payload");
545
+ }
546
+ return payload;
547
+ }
548
+ return {
549
+ sign,
550
+ verify
551
+ };
552
+ }
553
+ var DEFAULT_OPTIONS = {
554
+ type: argon2.argon2id,
555
+ memoryCost: 19456,
556
+ // ~19MB
557
+ timeCost: 2,
558
+ parallelism: 1
559
+ };
560
+ function createArgon2Service() {
561
+ async function hash(password) {
562
+ return await argon2.hash(password, DEFAULT_OPTIONS);
563
+ }
564
+ async function verify(digest, password) {
565
+ return await argon2.verify(digest, password);
566
+ }
567
+ return {
568
+ hash,
569
+ verify
570
+ };
571
+ }
572
+ function createCryptoService({
573
+ defaultSecret
574
+ }) {
575
+ const SECRET = crypto.createHash("sha256").update(defaultSecret).digest();
576
+ const ALGORITHM = "aes-256-gcm";
577
+ const IV_LENGTH = 12;
578
+ function generateToken() {
579
+ return crypto.randomBytes(32).toString("base64url");
580
+ }
581
+ function hash(value) {
582
+ return crypto.createHash("sha256").update(value).digest("hex");
583
+ }
584
+ function hashBuffer(value) {
585
+ return crypto.createHash("sha256").update(value).digest();
586
+ }
587
+ function encrypt(data) {
588
+ const iv = crypto.randomBytes(IV_LENGTH);
589
+ const cipher = crypto.createCipheriv(ALGORITHM, SECRET, iv);
590
+ const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
591
+ const tag = cipher.getAuthTag();
592
+ return [
593
+ iv.toString("hex"),
594
+ encrypted.toString("hex"),
595
+ tag.toString("hex")
596
+ ].join(":");
597
+ }
598
+ function decrypt(data) {
599
+ const parts = data.split(":");
600
+ if (parts.length !== 3 || !parts.every(Boolean)) {
601
+ throw new Error("Invalid encrypted payload");
602
+ }
603
+ const [ivHex, encryptedHex, tagHex] = parts;
604
+ const iv = Buffer.from(ivHex, "hex");
605
+ const encrypted = Buffer.from(encryptedHex, "hex");
606
+ const tag = Buffer.from(tagHex, "hex");
607
+ const decipher = crypto.createDecipheriv(ALGORITHM, SECRET, iv);
608
+ decipher.setAuthTag(tag);
609
+ const decrypted = Buffer.concat([
610
+ decipher.update(encrypted),
611
+ decipher.final()
612
+ ]);
613
+ return decrypted.toString("utf8");
614
+ }
615
+ function sign({
616
+ value,
617
+ expireSeconds,
618
+ createdAt
619
+ }) {
620
+ const payload = `${value}|${expireSeconds}|${createdAt ?? Date.now()}`;
621
+ const signature = crypto.createHmac("sha256", SECRET).update(payload).digest("hex");
622
+ const signed = `${payload}|${signature}`;
623
+ return { signed, signature };
624
+ }
625
+ return {
626
+ generateToken,
627
+ hash,
628
+ hashBuffer,
629
+ encrypt,
630
+ decrypt,
631
+ sign
632
+ };
633
+ }
634
+ var DEFAULT_OPTIONS2 = {
635
+ httpOnly: true,
636
+ secure: process.env["NODE_ENV"] === "production",
637
+ sameSite: "strict",
638
+ path: "/"
639
+ };
640
+ function createCookieService(nextCookies, cryptoService) {
641
+ async function set({
642
+ name,
643
+ value,
644
+ expireSeconds
645
+ }) {
646
+ const cookieStore = await nextCookies();
647
+ cookieStore.set(name, value, { ...DEFAULT_OPTIONS2, maxAge: expireSeconds });
648
+ }
649
+ async function setSignedCookie({
650
+ name,
651
+ value,
652
+ expireSeconds
653
+ }) {
654
+ const createdAt = Date.now();
655
+ const { signed } = cryptoService.sign({
656
+ value,
657
+ expireSeconds,
658
+ createdAt
659
+ });
660
+ await set({ name, value: signed, expireSeconds });
661
+ }
662
+ async function get({ name }) {
663
+ const cookieStore = await nextCookies();
664
+ const cookieValue = cookieStore.get(name)?.value;
665
+ return cookieValue;
666
+ }
667
+ async function getSignedCookie({ name }) {
668
+ const signed = await get({ name });
669
+ const value = verifySignedCookie({ signed });
670
+ return value;
671
+ }
672
+ function verifySignedCookie({ signed }) {
673
+ if (!signed) throw new Error("Invalid cookie");
674
+ const parts = signed.split("|");
675
+ if (parts.length !== 4) throw new Error("Invalid cookie");
676
+ const [value, expireSecondsStr, createdAtStr, signature] = parts;
677
+ const expireSeconds = Number.parseInt(expireSecondsStr, 10);
678
+ const createdAt = Number.parseInt(createdAtStr, 10);
679
+ if (Number.isNaN(expireSeconds) || Number.isNaN(createdAt)) {
680
+ throw new TypeError("Invalid cookie");
681
+ }
682
+ const now = Date.now();
683
+ if (now > createdAt + expireSeconds * 1e3) {
684
+ throw new Error("Cookie expired");
685
+ }
686
+ const { signature: generatedSignature } = cryptoService.sign({
687
+ value,
688
+ expireSeconds,
689
+ createdAt
690
+ });
691
+ const sig = Buffer.from(signature);
692
+ const gen = Buffer.from(generatedSignature);
693
+ if (sig.length !== gen.length || !timingSafeEqual(sig, gen)) {
694
+ throw new Error("Invalid cookie signature");
695
+ }
696
+ return value;
697
+ }
698
+ async function deleteCokkie({ name }) {
699
+ const cookieStore = await nextCookies();
700
+ cookieStore.delete(name);
701
+ }
702
+ return {
703
+ set,
704
+ setSignedCookie,
705
+ get,
706
+ getSignedCookie,
707
+ verifySignedCookie,
708
+ delete: deleteCokkie
709
+ };
710
+ }
711
+
712
+ // src/server/infrastructure/cache/cache-key-delimiter.ts
713
+ var CACHE_KEY_DELIMITER = "|";
714
+
715
+ // src/server/infrastructure/cache/create-cache.ts
716
+ function createCache({
717
+ redisUrl,
718
+ namespace,
719
+ keyDelimiter = CACHE_KEY_DELIMITER
720
+ }) {
721
+ const redisStore = new KeyvRedis(redisUrl, {
722
+ keyPrefixSeparator: keyDelimiter
723
+ });
724
+ return new Keyv({
725
+ store: redisStore,
726
+ namespace,
727
+ useKeyPrefix: false
728
+ });
729
+ }
730
+
731
+ // src/server/infrastructure/cache/normalize-cache-key.ts
732
+ var sanitize = (k) => k.replaceAll(/[\u200B-\u200D\uFEFF]/g, "").replaceAll(/[\r\n]/g, "").trim();
733
+ var normalizeCacheKey = (key, delimiter = CACHE_KEY_DELIMITER) => {
734
+ if (!key) return null;
735
+ if (Array.isArray(key)) {
736
+ if (key.length === 0) return null;
737
+ const normalized = key.map((k) => {
738
+ if (k === null) return "__null";
739
+ if (k === void 0) return "__undefined";
740
+ if (typeof k === "boolean") return k ? "__true" : "__false";
741
+ return sanitize(String(k));
742
+ });
743
+ return normalized.join(delimiter);
744
+ }
745
+ if (typeof key === "boolean") return key ? "__true" : "__false";
746
+ return String(key);
747
+ };
748
+
749
+ // src/server/infrastructure/cache/create-cache-result.ts
750
+ var DAY = 1e3 * 60 * 60 * 24;
751
+ function createCacheResult(cache2, logger) {
752
+ return async function cacheResult({
753
+ key: rawKey,
754
+ ttl = DAY,
755
+ load
756
+ }) {
757
+ const key = normalizeCacheKey(rawKey);
758
+ if (!key) {
759
+ logger.error("Cache skipped due to invalid key", {
760
+ rawKey,
761
+ scope: "cacheResult"
762
+ });
763
+ return load();
764
+ }
765
+ try {
766
+ const cached = await cache2.get(key);
767
+ if (cached !== void 0) {
768
+ return cached;
769
+ }
770
+ const data = await load();
771
+ if (data !== void 0) {
772
+ await cache2.set(key, data, ttl);
773
+ }
774
+ return data;
775
+ } catch (error2) {
776
+ logger.error("Cache failure, falling back to loader", {
777
+ key,
778
+ error: error2,
779
+ scope: "cacheResult"
780
+ });
781
+ return load();
782
+ }
783
+ };
784
+ }
785
+ var DEFAULT_MAX_ATTEMPTS = 10;
786
+ var DEFAULT_TIME_WINDOW = 60;
787
+ var lua = `
788
+ local current = redis.call('INCR', KEYS[1])
789
+ if current == 1 then
790
+ redis.call('EXPIRE', KEYS[1], ARGV[1])
791
+ end
792
+ return current
793
+ `;
794
+ function createIpRateLimiter({
795
+ appName,
796
+ cache: cache2,
797
+ headers
798
+ }) {
799
+ return async function ipRateLimiter({
800
+ key: rawKey,
801
+ maxAttempts = DEFAULT_MAX_ATTEMPTS,
802
+ timeWindow = DEFAULT_TIME_WINDOW
803
+ }) {
804
+ const secondaryStore = cache2.store;
805
+ const redis = await secondaryStore.getClient();
806
+ if (!redis) return true;
807
+ const headersStore = await headers();
808
+ const ip = headersStore.get("cf-connecting-ip")?.trim() || headersStore.get("x-forwarded-for")?.split(",")[0]?.trim() || "unknown";
809
+ const key = normalizeCacheKey([
810
+ appName,
811
+ "ip-rate-limiter",
812
+ ...Array.isArray(rawKey) ? rawKey : [rawKey],
813
+ ip
814
+ ]);
815
+ const currentRaw = await redis.eval(lua, {
816
+ keys: [key],
817
+ arguments: [timeWindow.toString()]
818
+ });
819
+ const current = typeof currentRaw === "number" ? currentRaw : Number(currentRaw);
820
+ return current <= maxAttempts;
821
+ };
822
+ }
823
+
824
+ // src/server/infrastructure/zod/rules/unique.ts
825
+ function createUnique(prisma) {
826
+ return async function unique(value, options) {
827
+ if (!value) return true;
828
+ let query = `
829
+ SELECT COUNT(*) AS count
830
+ FROM ${options.table}
831
+ WHERE ${options.column} = $1
832
+ `;
833
+ const params = [value];
834
+ const appendCondition = (op, sc) => {
835
+ const paramIndex = params.length + 1;
836
+ const cast = sc.cast ? `::"${sc.cast}"` : "";
837
+ query += ` AND ${sc.name} ${op} $${paramIndex}${cast}`;
838
+ params.push(sc.value);
839
+ };
840
+ if (options.scope) {
841
+ options.scope.forEach((sc) => appendCondition("=", sc));
842
+ }
843
+ if (options.excludeSelf) {
844
+ appendCondition("!=", options.excludeSelf);
845
+ }
846
+ try {
847
+ const result2 = await prisma.$queryRawUnsafe(
848
+ query,
849
+ ...params
850
+ );
851
+ return Number(result2[0]?.count || 0) === 0;
852
+ } catch (error2) {
853
+ console.error("Unique check failed:", error2);
854
+ return false;
855
+ }
856
+ };
857
+ }
858
+
859
+ // src/server/infrastructure/zod/rules/exist.ts
860
+ function createExist(prisma) {
861
+ return async function exist(value, options) {
862
+ if (!value) return false;
863
+ const column = options.column || "id";
864
+ const query = `
865
+ SELECT COUNT(*) AS count
866
+ FROM ${options.table}
867
+ WHERE ${column} = $1
868
+ `;
869
+ try {
870
+ const result2 = await prisma.$queryRawUnsafe(
871
+ query,
872
+ value
873
+ );
874
+ const count = Number(result2[0]?.count || 0);
875
+ return count > 0;
876
+ } catch (error2) {
877
+ console.error("Exist check failed:", error2);
878
+ return false;
879
+ }
880
+ };
881
+ }
882
+
883
+ // src/server/infrastructure/zod/rules/bcp47.ts
884
+ function bcp47(locale) {
885
+ if (typeof locale !== "string") return false;
886
+ const BCP47_REGEX = new RegExp(
887
+ "^[a-zA-Z]{2,3}(?:-[A-Z][a-z]{3})?" + // optional region
888
+ String.raw`(?:-(?:[A-Z]{2}|\d{3}))?` + // optional variants
889
+ String.raw`(?:-(?:[a-zA-Z0-9]{5,8}|\d[a-zA-Z0-9]{3}))*` + // optional extensions
890
+ "(?:-(?:[0-9A-WY-Za-wy-z]-[a-zA-Z0-9]{2,8}(?:-[a-zA-Z0-9]{2,8})*))*(?:-x(?:-[a-zA-Z0-9]{1,8})+)?$"
891
+ );
892
+ return BCP47_REGEX.test(locale);
893
+ }
894
+
895
+ // src/server/infrastructure/zod/rules/og-locale.ts
896
+ function ogLocale(locale) {
897
+ if (typeof locale !== "string") return false;
898
+ const OG_LOCALE_REGEX = /^[a-z]{2}_[A-Z]{2}$/;
899
+ return OG_LOCALE_REGEX.test(locale);
900
+ }
901
+
902
+ // src/server/infrastructure/zod/create-zod.ts
903
+ function createZod({
904
+ unique,
905
+ exist
906
+ }) {
907
+ const stringProto = z.ZodString.prototype;
908
+ const emailProto = z.ZodEmail.prototype;
909
+ if (!stringProto.unique) {
910
+ Object.defineProperty(stringProto, "unique", {
911
+ configurable: true,
912
+ writable: false,
913
+ value: function(options) {
914
+ return this.refine(async (v) => unique(v, options), {
915
+ params: { i18nKey: "validator.unique" }
916
+ });
917
+ }
918
+ });
919
+ }
920
+ if (!stringProto.exist) {
921
+ Object.defineProperty(stringProto, "exist", {
922
+ configurable: true,
923
+ writable: false,
924
+ value: function(options) {
925
+ return this.refine(async (v) => exist(v, options), {
926
+ params: { i18nKey: "validator.exist" }
927
+ });
928
+ }
929
+ });
930
+ }
931
+ if (!stringProto.bcp47) {
932
+ Object.defineProperty(stringProto, "bcp47", {
933
+ configurable: true,
934
+ writable: false,
935
+ value: function() {
936
+ return this.refine((v) => bcp47(v), {
937
+ params: { i18nKey: "validator.bcp47" }
938
+ });
939
+ }
940
+ });
941
+ }
942
+ if (!stringProto.ogLocale) {
943
+ Object.defineProperty(stringProto, "ogLocale", {
944
+ configurable: true,
945
+ writable: false,
946
+ value: function() {
947
+ return this.refine((v) => ogLocale(v), {
948
+ params: { i18nKey: "validator.og-locale" }
949
+ });
950
+ }
951
+ });
952
+ }
953
+ if (!emailProto.unique) {
954
+ Object.defineProperty(emailProto, "unique", {
955
+ configurable: true,
956
+ writable: false,
957
+ value: function(options) {
958
+ return this.refine(async (v) => unique(v, options), {
959
+ params: { i18nKey: "validator.unique" }
960
+ });
961
+ }
962
+ });
963
+ }
964
+ return z;
965
+ }
966
+
967
+ // src/server/infrastructure/zod/schemas/schemas.ts
968
+ function createSchemas({
969
+ z: z2,
970
+ localeArray,
971
+ exist
972
+ }) {
973
+ const trimmedString = () => z2.string().trim();
974
+ const MAX_NUMBER = 2147483647;
975
+ const MAX_STRING = 1e5;
976
+ const localeSet = new Set(localeArray);
977
+ function text() {
978
+ return trimmedString().max(MAX_STRING);
979
+ }
980
+ function positiveNumber() {
981
+ return z2.preprocess((val) => {
982
+ if (val == null || val === "") return;
983
+ const num = Number(String(val).trim());
984
+ return Number.isNaN(num) ? void 0 : num;
985
+ }, z2.number().min(0).max(MAX_NUMBER));
986
+ }
987
+ function url() {
988
+ return z2.preprocess((val) => {
989
+ if (typeof val === "string" && val.trim() === "" || val === null)
990
+ return null;
991
+ return val;
992
+ }, z2.url().max(2048).nullable());
993
+ }
994
+ function email() {
995
+ return z2.email().max(254).toLowerCase();
996
+ }
997
+ function password() {
998
+ return trimmedString().min(6).max(255);
999
+ }
1000
+ function isoString() {
1001
+ return trimmedString().regex(
1002
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
1003
+ );
1004
+ }
1005
+ function array(schema) {
1006
+ return z2.array(schema).max(100).transform(
1007
+ (arr) => arr.filter((v) => v != null)
1008
+ );
1009
+ }
1010
+ function id() {
1011
+ return trimmedString().length(26).regex(/^[0-9A-Z]{26}$/);
1012
+ }
1013
+ function key() {
1014
+ return trimmedString().max(1024);
1015
+ }
1016
+ function sha256Hash() {
1017
+ return trimmedString().length(64).regex(/^[a-f0-9]{64}$/);
1018
+ }
1019
+ function slug() {
1020
+ return trimmedString().regex(/^$|^[\p{L}0-9-_]+$/u).max(100);
1021
+ }
1022
+ function pathSegment() {
1023
+ return trimmedString().min(1).regex(/^(?!\.\.?$)[^\u0000-\u001F/\\]+$/u).max(255);
1024
+ }
1025
+ function locale() {
1026
+ return trimmedString().refine((val) => localeSet.has(val), {
1027
+ error: "Invalid locale"
1028
+ });
1029
+ }
1030
+ function singleItem(options) {
1031
+ return z2.object({
1032
+ id: id().refine((v) => exist(v, options), {
1033
+ error: "Resource does not exist"
1034
+ })
1035
+ }).nullable();
1036
+ }
1037
+ function multiItems(options) {
1038
+ return array(
1039
+ z2.object({
1040
+ id: id().refine((v) => exist(v, options), {
1041
+ error: "Resource does not exist"
1042
+ })
1043
+ })
1044
+ );
1045
+ }
1046
+ return {
1047
+ z: z2,
1048
+ // base
1049
+ text,
1050
+ positiveNumber,
1051
+ url,
1052
+ email,
1053
+ password,
1054
+ isoString,
1055
+ array,
1056
+ // resource related
1057
+ id,
1058
+ key,
1059
+ sha256Hash,
1060
+ slug,
1061
+ pathSegment,
1062
+ locale,
1063
+ // item
1064
+ singleItem,
1065
+ multiItems
1066
+ };
1067
+ }
1068
+
1069
+ // src/server/infrastructure/zod/schemas/toc-item.ts
1070
+ function createTocItemSchema({
1071
+ z: z2,
1072
+ schemas
1073
+ }) {
1074
+ const tocItem = z2.lazy(
1075
+ () => z2.object({
1076
+ text: schemas.text(),
1077
+ id: schemas.text(),
1078
+ level: z2.union([
1079
+ z2.literal(2),
1080
+ z2.literal(3),
1081
+ z2.literal(4),
1082
+ z2.literal(5),
1083
+ z2.literal(6)
1084
+ ]),
1085
+ children: z2.array(tocItem).default([])
1086
+ })
1087
+ );
1088
+ return tocItem;
1089
+ }
1090
+ function createTransporter({
1091
+ host,
1092
+ port,
1093
+ user,
1094
+ pass
1095
+ }) {
1096
+ return nodemailer.createTransport({
1097
+ host,
1098
+ port,
1099
+ auth: {
1100
+ user,
1101
+ pass
1102
+ }
1103
+ });
1104
+ }
1105
+ function createSendEmail({
1106
+ transporter,
1107
+ config
1108
+ }) {
1109
+ return async function sendEmail(options) {
1110
+ return await transporter.sendMail({
1111
+ from: `${config.fromName} <${config.fromAddress}>`,
1112
+ replyTo: `${config.replyToName} <${config.replyToaddress}>`,
1113
+ ...options
1114
+ });
1115
+ };
1116
+ }
1117
+ var TEMPLATE_DIR = path.resolve(process.cwd(), "emails");
1118
+ var LAYOUT_PATH = path.join(TEMPLATE_DIR, "layout.html");
1119
+ var isDev = process.env.NODE_ENV !== "production";
1120
+ var cache = /* @__PURE__ */ new Map();
1121
+ async function readTemplate(filePath) {
1122
+ if (!isDev && cache.has(filePath)) {
1123
+ return cache.get(filePath);
1124
+ }
1125
+ try {
1126
+ const html = await readFile(filePath, "utf8");
1127
+ if (!isDev) cache.set(filePath, html);
1128
+ return html;
1129
+ } catch (error2) {
1130
+ throw new Error(`Email template file not found: ${filePath}`);
1131
+ }
1132
+ }
1133
+ function applyReplacements(html, replacements, logger) {
1134
+ return html.replace(/\{\{\{(.*?)\}\}\}/g, (_, raw) => {
1135
+ const key = raw.trim();
1136
+ const value = replacements[key];
1137
+ if (value == null) {
1138
+ logger.warn({ msg: "Email template variable missing", variable: key });
1139
+ return "";
1140
+ }
1141
+ return value;
1142
+ });
1143
+ }
1144
+ function createRenderEmailTemplate({
1145
+ siteName,
1146
+ webUrl,
1147
+ logoUrl,
1148
+ logger
1149
+ }) {
1150
+ return async function renderEmailTemplate(templateKey, replacements = {}) {
1151
+ try {
1152
+ const contentPath = path.join(TEMPLATE_DIR, `${templateKey}.html`);
1153
+ const vars = {
1154
+ SITE_NAME: siteName,
1155
+ WEB_URL: webUrl,
1156
+ LOGO_URL: logoUrl,
1157
+ ...replacements
1158
+ };
1159
+ const layoutHtml = await readTemplate(LAYOUT_PATH);
1160
+ let contentHtml = await readTemplate(contentPath);
1161
+ const merged = layoutHtml.replaceAll("{{{content}}}", contentHtml);
1162
+ return applyReplacements(merged, vars, logger);
1163
+ } catch (error2) {
1164
+ logger.error({
1165
+ msg: "Email template render failed",
1166
+ templateKey,
1167
+ templateDir: TEMPLATE_DIR,
1168
+ error: error2
1169
+ });
1170
+ throw new Error(`Email template error: ${templateKey}`);
1171
+ }
1172
+ };
1173
+ }
1174
+
1175
+ // src/server/infrastructure/database/utils/connect.ts
1176
+ var ids = (items) => items.map(({ id }) => ({ id }));
1177
+ function connectOne(item) {
1178
+ return item ? { connect: { id: item.id } } : {};
1179
+ }
1180
+ function connectMany(items) {
1181
+ return { connect: ids(items) };
1182
+ }
1183
+ function updateOne(item) {
1184
+ return item ? { connect: { id: item.id } } : { disconnect: true };
1185
+ }
1186
+ function updateMany(items) {
1187
+ return { set: ids(items) };
1188
+ }
1189
+
1190
+ // src/server/infrastructure/database/admin/command/create-admin-command-repository.ts
1191
+ function createAdminCommandRepository(prisma) {
1192
+ async function create({
1193
+ // core
1194
+ role,
1195
+ email,
1196
+ passwordHash,
1197
+ // better seo
1198
+ socialLinks,
1199
+ // ------------------------------------
1200
+ // relations
1201
+ // ------------------------------------
1202
+ // File
1203
+ avatarImage,
1204
+ // ------------------------------------
1205
+ // translation
1206
+ // ------------------------------------
1207
+ translations
1208
+ }) {
1209
+ const created = await prisma.admin.create({
1210
+ data: {
1211
+ // core
1212
+ role,
1213
+ email,
1214
+ passwordHash,
1215
+ // better seo
1216
+ socialLinks,
1217
+ // ------------------------------------------------------------------------
1218
+ // relations
1219
+ // ------------------------------------------------------------------------
1220
+ // File
1221
+ avatarImage: connectOne(avatarImage),
1222
+ // ------------------------------------------------------------------------
1223
+ // translation
1224
+ // ------------------------------------------------------------------------
1225
+ translations: { create: translations }
1226
+ }
1227
+ });
1228
+ return created;
1229
+ }
1230
+ async function update({
1231
+ id,
1232
+ // core
1233
+ role,
1234
+ email,
1235
+ // better seo
1236
+ socialLinks,
1237
+ // ------------------------------------
1238
+ // relations
1239
+ // ------------------------------------
1240
+ // File
1241
+ avatarImage,
1242
+ // ------------------------------------
1243
+ // translation
1244
+ // ------------------------------------
1245
+ translations,
1246
+ // ------------------------------------
1247
+ // timestamps
1248
+ // ------------------------------------
1249
+ emailVerifiedAt
1250
+ }) {
1251
+ const updated = await prisma.admin.update({
1252
+ where: { id },
1253
+ data: {
1254
+ // core
1255
+ role,
1256
+ ...email !== void 0 ? { email } : {},
1257
+ // better seo
1258
+ socialLinks,
1259
+ // ------------------------------------------------------------------------
1260
+ // relations
1261
+ // ------------------------------------------------------------------------
1262
+ // File
1263
+ avatarImage: updateOne(avatarImage),
1264
+ // ------------------------------------------------------------------------
1265
+ // translation
1266
+ // ------------------------------------------------------------------------
1267
+ translations: {
1268
+ upsert: translations.map((t) => ({
1269
+ where: { adminId_locale: { adminId: id, locale: t.locale } },
1270
+ update: t,
1271
+ create: t
1272
+ }))
1273
+ },
1274
+ // ------------------------------------------------------------------------
1275
+ // timestamps
1276
+ // ------------------------------------------------------------------------
1277
+ emailVerifiedAt
1278
+ }
1279
+ });
1280
+ return updated;
1281
+ }
1282
+ async function _delete({ id }) {
1283
+ const deleted = await prisma.admin.delete({
1284
+ where: { id }
1285
+ });
1286
+ return deleted;
1287
+ }
1288
+ return {
1289
+ create,
1290
+ update,
1291
+ delete: _delete
1292
+ };
1293
+ }
1294
+
1295
+ // src/server/infrastructure/database/constants.ts
1296
+ var ORDER_BY = [
1297
+ { updatedAt: "desc" },
1298
+ { createdAt: "desc" },
1299
+ { id: "asc" }
1300
+ ];
1301
+ var ADMIN_ORDER_BY = [
1302
+ { role: "asc" },
1303
+ ...ORDER_BY
1304
+ ];
1305
+ var POST_ORDER_BY = [
1306
+ { index: "asc" },
1307
+ ...ORDER_BY
1308
+ ];
1309
+
1310
+ // src/server/infrastructure/database/admin/include.ts
1311
+ var ADMIN_FULL_INCLUDE = {
1312
+ // ---------------------------
1313
+ // relations: AdminRefreshToken
1314
+ // ---------------------------
1315
+ adminRefreshTokens: true,
1316
+ // ---------------------------
1317
+ // relations: File
1318
+ // ---------------------------
1319
+ avatarImage: { include: { translations: true } },
1320
+ // ---------------------------
1321
+ // relations: Post
1322
+ // ---------------------------
1323
+ posts: true,
1324
+ // ---------------------------
1325
+ // translation
1326
+ // ---------------------------
1327
+ translations: true
1328
+ };
1329
+
1330
+ // src/server/infrastructure/database/utils/create-search.ts
1331
+ function buildContainsOr(fields, value) {
1332
+ return fields.map((field) => ({
1333
+ [field]: { contains: value, mode: "insensitive" }
1334
+ }));
1335
+ }
1336
+ function buildTranslationSearch(locale, fields, value) {
1337
+ return {
1338
+ translations: {
1339
+ some: { locale, OR: buildContainsOr(fields, value) }
1340
+ }
1341
+ };
1342
+ }
1343
+ function createSearch({
1344
+ locale,
1345
+ searchString,
1346
+ rootFields = [],
1347
+ translationFields = []
1348
+ }) {
1349
+ if (!searchString) return {};
1350
+ const conditions = [];
1351
+ if (rootFields.length > 0) {
1352
+ conditions.push(...buildContainsOr(rootFields, searchString));
1353
+ }
1354
+ if (translationFields.length > 0 && locale) {
1355
+ conditions.push(
1356
+ buildTranslationSearch(locale, translationFields, searchString)
1357
+ );
1358
+ }
1359
+ return conditions.length > 0 ? { OR: conditions } : {};
1360
+ }
1361
+
1362
+ // src/server/infrastructure/database/utils/create-pagination.ts
1363
+ function createPagination(page, pageSize) {
1364
+ if (!page || !pageSize) return {};
1365
+ return {
1366
+ skip: (page - 1) * pageSize,
1367
+ take: pageSize
1368
+ };
1369
+ }
1370
+
1371
+ // src/server/infrastructure/database/admin/query/create-admin-query-repository.ts
1372
+ var OMIT_PASSWORD = { omit: { passwordHash: true } };
1373
+ function buildWhere(params) {
1374
+ if (params.id) return { id: params.id };
1375
+ if (params.email) return { email: params.email };
1376
+ return;
1377
+ }
1378
+ function createAdminQueryRepository(prisma) {
1379
+ async function findListCards({
1380
+ locale,
1381
+ // search
1382
+ searchString,
1383
+ role,
1384
+ adminIds,
1385
+ // pagination
1386
+ page,
1387
+ pageSize
1388
+ }) {
1389
+ const where = {
1390
+ // search
1391
+ ...createSearch({
1392
+ ...searchString !== void 0 ? { searchString } : {},
1393
+ locale,
1394
+ translationFields: ["name"]
1395
+ }),
1396
+ // role
1397
+ ...role !== ADMIN_ROLES.SUPER_ADMIN ? { role: { notIn: [ADMIN_ROLES.SUPER_ADMIN] } } : {},
1398
+ // Specified ids
1399
+ ...adminIds?.length ? { id: { in: adminIds } } : {}
1400
+ };
1401
+ const [items, total] = await prisma.$transaction([
1402
+ prisma.admin.findMany({
1403
+ where,
1404
+ orderBy: ADMIN_ORDER_BY,
1405
+ include: ADMIN_FULL_INCLUDE,
1406
+ ...createPagination(page, pageSize),
1407
+ ...OMIT_PASSWORD
1408
+ }),
1409
+ prisma.admin.count({ where })
1410
+ ]);
1411
+ return { items, total };
1412
+ }
1413
+ async function find(params) {
1414
+ const where = buildWhere(params);
1415
+ if (!where) return null;
1416
+ return prisma.admin.findUnique({
1417
+ where,
1418
+ ...OMIT_PASSWORD
1419
+ });
1420
+ }
1421
+ async function findFull(params) {
1422
+ const where = buildWhere(params);
1423
+ if (!where) return null;
1424
+ return prisma.admin.findUnique({
1425
+ where,
1426
+ include: ADMIN_FULL_INCLUDE,
1427
+ ...OMIT_PASSWORD
1428
+ });
1429
+ }
1430
+ async function findWithPasswordHash(params) {
1431
+ const where = buildWhere(params);
1432
+ if (!where) return null;
1433
+ return prisma.admin.findUnique({
1434
+ where
1435
+ });
1436
+ }
1437
+ return {
1438
+ findListCards,
1439
+ find,
1440
+ findFull,
1441
+ findWithPasswordHash
1442
+ };
1443
+ }
1444
+
1445
+ // src/server/infrastructure/database/admin-refresh-token/command/create-admin-refresh-token-command-repository.ts
1446
+ function createAdminRefreshTokenCommandRepository(prisma) {
1447
+ async function create({
1448
+ adminId,
1449
+ ...params
1450
+ }) {
1451
+ const created = await prisma.adminRefreshToken.create({
1452
+ data: {
1453
+ ...params,
1454
+ admin: { connect: { id: adminId } },
1455
+ deviceInfo: params.deviceInfo
1456
+ }
1457
+ });
1458
+ return created;
1459
+ }
1460
+ async function _delete({ id, tokenHash }) {
1461
+ const where = id ? { id } : tokenHash ? { tokenHash } : void 0;
1462
+ if (where) {
1463
+ await prisma.adminRefreshToken.delete({
1464
+ where
1465
+ });
1466
+ }
1467
+ }
1468
+ async function deleteManyByExpired() {
1469
+ const { count } = await prisma.adminRefreshToken.deleteMany({
1470
+ where: { expiresAt: { lt: /* @__PURE__ */ new Date() } }
1471
+ });
1472
+ return count;
1473
+ }
1474
+ return {
1475
+ create,
1476
+ delete: _delete,
1477
+ deleteManyByExpired
1478
+ };
1479
+ }
1480
+
1481
+ // src/server/infrastructure/database/admin-refresh-token/query/create-admin-refresh-token-query-repository.ts
1482
+ function createAdminRefreshTokenQueryRepository(prisma) {
1483
+ async function findManyByAdminId({
1484
+ adminId
1485
+ }) {
1486
+ return prisma.adminRefreshToken.findMany({
1487
+ where: { adminId },
1488
+ orderBy: [{ createdAt: "desc" }, { id: "asc" }]
1489
+ });
1490
+ }
1491
+ async function findByToken({
1492
+ tokenHash
1493
+ }) {
1494
+ return prisma.adminRefreshToken.findUnique({
1495
+ where: { tokenHash }
1496
+ });
1497
+ }
1498
+ return {
1499
+ findManyByAdminId,
1500
+ findByToken
1501
+ };
1502
+ }
1503
+
1504
+ // src/server/infrastructure/database/file/include.ts
1505
+ var FILE_FULL_INCLUDE = {
1506
+ // ---------------------------
1507
+ // relations: Folder
1508
+ // ---------------------------
1509
+ folder: true,
1510
+ // ---------------------------
1511
+ // relations: Admin
1512
+ // ---------------------------
1513
+ adminAsAvatarImage: true,
1514
+ // ---------------------------
1515
+ // relations: Post
1516
+ // ---------------------------
1517
+ postsAsCoverImage: true,
1518
+ postsAsContentImage: true,
1519
+ // ---------------------------
1520
+ // --- custom fields
1521
+ // ---------------------------
1522
+ postsAsImages1: true,
1523
+ postsAsImages2: true,
1524
+ postsAsImage1: true,
1525
+ postsAsImage2: true,
1526
+ postsAsImage3: true,
1527
+ postsAsImage4: true,
1528
+ // ---------------------------
1529
+ // translation
1530
+ // ---------------------------
1531
+ translations: true
1532
+ };
1533
+
1534
+ // src/server/infrastructure/database/file/command/create-file-command-repository.ts
1535
+ function createFileCommandRepository(prisma) {
1536
+ async function create({
1537
+ // core
1538
+ key,
1539
+ checksum,
1540
+ // meta
1541
+ fileMeta,
1542
+ // media info
1543
+ width,
1544
+ height,
1545
+ duration,
1546
+ // ------------------------------------
1547
+ // relations
1548
+ // ------------------------------------
1549
+ // Folder
1550
+ folder,
1551
+ // ------------------------------------
1552
+ // translation
1553
+ // ------------------------------------
1554
+ translations
1555
+ }) {
1556
+ const extension2 = mimeToExtension(fileMeta.type);
1557
+ const created = await prisma.file.create({
1558
+ data: {
1559
+ // core
1560
+ key,
1561
+ checksum,
1562
+ // media info
1563
+ ...width !== void 0 ? { width } : {},
1564
+ ...height !== void 0 ? { height } : {},
1565
+ ...duration !== void 0 ? { duration } : {},
1566
+ // derived
1567
+ originalName: fileMeta.name || "unknown",
1568
+ size: fileMeta.size ?? 0,
1569
+ extension: extension2,
1570
+ mimeType: fileMeta.type || "unknown",
1571
+ type: classifyFileType(fileMeta.type, extension2),
1572
+ // ------------------------------------------------------------------------
1573
+ // relations
1574
+ // ------------------------------------------------------------------------
1575
+ // Folder
1576
+ folder: connectOne(folder),
1577
+ // ------------------------------------------------------------------------
1578
+ // translation
1579
+ // ------------------------------------------------------------------------
1580
+ translations: {
1581
+ create: translations.map((t) => ({
1582
+ ...t,
1583
+ name: t.name ?? fileMeta.name ?? "unknown"
1584
+ }))
1585
+ }
1586
+ },
1587
+ include: FILE_FULL_INCLUDE
1588
+ });
1589
+ return created;
1590
+ }
1591
+ async function update({
1592
+ file,
1593
+ // Original file (File)
1594
+ id,
1595
+ // core
1596
+ key,
1597
+ checksum,
1598
+ // meta
1599
+ fileMeta,
1600
+ // media info
1601
+ width,
1602
+ height,
1603
+ duration,
1604
+ // ------------------------------------
1605
+ // relations
1606
+ // ------------------------------------
1607
+ // Folder
1608
+ folder,
1609
+ // ------------------------------------
1610
+ // translation
1611
+ // ------------------------------------
1612
+ translations
1613
+ }) {
1614
+ const extension2 = mimeToExtension(fileMeta.type);
1615
+ const updated = await prisma.file.update({
1616
+ where: { id: file.id },
1617
+ data: {
1618
+ // core
1619
+ key,
1620
+ checksum,
1621
+ // media info
1622
+ ...width !== void 0 ? { width } : {},
1623
+ ...height !== void 0 ? { height } : {},
1624
+ ...duration !== void 0 ? { duration } : {},
1625
+ // derived
1626
+ size: fileMeta.size ?? file.size,
1627
+ extension: fileMeta ? extension2 : file.extension,
1628
+ mimeType: fileMeta.type || file.mimeType,
1629
+ type: fileMeta.type ? classifyFileType(fileMeta.type, extension2) : file.type,
1630
+ // ------------------------------------------------------------------------
1631
+ // relations
1632
+ // ------------------------------------------------------------------------
1633
+ // Folder
1634
+ folder: updateOne(folder),
1635
+ // ------------------------------------------------------------------------
1636
+ // translation
1637
+ // ------------------------------------------------------------------------
1638
+ translations: {
1639
+ upsert: translations.map((t) => ({
1640
+ where: { fileId_locale: { fileId: id, locale: t.locale } },
1641
+ update: t,
1642
+ create: t
1643
+ }))
1644
+ }
1645
+ }
1646
+ });
1647
+ return updated;
1648
+ }
1649
+ async function softDelete({ id }) {
1650
+ const updated = await prisma.file.update({
1651
+ where: { id, isLocked: false },
1652
+ data: { deletedAt: /* @__PURE__ */ new Date() }
1653
+ });
1654
+ return updated;
1655
+ }
1656
+ async function softDeleteMany({ ids: ids2 }) {
1657
+ const { count } = await prisma.file.updateMany({
1658
+ where: { id: { in: ids2 }, isLocked: false },
1659
+ data: { deletedAt: /* @__PURE__ */ new Date() }
1660
+ });
1661
+ return count;
1662
+ }
1663
+ async function restoreMany({ ids: ids2 }) {
1664
+ const { count } = await prisma.file.updateMany({
1665
+ where: { id: { in: ids2 } },
1666
+ data: { deletedAt: null }
1667
+ });
1668
+ return count;
1669
+ }
1670
+ async function _delete({ id }) {
1671
+ const deleted = await prisma.file.delete({
1672
+ where: { id }
1673
+ });
1674
+ return deleted;
1675
+ }
1676
+ return {
1677
+ create,
1678
+ update,
1679
+ softDelete,
1680
+ softDeleteMany,
1681
+ restoreMany,
1682
+ delete: _delete
1683
+ };
1684
+ }
1685
+
1686
+ // src/server/infrastructure/database/utils/build-file-usage.ts
1687
+ function buildFileUsage(isLocked) {
1688
+ const relations = [
1689
+ "adminAsAvatarImage",
1690
+ "postsAsCoverImage",
1691
+ "postsAsContentImage",
1692
+ "postsAsImages1",
1693
+ "postsAsImages2",
1694
+ "postsAsImage1",
1695
+ "postsAsImage2",
1696
+ "postsAsImage3",
1697
+ "postsAsImage4"
1698
+ ];
1699
+ if (isLocked === void 0 || isLocked === null) return {};
1700
+ const condition = relations.map((r) => ({
1701
+ [r]: { [isLocked ? "some" : "none"]: {} }
1702
+ }));
1703
+ return isLocked ? { OR: condition } : { AND: condition };
1704
+ }
1705
+
1706
+ // src/server/infrastructure/database/file/query/create-file-query-repository.ts
1707
+ function createFileQueryRepository(prisma) {
1708
+ async function findListCards({
1709
+ locale,
1710
+ // pagination
1711
+ page,
1712
+ pageSize,
1713
+ // search
1714
+ searchString,
1715
+ type,
1716
+ folderId,
1717
+ isLocked,
1718
+ isDeleted = false,
1719
+ fileIds
1720
+ }) {
1721
+ const where = {
1722
+ // search
1723
+ ...createSearch({
1724
+ ...searchString !== void 0 ? { searchString } : {},
1725
+ locale,
1726
+ translationFields: ["name"]
1727
+ }),
1728
+ // type
1729
+ ...type ? { type } : {},
1730
+ // state: isLocked
1731
+ ...buildFileUsage(isLocked),
1732
+ // state: deleteAt
1733
+ deletedAt: isDeleted ? { not: null } : null,
1734
+ // Find deleted files in trash page
1735
+ // relations: Folder
1736
+ ...folderId ? { folderId: folderId === ROOT_FOLDER_ID ? null : folderId } : {},
1737
+ // Specified ids
1738
+ ...fileIds ? { id: { in: fileIds } } : {}
1739
+ };
1740
+ const [items, total] = await prisma.$transaction([
1741
+ prisma.file.findMany({
1742
+ where,
1743
+ orderBy: [...ORDER_BY],
1744
+ include: FILE_FULL_INCLUDE,
1745
+ ...createPagination(page, pageSize)
1746
+ }),
1747
+ prisma.file.count({ where })
1748
+ ]);
1749
+ return { items, total };
1750
+ }
1751
+ async function findManyByIds({
1752
+ ids: ids2
1753
+ }) {
1754
+ return prisma.file.findMany({
1755
+ where: { id: { in: ids2 } },
1756
+ include: FILE_FULL_INCLUDE
1757
+ });
1758
+ }
1759
+ async function findFull({ id }) {
1760
+ return prisma.file.findUnique({
1761
+ where: { id },
1762
+ include: FILE_FULL_INCLUDE
1763
+ });
1764
+ }
1765
+ return {
1766
+ findListCards,
1767
+ findManyByIds,
1768
+ findFull
1769
+ };
1770
+ }
1771
+
1772
+ // src/server/infrastructure/database/folder/command/create-folder-command-repository.ts
1773
+ function createFolderCommandRepository(prisma) {
1774
+ async function create({
1775
+ // core
1776
+ key,
1777
+ name,
1778
+ // ------------------------------------
1779
+ // relations
1780
+ // ------------------------------------
1781
+ // Folder
1782
+ parentFolder
1783
+ }) {
1784
+ const created = await prisma.folder.create({
1785
+ data: {
1786
+ // core
1787
+ key,
1788
+ name,
1789
+ // ------------------------------------------------------------------------
1790
+ // relations
1791
+ // ------------------------------------------------------------------------
1792
+ // Folder
1793
+ parentFolder: connectOne(parentFolder)
1794
+ }
1795
+ });
1796
+ return created;
1797
+ }
1798
+ async function update({
1799
+ id,
1800
+ // core
1801
+ key,
1802
+ name,
1803
+ // ------------------------------------
1804
+ // relations
1805
+ // ------------------------------------
1806
+ // Folder
1807
+ parentFolder
1808
+ }) {
1809
+ const updated = await prisma.folder.update({
1810
+ where: { id },
1811
+ data: {
1812
+ // core
1813
+ key,
1814
+ name,
1815
+ // ------------------------------------------------------------------------
1816
+ // relations
1817
+ // ------------------------------------------------------------------------
1818
+ // Folder
1819
+ parentFolder: updateOne(parentFolder)
1820
+ }
1821
+ });
1822
+ return updated;
1823
+ }
1824
+ async function _delete({ id }) {
1825
+ const deleted = await prisma.folder.delete({
1826
+ where: { id }
1827
+ });
1828
+ return deleted;
1829
+ }
1830
+ return {
1831
+ create,
1832
+ update,
1833
+ delete: _delete
1834
+ };
1835
+ }
1836
+
1837
+ // src/server/infrastructure/database/folder/include.ts
1838
+ var FOLDER_FULL_INCLUDE = {
1839
+ // ---------------------------
1840
+ // relations: Folder
1841
+ // ---------------------------
1842
+ parentFolder: { include: { subFolders: true, files: true } },
1843
+ subFolders: true,
1844
+ // ---------------------------
1845
+ // relations: File
1846
+ // ---------------------------
1847
+ files: true
1848
+ };
1849
+
1850
+ // src/server/infrastructure/database/folder/query/create-folder-query-repository.ts
1851
+ function buildWhere2(params) {
1852
+ if (params.id) return { id: params.id };
1853
+ if (params.key) return { key: params.key };
1854
+ return;
1855
+ }
1856
+ function createFolderQueryRepository(prisma) {
1857
+ async function findListCards({
1858
+ // search
1859
+ searchString,
1860
+ parentFolderId,
1861
+ folderIds,
1862
+ // pagination
1863
+ page,
1864
+ pageSize
1865
+ }) {
1866
+ const where = {
1867
+ // search
1868
+ ...searchString ? { name: { contains: searchString, mode: "insensitive" } } : {},
1869
+ // relations: Folder
1870
+ ...parentFolderId ? parentFolderId === ROOT_FOLDER_ID ? { parentFolderId: null } : { parentFolderId } : {},
1871
+ // Specified ids
1872
+ ...folderIds ? { id: { in: folderIds } } : {}
1873
+ };
1874
+ const [items, total] = await prisma.$transaction([
1875
+ prisma.folder.findMany({
1876
+ where,
1877
+ orderBy: [...ORDER_BY],
1878
+ include: FOLDER_FULL_INCLUDE,
1879
+ ...createPagination(page, pageSize)
1880
+ }),
1881
+ prisma.folder.count({ where })
1882
+ ]);
1883
+ return { items, total };
1884
+ }
1885
+ async function findFull(params) {
1886
+ const where = buildWhere2(params);
1887
+ if (!where) return null;
1888
+ return prisma.folder.findUnique({
1889
+ where,
1890
+ include: FOLDER_FULL_INCLUDE
1891
+ });
1892
+ }
1893
+ return {
1894
+ findListCards,
1895
+ findFull
1896
+ };
1897
+ }
1898
+ function createPostCommandRepository(prisma) {
1899
+ async function create({
1900
+ slug,
1901
+ // ------------------------------------
1902
+ // relations
1903
+ // ------------------------------------
1904
+ // Admin
1905
+ author,
1906
+ // Post
1907
+ topicId,
1908
+ parents,
1909
+ tags,
1910
+ relatedPosts,
1911
+ // File
1912
+ coverImage,
1913
+ contentImageIds,
1914
+ // ------------------------------------
1915
+ // --- custom fields
1916
+ // ------------------------------------
1917
+ // multi images
1918
+ images1,
1919
+ images2,
1920
+ // single images
1921
+ image1,
1922
+ image2,
1923
+ image3,
1924
+ image4,
1925
+ // ------------------------------------
1926
+ // translation
1927
+ // ------------------------------------
1928
+ translations,
1929
+ // rest
1930
+ ...params
1931
+ }) {
1932
+ const id = ulid();
1933
+ const created = await prisma.post.create({
1934
+ data: {
1935
+ ...params,
1936
+ id,
1937
+ // ------------------------------------------------------------------------
1938
+ // states
1939
+ // ------------------------------------------------------------------------
1940
+ slug: slug?.trim() || id,
1941
+ // Use id when slug is null or empty string
1942
+ // ------------------------------------------------------------------------
1943
+ // relations
1944
+ // ------------------------------------------------------------------------
1945
+ // Admin
1946
+ author: connectOne(author),
1947
+ // Post
1948
+ topic: connectOne(topicId ? { id: topicId } : null),
1949
+ parents: connectMany(parents),
1950
+ tags: connectMany(tags),
1951
+ relatedPosts: connectMany(relatedPosts),
1952
+ // File
1953
+ coverImage: connectOne(coverImage),
1954
+ contentImages: connectMany(contentImageIds.map((id2) => ({ id: id2 }))),
1955
+ // ------------------------------------------------------------------------
1956
+ // --- custom fields
1957
+ // ------------------------------------------------------------------------
1958
+ // multi images
1959
+ images1: connectMany(images1),
1960
+ images2: connectMany(images2),
1961
+ // single images
1962
+ image1: connectOne(image1),
1963
+ image2: connectOne(image2),
1964
+ image3: connectOne(image3),
1965
+ image4: connectOne(image4),
1966
+ // ------------------------------------------------------------------------
1967
+ // translation
1968
+ // ------------------------------------------------------------------------
1969
+ translations: {
1970
+ create: translations.map((t) => ({
1971
+ ...t,
1972
+ externalLinks: t.externalLinks,
1973
+ faq: t.faq,
1974
+ toc: t.toc
1975
+ }))
1976
+ }
1977
+ }
1978
+ });
1979
+ return created;
1980
+ }
1981
+ async function update({
1982
+ id,
1983
+ slug,
1984
+ // ------------------------------------
1985
+ // relations
1986
+ // ------------------------------------
1987
+ // Admin
1988
+ author,
1989
+ // Post
1990
+ topicId,
1991
+ parents,
1992
+ tags,
1993
+ relatedPosts,
1994
+ // File
1995
+ coverImage,
1996
+ contentImageIds,
1997
+ // ------------------------------------
1998
+ // --- custom fields
1999
+ // ------------------------------------
2000
+ // multi images
2001
+ images1,
2002
+ images2,
2003
+ // single images
2004
+ image1,
2005
+ image2,
2006
+ image3,
2007
+ image4,
2008
+ // ------------------------------------
2009
+ // translation
2010
+ // ------------------------------------
2011
+ translations,
2012
+ // rest
2013
+ ...params
2014
+ }) {
2015
+ const updated = await prisma.post.update({
2016
+ where: { id },
2017
+ data: {
2018
+ ...params,
2019
+ // ------------------------------------------------------------------------
2020
+ // states
2021
+ // ------------------------------------------------------------------------
2022
+ slug: slug?.trim() || id,
2023
+ // Use id when slug is null or empty string
2024
+ // ------------------------------------------------------------------------
2025
+ // relations
2026
+ // ------------------------------------------------------------------------
2027
+ // Admin
2028
+ author: updateOne(author),
2029
+ // Post
2030
+ topic: updateOne(topicId ? { id: topicId } : null),
2031
+ parents: updateMany(parents),
2032
+ tags: updateMany(tags),
2033
+ relatedPosts: updateMany(relatedPosts),
2034
+ // File
2035
+ coverImage: updateOne(coverImage),
2036
+ contentImages: updateMany(contentImageIds.map((id2) => ({ id: id2 }))),
2037
+ // ------------------------------------------------------------------------
2038
+ // --- custom fields
2039
+ // ------------------------------------------------------------------------
2040
+ // multi images
2041
+ images1: updateMany(images1),
2042
+ images2: updateMany(images2),
2043
+ // single images
2044
+ image1: updateOne(image1),
2045
+ image2: updateOne(image2),
2046
+ image3: updateOne(image3),
2047
+ image4: updateOne(image4),
2048
+ // ------------------------------------------------------------------------
2049
+ // translation
2050
+ // ------------------------------------------------------------------------
2051
+ translations: {
2052
+ upsert: translations.map((t) => ({
2053
+ where: { postId_locale: { postId: id, locale: t.locale } },
2054
+ update: {
2055
+ ...t,
2056
+ externalLinks: t.externalLinks,
2057
+ faq: t.faq,
2058
+ toc: t.toc
2059
+ },
2060
+ create: {
2061
+ ...t,
2062
+ externalLinks: t.externalLinks,
2063
+ faq: t.faq,
2064
+ toc: t.toc
2065
+ }
2066
+ }))
2067
+ }
2068
+ }
2069
+ });
2070
+ return updated;
2071
+ }
2072
+ async function _delete({ id }) {
2073
+ const deleted = await prisma.post.delete({
2074
+ where: { id }
2075
+ });
2076
+ return deleted;
2077
+ }
2078
+ return {
2079
+ create,
2080
+ update,
2081
+ delete: _delete
2082
+ };
2083
+ }
2084
+
2085
+ // src/server/infrastructure/database/post/include.ts
2086
+ var POST_LIST_CARD_INCLUDE = {
2087
+ // ---------------------------
2088
+ // relations: Post
2089
+ // ---------------------------
2090
+ topic: { include: { translations: true } },
2091
+ // Use as post, category
2092
+ postsInTopic: true,
2093
+ // Use as topic
2094
+ children: true,
2095
+ // Use as category
2096
+ taggedPosts: true,
2097
+ // Use as tag
2098
+ // ---------------------------
2099
+ // relations: File
2100
+ // ---------------------------
2101
+ coverImage: true,
2102
+ // ---------------------------
2103
+ // translation
2104
+ // ---------------------------
2105
+ translations: true
2106
+ };
2107
+ var POST_FULL_INCLUDE = {
2108
+ // ---------------------------
2109
+ // relations: Admin
2110
+ // ---------------------------
2111
+ author: { include: { translations: true }, omit: { passwordHash: true } },
2112
+ // ---------------------------
2113
+ // relations: Post
2114
+ // ---------------------------
2115
+ topic: { include: POST_LIST_CARD_INCLUDE },
2116
+ postsInTopic: { include: POST_LIST_CARD_INCLUDE },
2117
+ parents: { include: POST_LIST_CARD_INCLUDE },
2118
+ children: { include: POST_LIST_CARD_INCLUDE },
2119
+ tags: { include: POST_LIST_CARD_INCLUDE },
2120
+ taggedPosts: { include: POST_LIST_CARD_INCLUDE },
2121
+ relatedPosts: { include: POST_LIST_CARD_INCLUDE },
2122
+ referencingPosts: { include: POST_LIST_CARD_INCLUDE },
2123
+ // ---------------------------
2124
+ // relations: File
2125
+ // ---------------------------
2126
+ coverImage: { include: { translations: true } },
2127
+ // ---------------------------
2128
+ // relations: File
2129
+ // ---------------------------
2130
+ seoMetadatas: true,
2131
+ // ---------------------------
2132
+ // --- custom fields
2133
+ // ---------------------------
2134
+ images1: { include: { translations: true } },
2135
+ images2: { include: { translations: true } },
2136
+ image1: { include: { translations: true } },
2137
+ image2: { include: { translations: true } },
2138
+ image3: { include: { translations: true } },
2139
+ image4: { include: { translations: true } },
2140
+ // ---------------------------
2141
+ // translation
2142
+ // ---------------------------
2143
+ translations: true
2144
+ };
2145
+
2146
+ // src/server/infrastructure/database/post/query/create-post-query-repository.ts
2147
+ function buildWhere3(params) {
2148
+ if (params.id) return { id: params.id };
2149
+ if (params.type && params.slug)
2150
+ return { type: params.type, slug: params.slug };
2151
+ return;
2152
+ }
2153
+ function createPostQueryRepository(prisma) {
2154
+ async function findListCards({
2155
+ locale,
2156
+ // search
2157
+ searchString,
2158
+ // type
2159
+ type,
2160
+ // states
2161
+ isActive,
2162
+ isIndexActive,
2163
+ isSlugActive,
2164
+ isFeatured,
2165
+ isShownOnHome,
2166
+ state1,
2167
+ state2,
2168
+ state3,
2169
+ state4,
2170
+ state5,
2171
+ state6,
2172
+ state7,
2173
+ state8,
2174
+ state9,
2175
+ state10,
2176
+ // relations
2177
+ topicId,
2178
+ topicSlug,
2179
+ categoryId,
2180
+ categorySlug,
2181
+ // specified ids
2182
+ postIds,
2183
+ excludeIds,
2184
+ // pagination
2185
+ page,
2186
+ pageSize
2187
+ }) {
2188
+ const where = {
2189
+ // search
2190
+ ...createSearch({
2191
+ ...searchString ? { searchString } : {},
2192
+ locale,
2193
+ rootFields: ["slug"],
2194
+ translationFields: [
2195
+ "title",
2196
+ "subtitle",
2197
+ "summary",
2198
+ "description",
2199
+ "content"
2200
+ ]
2201
+ }),
2202
+ // type
2203
+ ...type ? { type } : {},
2204
+ // states
2205
+ ...isActive ? { isActive: true } : {},
2206
+ ...isIndexActive ? { isIndexActive: true } : {},
2207
+ ...isSlugActive ? { isSlugActive: true } : {},
2208
+ ...isFeatured ? { isFeatured: true } : {},
2209
+ ...isShownOnHome ? { isShownOnHome: true } : {},
2210
+ ...state1 ? { state1: true } : {},
2211
+ ...state2 ? { state2: true } : {},
2212
+ ...state3 ? { state3: true } : {},
2213
+ ...state4 ? { state4: true } : {},
2214
+ ...state5 ? { state5: true } : {},
2215
+ ...state6 ? { state6: true } : {},
2216
+ ...state7 ? { state7: true } : {},
2217
+ ...state8 ? { state8: true } : {},
2218
+ ...state9 ? { state9: true } : {},
2219
+ ...state10 ? { state10: true } : {},
2220
+ // relations
2221
+ ...topicId ? { topicId } : {},
2222
+ ...topicSlug ? { topic: { slug: topicSlug } } : {},
2223
+ ...categoryId ? { parents: { some: { id: categoryId } } } : {},
2224
+ ...categorySlug ? { parents: { some: { slug: categorySlug } } } : {},
2225
+ // Specified ids
2226
+ ...postIds?.length || excludeIds?.length ? {
2227
+ id: {
2228
+ ...postIds ? { in: postIds } : {},
2229
+ ...excludeIds ? { notIn: excludeIds } : {}
2230
+ }
2231
+ } : {}
2232
+ };
2233
+ const [items, total] = await prisma.$transaction([
2234
+ prisma.post.findMany({
2235
+ where,
2236
+ orderBy: POST_ORDER_BY,
2237
+ include: POST_LIST_CARD_INCLUDE,
2238
+ ...createPagination(page, pageSize)
2239
+ }),
2240
+ prisma.post.count({ where })
2241
+ ]);
2242
+ return { items, total };
2243
+ }
2244
+ async function find({
2245
+ isActive,
2246
+ ...rest
2247
+ }) {
2248
+ const where = buildWhere3(rest);
2249
+ if (!where) return null;
2250
+ return prisma.post.findFirst({
2251
+ where: { ...where, ...isActive ? { isActive } : {} },
2252
+ include: { translations: true }
2253
+ });
2254
+ }
2255
+ async function findMany({
2256
+ type,
2257
+ topicId
2258
+ }) {
2259
+ return prisma.post.findMany({
2260
+ where: { type, ...topicId ? { topicId } : {} },
2261
+ orderBy: POST_ORDER_BY,
2262
+ include: { translations: true }
2263
+ });
2264
+ }
2265
+ async function findFull({
2266
+ // states
2267
+ isActive,
2268
+ // relations
2269
+ topicSlug,
2270
+ ...rest
2271
+ }) {
2272
+ const where = buildWhere3(rest);
2273
+ if (!where) return null;
2274
+ return prisma.post.findFirst({
2275
+ where: {
2276
+ ...where,
2277
+ // states
2278
+ ...isActive ? { isActive } : {},
2279
+ // relations
2280
+ ...topicSlug ? { topic: { slug: topicSlug } } : {}
2281
+ },
2282
+ include: POST_FULL_INCLUDE
2283
+ });
2284
+ }
2285
+ return {
2286
+ findListCards,
2287
+ findMany,
2288
+ find,
2289
+ findFull
2290
+ };
2291
+ }
2292
+
2293
+ // src/server/infrastructure/database/seo-metadata/command/create-seo-metadata-command-repository.ts
2294
+ function createSeoMetadataCommandRepository(prisma) {
2295
+ async function upsert({
2296
+ // ------------------------------------
2297
+ // relations
2298
+ // ------------------------------------
2299
+ // Post
2300
+ postId,
2301
+ // ------------------------------------
2302
+ // translation
2303
+ // ------------------------------------
2304
+ translations
2305
+ }) {
2306
+ await prisma.post.update({
2307
+ where: { id: postId },
2308
+ data: {
2309
+ seoMetadatas: {
2310
+ upsert: translations.map((t) => ({
2311
+ where: { postId_locale: { postId, locale: t.locale } },
2312
+ update: t,
2313
+ create: t
2314
+ }))
2315
+ }
2316
+ }
2317
+ });
2318
+ }
2319
+ return {
2320
+ upsert
2321
+ };
2322
+ }
2323
+
2324
+ // src/server/server-error.ts
2325
+ var ServerError = class _ServerError extends Error {
2326
+ i18nKey;
2327
+ statusCode;
2328
+ constructor({
2329
+ message,
2330
+ i18nKey,
2331
+ statusCode
2332
+ }) {
2333
+ super(message);
2334
+ this.name = this.constructor.name;
2335
+ if (i18nKey) this.i18nKey = i18nKey;
2336
+ if (statusCode) this.statusCode = statusCode;
2337
+ }
2338
+ /** 401 Unauthorized */
2339
+ static unauthorized() {
2340
+ return new _ServerError({ i18nKey: "error.unauthorized", statusCode: 401 });
2341
+ }
2342
+ /** 403 Forbidden */
2343
+ static forbidden() {
2344
+ return new _ServerError({ i18nKey: "error.forbidden", statusCode: 403 });
2345
+ }
2346
+ /** 404 Not found */
2347
+ static notFound() {
2348
+ return new _ServerError({ i18nKey: "error.not-found", statusCode: 404 });
2349
+ }
2350
+ /** 500 Internal Server Error */
2351
+ static internalServerError() {
2352
+ return new _ServerError({
2353
+ i18nKey: "error.internal-server-error",
2354
+ statusCode: 500
2355
+ });
2356
+ }
2357
+ };
2358
+
2359
+ // src/server/interfaces/execution/normalize-error.ts
2360
+ var normalizeError = (error2, translator) => {
2361
+ if (error2 instanceof ZodError) {
2362
+ const errors = error2.issues.map((issue) => {
2363
+ let message = issue.message;
2364
+ if (issue.code === "custom" && issue.params?.["i18nKey"]) {
2365
+ message = translator.t(issue.params["i18nKey"]);
2366
+ }
2367
+ return {
2368
+ field: issue.path.join("."),
2369
+ // e.path: string[] e.g. ["name", "email"]
2370
+ message,
2371
+ code: issue.code
2372
+ };
2373
+ });
2374
+ return { message: "Validation faild", errors, statusCode: 422 };
2375
+ }
2376
+ if (error2 instanceof ServerError) {
2377
+ const message = translator.t(
2378
+ error2.i18nKey ?? "error.internal-server-error"
2379
+ );
2380
+ return { message, statusCode: error2.statusCode ?? 500 };
2381
+ }
2382
+ return {
2383
+ message: error2 instanceof Error ? error2.message : JSON.stringify(error2),
2384
+ statusCode: 500,
2385
+ isInternal: true
2386
+ };
2387
+ };
2388
+
2389
+ // src/server/interfaces/execution/execute-action/create-execute-action.ts
2390
+ function createExecuteAction({
2391
+ initI18n,
2392
+ cacheResult,
2393
+ cache: cache2,
2394
+ logger
2395
+ }) {
2396
+ return async function executeAction(fn, options = {}) {
2397
+ const translator = await initI18n();
2398
+ const withCache = options.type === "query" && options.key;
2399
+ try {
2400
+ const { data, i18nKey, message, meta } = withCache ? await cacheResult({
2401
+ key: options.key,
2402
+ ...options.ttl ? { ttl: options.ttl } : {},
2403
+ load: async () => fn(translator)
2404
+ }) : await fn(translator);
2405
+ if (options.type === "command") cache2.clear();
2406
+ const finalMessage = i18nKey ? translator.t(i18nKey) : message;
2407
+ return result.success({
2408
+ ...finalMessage ? { message: finalMessage } : {},
2409
+ ...data !== void 0 ? { data } : {},
2410
+ ...meta ? { meta } : {}
2411
+ });
2412
+ } catch (error2) {
2413
+ const { message, errors, isInternal } = normalizeError(error2, translator);
2414
+ logger.error({ message, errors });
2415
+ return result.error({
2416
+ message: isInternal ? "Internal server error" : message,
2417
+ ...errors !== void 0 ? { errors } : {}
2418
+ });
2419
+ }
2420
+ };
2421
+ }
2422
+ function createExecuteApi({ initI18n, logger }) {
2423
+ return async function serverApi(fn) {
2424
+ const translator = await initI18n();
2425
+ try {
2426
+ return await fn(translator);
2427
+ } catch (error2) {
2428
+ const { message, errors, statusCode, isInternal } = normalizeError(
2429
+ error2,
2430
+ translator
2431
+ );
2432
+ logger.error({ message, errors });
2433
+ return NextResponse.json(
2434
+ result.error({
2435
+ message: isInternal ? "Internal server error" : message,
2436
+ ...errors ? { errors } : {}
2437
+ }),
2438
+ { status: statusCode }
2439
+ );
2440
+ }
2441
+ };
2442
+ }
2443
+
2444
+ // src/server/interfaces/middlewares/auth/create-auth-middleware.ts
2445
+ function createAuthMiddleware({
2446
+ adminRefreshTokenCommandRepository,
2447
+ authUseCases,
2448
+ verifyAccessToken,
2449
+ verifyRefreshToken,
2450
+ headers
2451
+ }) {
2452
+ const authMiddleware = {
2453
+ async authenticate() {
2454
+ const verifiedAccessToken = await verifyAccessToken();
2455
+ if (verifiedAccessToken) return verifiedAccessToken.admin;
2456
+ const verifiedRefreshToken = await verifyRefreshToken();
2457
+ if (!verifiedRefreshToken) throw ServerError.unauthorized();
2458
+ const { adminRefreshToken, admin } = verifiedRefreshToken;
2459
+ await adminRefreshTokenCommandRepository.delete({
2460
+ id: adminRefreshToken.id
2461
+ });
2462
+ await authUseCases.refreshTokens({
2463
+ admin,
2464
+ deviceInfo: adminRefreshToken.deviceInfo,
2465
+ ip: (await headers()).get("x-forwarded-for") || "unknown"
2466
+ });
2467
+ return admin;
2468
+ }
2469
+ };
2470
+ return authMiddleware;
2471
+ }
2472
+
2473
+ // src/server/interfaces/middlewares/auth/create-verify-access-token.ts
2474
+ function createVerifyAccessToken({
2475
+ adminQueryRepository,
2476
+ jwtService,
2477
+ cryptoService,
2478
+ cookieService,
2479
+ config
2480
+ }) {
2481
+ return async function verifyAccessToken() {
2482
+ try {
2483
+ const token = await cookieService.getSignedCookie({
2484
+ name: config.accessTokenName
2485
+ });
2486
+ const payload = jwtService.verify({
2487
+ token,
2488
+ secret: cryptoService.hash(config.accessTokenSecret)
2489
+ });
2490
+ const admin = await adminQueryRepository.findFull({
2491
+ id: payload["id"]
2492
+ });
2493
+ return admin ? { admin } : null;
2494
+ } catch {
2495
+ return null;
2496
+ }
2497
+ };
2498
+ }
2499
+
2500
+ // src/server/interfaces/middlewares/auth/create-verify-refresh-token.ts
2501
+ function createVerifyRefreshToken({
2502
+ adminQueryRepository,
2503
+ adminRefreshTokenQueryRepository,
2504
+ cryptoService,
2505
+ cookieService,
2506
+ config
2507
+ }) {
2508
+ return async function verifyRefreshToken() {
2509
+ try {
2510
+ const token = await cookieService.getSignedCookie({
2511
+ name: config.refreshTokenName
2512
+ });
2513
+ const adminRefreshToken = await adminRefreshTokenQueryRepository.findByToken({
2514
+ tokenHash: cryptoService.hash(token)
2515
+ });
2516
+ if (!adminRefreshToken) return null;
2517
+ const admin = await adminQueryRepository.findFull({
2518
+ id: adminRefreshToken.adminId
2519
+ });
2520
+ return admin ? { adminRefreshToken, admin } : null;
2521
+ } catch {
2522
+ return null;
2523
+ }
2524
+ };
2525
+ }
2526
+
2527
+ // src/server/interfaces/actions/auth/sign-in/sign-in-validator.ts
2528
+ var signInValidator = (schemas) => schemas.z.object({
2529
+ email: schemas.email(),
2530
+ password: schemas.password()
2531
+ });
2532
+
2533
+ // src/server/interfaces/actions/auth/sign-in/create-sign-in-action.ts
2534
+ function createSignInAction(ctx) {
2535
+ return async function signInAction({
2536
+ formData,
2537
+ deviceInfo
2538
+ }) {
2539
+ const {
2540
+ repositories: {
2541
+ adminQueryRepository,
2542
+ adminRefreshTokenCommandRepository
2543
+ },
2544
+ useCases: { authUseCases },
2545
+ action: { executeAction, ipRateLimiter },
2546
+ http: { headers },
2547
+ schemas: { schemas }
2548
+ } = ctx;
2549
+ return executeAction(
2550
+ async () => {
2551
+ await ipRateLimiter({ key: ["sign-in"] });
2552
+ const { email, password } = await signInValidator(schemas).parseAsync(formData);
2553
+ const verified = await authUseCases.verifyCredentials({
2554
+ email,
2555
+ password
2556
+ });
2557
+ const admin = await adminQueryRepository.findFull({
2558
+ id: verified.id
2559
+ });
2560
+ if (!admin) throw ServerError.notFound();
2561
+ await authUseCases.refreshTokens({
2562
+ admin,
2563
+ deviceInfo,
2564
+ ip: (await headers()).get("x-forwarded-for") || "unknown"
2565
+ });
2566
+ await adminRefreshTokenCommandRepository.deleteManyByExpired();
2567
+ return {
2568
+ i18nKey: "ok.sign-in-ok",
2569
+ data: { admin }
2570
+ };
2571
+ },
2572
+ { type: "command" }
2573
+ );
2574
+ };
2575
+ }
2576
+
2577
+ // src/server/interfaces/actions/auth/sign-out/create-sign-out-action.ts
2578
+ function createSignOutAction(ctx) {
2579
+ const {
2580
+ services: { cryptoService, cookieService },
2581
+ repositories: { adminRefreshTokenCommandRepository },
2582
+ action: { executeAction },
2583
+ config: { accessTokenName, refreshTokenName }
2584
+ } = ctx;
2585
+ return async function signOutAction() {
2586
+ return executeAction(
2587
+ async () => {
2588
+ let token;
2589
+ try {
2590
+ token = await cookieService.getSignedCookie({
2591
+ name: refreshTokenName
2592
+ });
2593
+ } catch {
2594
+ }
2595
+ if (token) {
2596
+ await adminRefreshTokenCommandRepository.delete({
2597
+ tokenHash: cryptoService.hash(token)
2598
+ });
2599
+ }
2600
+ await cookieService.delete({ name: accessTokenName });
2601
+ await cookieService.delete({ name: refreshTokenName });
2602
+ return {
2603
+ i18nKey: "ok.sign-out-ok"
2604
+ };
2605
+ },
2606
+ { type: "command" }
2607
+ );
2608
+ };
2609
+ }
2610
+
2611
+ // src/server/interfaces/actions/auth/verify/create-verify-action.ts
2612
+ function createVerifyAction(ctx) {
2613
+ const {
2614
+ middlewares: { authMiddleware },
2615
+ action: { executeAction, ipRateLimiter }
2616
+ } = ctx;
2617
+ return async function verifyAction() {
2618
+ return executeAction(async () => {
2619
+ await ipRateLimiter({ key: ["verify"], maxAttempts: 60 });
2620
+ const admin = await authMiddleware.authenticate();
2621
+ return {
2622
+ data: { admin }
2623
+ };
2624
+ });
2625
+ };
2626
+ }
2627
+
2628
+ // src/server/interfaces/actions/auth/change-password/change-password-validator.ts
2629
+ var changePasswordValidator = (schemas) => schemas.z.object({
2630
+ password: schemas.password(),
2631
+ newPassword: schemas.password(),
2632
+ newPasswordConfirm: schemas.password()
2633
+ }).superRefine((data, ctx) => {
2634
+ if (data.newPassword !== data.newPasswordConfirm) {
2635
+ ctx.addIssue({
2636
+ code: "custom",
2637
+ params: { i18nKey: "validator.password-confirm" },
2638
+ path: ["newPassword"]
2639
+ });
2640
+ ctx.addIssue({
2641
+ code: "custom",
2642
+ params: { i18nKey: "validator.password-confirm" },
2643
+ path: ["newPasswordConfirm"]
2644
+ });
2645
+ }
2646
+ });
2647
+
2648
+ // src/server/interfaces/actions/auth/change-password/create-change-password-action.ts
2649
+ function createChangePasswordAction(ctx) {
2650
+ return async function changePasswordAction({
2651
+ formData
2652
+ }) {
2653
+ const {
2654
+ action: { executeAction, ipRateLimiter },
2655
+ useCases: { authUseCases },
2656
+ middlewares: { authMiddleware },
2657
+ schemas: { schemas }
2658
+ } = ctx;
2659
+ return executeAction(
2660
+ async () => {
2661
+ await ipRateLimiter({ key: ["change-password"] });
2662
+ const { email } = await authMiddleware.authenticate();
2663
+ const { password, newPassword } = await changePasswordValidator(schemas).parseAsync(formData);
2664
+ await authUseCases.verifyCredentials({ email, password });
2665
+ await authUseCases.updatePassword({ email, password: newPassword });
2666
+ return {
2667
+ i18nKey: "ok.change-password-ok"
2668
+ };
2669
+ },
2670
+ { type: "command" }
2671
+ );
2672
+ };
2673
+ }
2674
+
2675
+ // src/server/interfaces/actions/auth/verify-email/verify-email-validator.ts
2676
+ var verifyEmailValidator = (schemas) => schemas.z.object({
2677
+ email: schemas.email(),
2678
+ emailVerificationToken: schemas.z.string().trim().max(1e3)
2679
+ });
2680
+
2681
+ // src/server/interfaces/actions/auth/verify-email/create-verify-email-action.ts
2682
+ function createVerifyEmailAction(ctx) {
2683
+ return async function verifyEmailAction({
2684
+ formData
2685
+ }) {
2686
+ const {
2687
+ repositories: { adminQueryRepository },
2688
+ useCases: { authUseCases },
2689
+ action: { executeAction, ipRateLimiter },
2690
+ schemas: { schemas }
2691
+ } = ctx;
2692
+ return executeAction(
2693
+ async () => {
2694
+ await ipRateLimiter({ key: ["verify-email"] });
2695
+ const { email, emailVerificationToken } = await verifyEmailValidator(schemas).parseAsync(formData);
2696
+ const admin = await adminQueryRepository.find({ email });
2697
+ if (!admin) throw new ServerError({ i18nKey: "error.email-not-found" });
2698
+ await authUseCases.verifyEmailAndUpdate({
2699
+ token: emailVerificationToken || "",
2700
+ admin
2701
+ });
2702
+ return {
2703
+ i18nKey: "ok.verify-email-ok",
2704
+ data: { admin }
2705
+ };
2706
+ },
2707
+ { type: "command" }
2708
+ );
2709
+ };
2710
+ }
2711
+
2712
+ // src/server/interfaces/actions/auth/email-unverifired/email-unverifired-validator.ts
2713
+ var emailUnverifiedValidator = (schemas) => schemas.z.object({
2714
+ email: schemas.email()
2715
+ });
2716
+
2717
+ // src/server/interfaces/actions/auth/email-unverifired/create-email-unverifired-action.ts
2718
+ function createEmailUnverifiedAction(ctx) {
2719
+ return async function emailUnverifiedAction({
2720
+ formData
2721
+ }) {
2722
+ const {
2723
+ repositories: { adminQueryRepository },
2724
+ action: { executeAction, ipRateLimiter },
2725
+ emails: { emailVerificationEmail },
2726
+ schemas: { schemas }
2727
+ } = ctx;
2728
+ return executeAction(
2729
+ async (translator) => {
2730
+ await ipRateLimiter({
2731
+ key: ["email-unverified"],
2732
+ maxAttempts: 2,
2733
+ timeWindow: 60
2734
+ });
2735
+ const { email } = await emailUnverifiedValidator(schemas).parseAsync(formData);
2736
+ const admin = await adminQueryRepository.find({ email });
2737
+ if (!admin) throw new ServerError({ i18nKey: "error.email-not-found" });
2738
+ void emailVerificationEmail.send({ translator, admin });
2739
+ return {
2740
+ i18nKey: "ok.email-unverified-ok"
2741
+ };
2742
+ },
2743
+ { type: "command" }
2744
+ );
2745
+ };
2746
+ }
2747
+
2748
+ // src/server/interfaces/actions/auth/forgot-password/forgot-password-validator.ts
2749
+ var forgetPasswordValidator = (schemas) => schemas.z.object({
2750
+ email: schemas.email()
2751
+ });
2752
+
2753
+ // src/server/interfaces/actions/auth/forgot-password/create-forgot-password-action.ts
2754
+ function createForgotPasswordAction(ctx) {
2755
+ return async function forgotPasswordAction({
2756
+ formData
2757
+ }) {
2758
+ const {
2759
+ repositories: { adminQueryRepository },
2760
+ action: { executeAction, ipRateLimiter },
2761
+ emails: { forgotPasswordEmail },
2762
+ schemas: { schemas }
2763
+ } = ctx;
2764
+ return executeAction(
2765
+ async (translator) => {
2766
+ await ipRateLimiter({
2767
+ key: ["forget-password"],
2768
+ maxAttempts: 2,
2769
+ timeWindow: 60
2770
+ });
2771
+ const { email } = await forgetPasswordValidator(schemas).parseAsync(formData);
2772
+ const admin = await adminQueryRepository.find({ email });
2773
+ if (!admin) throw new ServerError({ i18nKey: "error.email-not-found" });
2774
+ void forgotPasswordEmail.send({ translator, admin });
2775
+ return {
2776
+ i18nKey: "ok.forgot-password-ok"
2777
+ };
2778
+ },
2779
+ { type: "command" }
2780
+ );
2781
+ };
2782
+ }
2783
+
2784
+ // src/server/interfaces/actions/auth/reset-password/reset-password-validator.ts
2785
+ var resetPasswordValidator = (schemas) => schemas.z.object({
2786
+ passwordResetToken: schemas.z.string().trim().max(1e3),
2787
+ newPassword: schemas.password(),
2788
+ newPasswordConfirm: schemas.password()
2789
+ }).superRefine((data, ctx) => {
2790
+ if (data.newPassword !== data.newPasswordConfirm) {
2791
+ ctx.addIssue({
2792
+ code: "custom",
2793
+ message: "custom.password-confirm",
2794
+ path: ["newPassword"]
2795
+ });
2796
+ ctx.addIssue({
2797
+ code: "custom",
2798
+ message: "custom.password-confirm",
2799
+ path: ["newPasswordConfirm"]
2800
+ });
2801
+ }
2802
+ });
2803
+
2804
+ // src/server/interfaces/actions/auth/reset-password/create-reset-password-action.ts
2805
+ function createResetPasswordAction(ctx) {
2806
+ return async function ResetPasswordAction({
2807
+ formData
2808
+ }) {
2809
+ const {
2810
+ useCases: { authUseCases },
2811
+ action: { executeAction, ipRateLimiter },
2812
+ schemas: { schemas }
2813
+ } = ctx;
2814
+ return executeAction(
2815
+ async () => {
2816
+ await ipRateLimiter({ key: ["reset-password"] });
2817
+ const { newPassword, passwordResetToken } = await resetPasswordValidator(schemas).parseAsync(formData);
2818
+ const decoded = authUseCases.verifyPasswordResetToken({
2819
+ token: passwordResetToken
2820
+ });
2821
+ await authUseCases.updatePassword({
2822
+ email: decoded.email,
2823
+ password: newPassword
2824
+ });
2825
+ return {
2826
+ i18nKey: "ok.reset-password-ok"
2827
+ };
2828
+ },
2829
+ { type: "command" }
2830
+ );
2831
+ };
2832
+ }
2833
+
2834
+ // src/server/interfaces/actions/resources/admin/commands/create/admin-create-validator.ts
2835
+ var adminCreateValidator = (schemas) => schemas.z.object({
2836
+ // core
2837
+ role: schemas.z.enum(Object.values(ADMIN_ROLES)),
2838
+ email: schemas.email().unique({ table: "admins", column: "email" }),
2839
+ password: schemas.password(),
2840
+ // better seo
2841
+ socialLinks: schemas.array(schemas.url()),
2842
+ // ----------------------------------------------------------------------------
2843
+ // relations
2844
+ // ----------------------------------------------------------------------------
2845
+ // File
2846
+ avatarImage: schemas.z.object({ id: schemas.id().exist({ table: "files" }) }).nullable(),
2847
+ // ----------------------------------------------------------------------------
2848
+ // translation
2849
+ // ----------------------------------------------------------------------------
2850
+ translations: schemas.array(
2851
+ schemas.z.object({
2852
+ // core
2853
+ locale: schemas.locale(),
2854
+ // text
2855
+ name: schemas.text().nullable(),
2856
+ // better seo
2857
+ authorName: schemas.text().nullable(),
2858
+ description: schemas.text().nullable(),
2859
+ jobTitle: schemas.text().nullable(),
2860
+ url: schemas.url().nullable(),
2861
+ worksFor: schemas.text().nullable(),
2862
+ knowsAbout: schemas.array(schemas.text()),
2863
+ homeLocation: schemas.text().nullable(),
2864
+ nationality: schemas.text().nullable()
2865
+ })
2866
+ )
2867
+ });
2868
+
2869
+ // src/server/interfaces/actions/resources/admin/commands/create/create-admin-create-action.ts
2870
+ function createAdminCreateAction(ctx) {
2871
+ const {
2872
+ services: { argon2Service },
2873
+ repositories: { adminCommandRepository },
2874
+ middlewares: { authMiddleware },
2875
+ action: { executeAction },
2876
+ emails: { emailVerificationEmail },
2877
+ schemas: { schemas }
2878
+ } = ctx;
2879
+ return async function adminCreateAction({
2880
+ formData
2881
+ }) {
2882
+ return executeAction(
2883
+ async (translator) => {
2884
+ await authMiddleware.authenticate();
2885
+ const {
2886
+ role,
2887
+ email,
2888
+ password,
2889
+ socialLinks,
2890
+ avatarImage,
2891
+ translations
2892
+ } = await adminCreateValidator(schemas).parseAsync(formData);
2893
+ const passwordHash = await argon2Service.hash(password);
2894
+ const created = await adminCommandRepository.create({
2895
+ role,
2896
+ email,
2897
+ passwordHash,
2898
+ socialLinks,
2899
+ avatarImage,
2900
+ translations
2901
+ });
2902
+ void emailVerificationEmail.send({ translator, admin: created });
2903
+ return {
2904
+ i18nKey: "ok.admins-store-ok",
2905
+ data: { admin: created }
2906
+ };
2907
+ },
2908
+ { type: "command" }
2909
+ );
2910
+ };
2911
+ }
2912
+
2913
+ // src/server/interfaces/actions/resources/admin/commands/update/admin-update-validator.ts
2914
+ var adminUpdateValidator = (schemas, id) => schemas.z.object({
2915
+ // core
2916
+ role: schemas.z.enum(Object.values(ADMIN_ROLES)),
2917
+ email: schemas.email().unique({
2918
+ table: "admins",
2919
+ column: "email",
2920
+ excludeSelf: { name: "id", value: id }
2921
+ }).optional(),
2922
+ // better seo
2923
+ socialLinks: schemas.array(schemas.url()),
2924
+ // ----------------------------------------------------------------------------
2925
+ // relations
2926
+ // ----------------------------------------------------------------------------
2927
+ // File
2928
+ avatarImage: schemas.z.object({ id: schemas.id().exist({ table: "files" }) }).nullable(),
2929
+ // ----------------------------------------------------------------------------
2930
+ // translation
2931
+ // ----------------------------------------------------------------------------
2932
+ translations: schemas.array(
2933
+ schemas.z.object({
2934
+ // core
2935
+ locale: schemas.locale(),
2936
+ // text
2937
+ name: schemas.text().nullable(),
2938
+ // better seo
2939
+ authorName: schemas.text().nullable(),
2940
+ description: schemas.text().nullable(),
2941
+ jobTitle: schemas.text().nullable(),
2942
+ url: schemas.url().nullable(),
2943
+ worksFor: schemas.text().nullable(),
2944
+ knowsAbout: schemas.array(schemas.text()),
2945
+ homeLocation: schemas.text().nullable(),
2946
+ nationality: schemas.text().nullable()
2947
+ })
2948
+ )
2949
+ });
2950
+
2951
+ // src/server/interfaces/actions/resources/admin/commands/update/create-admin-update-action.ts
2952
+ function createAdminUpdateAction(ctx) {
2953
+ const {
2954
+ repositories: { adminQueryRepository, adminCommandRepository },
2955
+ middlewares: { authMiddleware },
2956
+ action: { executeAction },
2957
+ emails: { emailVerificationEmail },
2958
+ schemas: { schemas }
2959
+ } = ctx;
2960
+ return async function adminUpdateAction({
2961
+ id,
2962
+ formData
2963
+ }) {
2964
+ return executeAction(
2965
+ async (translator) => {
2966
+ const currentAdmin = await authMiddleware.authenticate();
2967
+ const targetAdmin = await adminQueryRepository.findFull({ id });
2968
+ if (!targetAdmin) throw ServerError.notFound();
2969
+ const isSelf = currentAdmin.id === targetAdmin.id;
2970
+ const canModifyOthers = currentAdmin.role === ADMIN_ROLES.SUPER_ADMIN;
2971
+ if (!isSelf && !canModifyOthers) throw ServerError.forbidden();
2972
+ const { role, email, socialLinks, avatarImage, translations } = await adminUpdateValidator(schemas, targetAdmin.id).parseAsync(
2973
+ formData
2974
+ );
2975
+ const isUpdatingEmail = email !== targetAdmin.email;
2976
+ if (isUpdatingEmail) {
2977
+ void emailVerificationEmail.send({
2978
+ translator,
2979
+ admin: targetAdmin,
2980
+ ...email ? { newEmail: email } : {}
2981
+ });
2982
+ }
2983
+ const updatedAdmin = await adminCommandRepository.update({
2984
+ id: targetAdmin.id,
2985
+ role,
2986
+ ...email !== void 0 ? { email } : {},
2987
+ socialLinks,
2988
+ avatarImage,
2989
+ translations,
2990
+ emailVerifiedAt: !isUpdatingEmail ? targetAdmin.emailVerifiedAt : null
2991
+ // Clear emailVerifiedAt if updating a new email
2992
+ });
2993
+ return {
2994
+ i18nKey: !isUpdatingEmail ? "ok.update-ok" : "ok.admins-update-email-ok",
2995
+ data: { admin: updatedAdmin }
2996
+ };
2997
+ },
2998
+ { type: "command" }
2999
+ );
3000
+ };
3001
+ }
3002
+
3003
+ // src/server/interfaces/actions/resources/admin/commands/delete/create-admin-delete-action.ts
3004
+ function createAdminDeleteAction(ctx) {
3005
+ const {
3006
+ repositories: { adminQueryRepository, adminCommandRepository },
3007
+ middlewares: { authMiddleware },
3008
+ action: { executeAction }
3009
+ } = ctx;
3010
+ return async function adminDeleteAction({ targetId }) {
3011
+ return executeAction(
3012
+ async () => {
3013
+ await authMiddleware.authenticate();
3014
+ const targetAdmin = await adminQueryRepository.findFull({
3015
+ id: targetId
3016
+ });
3017
+ if (!targetAdmin) throw ServerError.notFound();
3018
+ if (targetAdmin.role === ADMIN_ROLES.SUPER_ADMIN) {
3019
+ throw new ServerError({ i18nKey: "error.admins-destroy-forbidden" });
3020
+ }
3021
+ await adminCommandRepository.delete({ id: targetId });
3022
+ return {
3023
+ i18nKey: "ok.destroy-ok"
3024
+ };
3025
+ },
3026
+ { type: "command" }
3027
+ );
3028
+ };
3029
+ }
3030
+
3031
+ // src/server/interfaces/actions/resources/admin/queries/create-admin-find-full-action.ts
3032
+ function createAdminFindFullAction(ctx) {
3033
+ const {
3034
+ repositories: { adminQueryRepository },
3035
+ middlewares: { authMiddleware },
3036
+ action: { executeAction }
3037
+ } = ctx;
3038
+ return async function adminFindFullAction(params) {
3039
+ return executeAction(
3040
+ async () => {
3041
+ await authMiddleware.authenticate();
3042
+ const admin = await adminQueryRepository.findFull(params);
3043
+ if (!admin) throw ServerError.notFound();
3044
+ return {
3045
+ data: { admin }
3046
+ };
3047
+ },
3048
+ {
3049
+ type: "query",
3050
+ key: ["admin", "findFullAction", params.id, params.email]
3051
+ }
3052
+ );
3053
+ };
3054
+ }
3055
+
3056
+ // src/server/interfaces/actions/resources/admin/queries/create-admin-find-list-cards-action.ts
3057
+ function createAdminFindListCardsAction(ctx) {
3058
+ const {
3059
+ repositories: { adminQueryRepository },
3060
+ middlewares: { authMiddleware },
3061
+ action: { executeAction }
3062
+ } = ctx;
3063
+ return async function adminFindFullAction(params) {
3064
+ return executeAction(
3065
+ async ({ locale }) => {
3066
+ const { role } = await authMiddleware.authenticate();
3067
+ const { items, total } = await adminQueryRepository.findListCards({
3068
+ ...params,
3069
+ locale,
3070
+ role
3071
+ });
3072
+ return {
3073
+ data: { items, total }
3074
+ };
3075
+ },
3076
+ {
3077
+ type: "query",
3078
+ key: [
3079
+ "admin",
3080
+ "findListCardsAction",
3081
+ params.searchString,
3082
+ ...params.adminIds ?? [],
3083
+ params.page,
3084
+ params.pageSize
3085
+ ]
3086
+ }
3087
+ );
3088
+ };
3089
+ }
3090
+
3091
+ // src/server/interfaces/actions/resources/admin-refresh-token/commands/delete/create-admin-refresh-token-delete-action.ts
3092
+ function createAdminRefreshTokenDeleteAction(ctx) {
3093
+ const {
3094
+ repositories: {
3095
+ adminRefreshTokenQueryRepository,
3096
+ adminRefreshTokenCommandRepository
3097
+ },
3098
+ middlewares: { authMiddleware },
3099
+ action: { executeAction }
3100
+ } = ctx;
3101
+ return async function adminRefreshTokenDeleteAction(tokenHash) {
3102
+ return executeAction(
3103
+ async () => {
3104
+ const currentAdmin = await authMiddleware.authenticate();
3105
+ const targetAdminRefreshToken = await adminRefreshTokenQueryRepository.findByToken({
3106
+ tokenHash
3107
+ });
3108
+ if (!targetAdminRefreshToken) throw ServerError.notFound();
3109
+ const isSelf = currentAdmin.id === targetAdminRefreshToken.adminId;
3110
+ const canModifyOthers = currentAdmin.role === ADMIN_ROLES.SUPER_ADMIN;
3111
+ if (!isSelf && !canModifyOthers) throw ServerError.forbidden();
3112
+ await adminRefreshTokenCommandRepository.delete({
3113
+ id: targetAdminRefreshToken.id
3114
+ });
3115
+ return {
3116
+ i18nKey: "ok.destroy-ok"
3117
+ };
3118
+ },
3119
+ { type: "command" }
3120
+ );
3121
+ };
3122
+ }
3123
+
3124
+ // src/server/interfaces/actions/resources/admin-refresh-token/queries/create-admin-refresh-token-find-full-action.ts
3125
+ function createAdminRefreshTokenFindManyAction(ctx) {
3126
+ const {
3127
+ services: { cryptoService, cookieService },
3128
+ repositories: { adminRefreshTokenQueryRepository },
3129
+ middlewares: { authMiddleware },
3130
+ action: { executeAction },
3131
+ config: { refreshTokenName }
3132
+ } = ctx;
3133
+ return async function adminRefreshTokenFindManyAction({
3134
+ adminId
3135
+ }) {
3136
+ return executeAction(
3137
+ async () => {
3138
+ await authMiddleware.authenticate();
3139
+ const token = await cookieService.getSignedCookie({
3140
+ name: refreshTokenName
3141
+ });
3142
+ const currentToken = await adminRefreshTokenQueryRepository.findByToken(
3143
+ { tokenHash: cryptoService.hash(token) }
3144
+ );
3145
+ if (!currentToken) throw ServerError.notFound();
3146
+ const adminRefreshTokens = await adminRefreshTokenQueryRepository.findManyByAdminId({
3147
+ adminId
3148
+ });
3149
+ return {
3150
+ data: {
3151
+ adminRefreshTokens,
3152
+ currentToken
3153
+ }
3154
+ };
3155
+ },
3156
+ {
3157
+ type: "query",
3158
+ key: ["admin-refresh-token", "findManyAction", adminId]
3159
+ }
3160
+ );
3161
+ };
3162
+ }
3163
+
3164
+ // src/server/interfaces/actions/resources/file/commands/create/file-create-validator.ts
3165
+ var fileCreateValidator = (schemas) => schemas.z.object({
3166
+ key: schemas.key(),
3167
+ checksum: schemas.sha256Hash(),
3168
+ // file meta
3169
+ fileMeta: schemas.z.object({
3170
+ type: schemas.text(),
3171
+ size: schemas.positiveNumber(),
3172
+ name: schemas.text()
3173
+ }),
3174
+ // media info
3175
+ width: schemas.positiveNumber().nullable().optional(),
3176
+ height: schemas.positiveNumber().nullable().optional(),
3177
+ duration: schemas.positiveNumber().nullable().optional(),
3178
+ // ----------------------------------------------------------------------------
3179
+ // relations
3180
+ // ----------------------------------------------------------------------------
3181
+ folder: schemas.z.object({
3182
+ id: schemas.id().exist({ table: "folders", column: "id" })
3183
+ }).nullable().optional(),
3184
+ // Use in [file] pages
3185
+ folderKey: schemas.key().exist({ table: "folders", column: "key" }).optional(),
3186
+ // Use in Simple Upload
3187
+ // ----------------------------------------------------------------------------
3188
+ // translation
3189
+ // ----------------------------------------------------------------------------
3190
+ translations: schemas.array(
3191
+ schemas.z.object({
3192
+ // core
3193
+ locale: schemas.locale(),
3194
+ // text
3195
+ name: schemas.text().nullable(),
3196
+ alt: schemas.text().nullable()
3197
+ })
3198
+ )
3199
+ });
3200
+
3201
+ // src/server/interfaces/actions/resources/file/commands/create/create-file-create-action.ts
3202
+ function createFileCreateAction(ctx) {
3203
+ const {
3204
+ repositories: { folderQueryRepository, fileCommandRepository },
3205
+ middlewares: { authMiddleware },
3206
+ action: { executeAction },
3207
+ schemas: { schemas }
3208
+ } = ctx;
3209
+ return async function fileCreateAction({
3210
+ formData
3211
+ }) {
3212
+ return executeAction(
3213
+ async () => {
3214
+ await authMiddleware.authenticate();
3215
+ const {
3216
+ key,
3217
+ checksum,
3218
+ fileMeta,
3219
+ width,
3220
+ height,
3221
+ duration,
3222
+ folder,
3223
+ folderKey,
3224
+ translations
3225
+ } = await fileCreateValidator(schemas).parseAsync(formData);
3226
+ let finalFolder = folder;
3227
+ if (folderKey && !folder) {
3228
+ finalFolder = await folderQueryRepository.findFull({
3229
+ key: folderKey
3230
+ });
3231
+ }
3232
+ const createdFile = await fileCommandRepository.create({
3233
+ key,
3234
+ checksum,
3235
+ fileMeta,
3236
+ ...width ? { width } : {},
3237
+ ...height ? { height } : {},
3238
+ ...duration ? { duration } : {},
3239
+ ...finalFolder ? { folder: finalFolder } : {},
3240
+ translations
3241
+ });
3242
+ return {
3243
+ i18nKey: "ok.store-ok",
3244
+ data: { file: createdFile }
3245
+ };
3246
+ },
3247
+ { type: "command" }
3248
+ );
3249
+ };
3250
+ }
3251
+
3252
+ // src/server/interfaces/actions/resources/file/commands/update/file-update-validator.ts
3253
+ var fileUpdateValidator = (schemas) => schemas.z.object({
3254
+ // core
3255
+ key: schemas.key().optional(),
3256
+ checksum: schemas.sha256Hash().optional(),
3257
+ // file meta
3258
+ fileMeta: schemas.z.object({
3259
+ type: schemas.text(),
3260
+ size: schemas.positiveNumber(),
3261
+ name: schemas.text()
3262
+ }),
3263
+ // media info
3264
+ width: schemas.positiveNumber().nullable(),
3265
+ height: schemas.positiveNumber().nullable(),
3266
+ duration: schemas.positiveNumber().nullable(),
3267
+ // ----------------------------------------------------------------------------
3268
+ // relations
3269
+ // ----------------------------------------------------------------------------
3270
+ folder: schemas.z.object({
3271
+ id: schemas.id().exist({ table: "folders", column: "id" })
3272
+ }).nullable().optional(),
3273
+ // ----------------------------------------------------------------------------
3274
+ // translation
3275
+ // ----------------------------------------------------------------------------
3276
+ translations: schemas.array(
3277
+ schemas.z.object({
3278
+ // core
3279
+ locale: schemas.locale(),
3280
+ // text
3281
+ name: schemas.text().nullable(),
3282
+ alt: schemas.text().nullable()
3283
+ })
3284
+ )
3285
+ });
3286
+ function createFileUpdateAction(ctx) {
3287
+ const {
3288
+ services: { storageService },
3289
+ repositories: { fileQueryRepository, fileCommandRepository },
3290
+ middlewares: { authMiddleware },
3291
+ action: { executeAction },
3292
+ schemas: { schemas }
3293
+ } = ctx;
3294
+ return async function fileUpdateAction({
3295
+ id,
3296
+ formData
3297
+ }) {
3298
+ return executeAction(
3299
+ async () => {
3300
+ await authMiddleware.authenticate();
3301
+ const { checksum, width, height, duration, translations } = await fileUpdateValidator(schemas).parseAsync(formData);
3302
+ const { folder, fileMeta, key: uploadedKey } = formData;
3303
+ const foundFile = await fileQueryRepository.findFull({ id });
3304
+ if (!foundFile) throw ServerError.notFound();
3305
+ const oldKey = foundFile.key;
3306
+ let newKey = oldKey;
3307
+ const folderChanged = folder?.key !== foundFile.folder?.key;
3308
+ if (uploadedKey) {
3309
+ newKey = uploadedKey;
3310
+ } else if (folderChanged) {
3311
+ const folderKey = folder?.key ?? "";
3312
+ newKey = path2.join(folderKey, path2.basename(oldKey));
3313
+ await storageService.move({ fromKey: oldKey, toKey: newKey });
3314
+ }
3315
+ const updatedFile = await fileCommandRepository.update({
3316
+ file: foundFile,
3317
+ id,
3318
+ key: newKey,
3319
+ checksum: checksum ?? foundFile.checksum,
3320
+ fileMeta,
3321
+ width,
3322
+ height,
3323
+ duration,
3324
+ folder,
3325
+ translations
3326
+ });
3327
+ if (uploadedKey) {
3328
+ await storageService.remove({ key: oldKey });
3329
+ }
3330
+ return {
3331
+ i18nKey: "ok.update-ok",
3332
+ data: { file: updatedFile }
3333
+ };
3334
+ },
3335
+ { type: "command" }
3336
+ );
3337
+ };
3338
+ }
3339
+
3340
+ // src/server/interfaces/actions/resources/file/commands/create-many/file-create-many-validator.ts
3341
+ var fileCreateManyValidator = (schemas) => schemas.z.object({
3342
+ uploadResults: schemas.array(
3343
+ schemas.z.object({
3344
+ // core
3345
+ key: schemas.key(),
3346
+ checksum: schemas.sha256Hash(),
3347
+ // file meta
3348
+ fileMeta: schemas.z.object({
3349
+ type: schemas.text(),
3350
+ size: schemas.positiveNumber(),
3351
+ name: schemas.text()
3352
+ }),
3353
+ // media info
3354
+ width: schemas.positiveNumber().nullable(),
3355
+ height: schemas.positiveNumber().nullable(),
3356
+ duration: schemas.positiveNumber().nullable(),
3357
+ // ----------------------------------------------------------------------------
3358
+ // translation
3359
+ // ----------------------------------------------------------------------------
3360
+ translations: schemas.array(
3361
+ schemas.z.object({
3362
+ // core
3363
+ locale: schemas.locale(),
3364
+ // text
3365
+ name: schemas.text().nullable(),
3366
+ alt: schemas.text().nullable()
3367
+ })
3368
+ )
3369
+ })
3370
+ ),
3371
+ // ----------------------------------------------------------------------------
3372
+ // relations
3373
+ // ----------------------------------------------------------------------------
3374
+ folder: schemas.z.object({ id: schemas.id().exist({ table: "folders", column: "id" }) }).nullable().optional()
3375
+ });
3376
+
3377
+ // node_modules/yocto-queue/index.js
3378
+ var Node = class {
3379
+ value;
3380
+ next;
3381
+ constructor(value) {
3382
+ this.value = value;
3383
+ }
3384
+ };
3385
+ var Queue = class {
3386
+ #head;
3387
+ #tail;
3388
+ #size;
3389
+ constructor() {
3390
+ this.clear();
3391
+ }
3392
+ enqueue(value) {
3393
+ const node = new Node(value);
3394
+ if (this.#head) {
3395
+ this.#tail.next = node;
3396
+ this.#tail = node;
3397
+ } else {
3398
+ this.#head = node;
3399
+ this.#tail = node;
3400
+ }
3401
+ this.#size++;
3402
+ }
3403
+ dequeue() {
3404
+ const current = this.#head;
3405
+ if (!current) {
3406
+ return;
3407
+ }
3408
+ this.#head = this.#head.next;
3409
+ this.#size--;
3410
+ if (!this.#head) {
3411
+ this.#tail = void 0;
3412
+ }
3413
+ return current.value;
3414
+ }
3415
+ peek() {
3416
+ if (!this.#head) {
3417
+ return;
3418
+ }
3419
+ return this.#head.value;
3420
+ }
3421
+ clear() {
3422
+ this.#head = void 0;
3423
+ this.#tail = void 0;
3424
+ this.#size = 0;
3425
+ }
3426
+ get size() {
3427
+ return this.#size;
3428
+ }
3429
+ *[Symbol.iterator]() {
3430
+ let current = this.#head;
3431
+ while (current) {
3432
+ yield current.value;
3433
+ current = current.next;
3434
+ }
3435
+ }
3436
+ *drain() {
3437
+ while (this.#head) {
3438
+ yield this.dequeue();
3439
+ }
3440
+ }
3441
+ };
3442
+
3443
+ // node_modules/p-limit/index.js
3444
+ function pLimit(concurrency) {
3445
+ validateConcurrency(concurrency);
3446
+ const queue = new Queue();
3447
+ let activeCount = 0;
3448
+ const resumeNext = () => {
3449
+ if (activeCount < concurrency && queue.size > 0) {
3450
+ activeCount++;
3451
+ queue.dequeue()();
3452
+ }
3453
+ };
3454
+ const next = () => {
3455
+ activeCount--;
3456
+ resumeNext();
3457
+ };
3458
+ const run = async (function_, resolve, arguments_) => {
3459
+ const result2 = (async () => function_(...arguments_))();
3460
+ resolve(result2);
3461
+ try {
3462
+ await result2;
3463
+ } catch {
3464
+ }
3465
+ next();
3466
+ };
3467
+ const enqueue = (function_, resolve, arguments_) => {
3468
+ new Promise((internalResolve) => {
3469
+ queue.enqueue(internalResolve);
3470
+ }).then(run.bind(void 0, function_, resolve, arguments_));
3471
+ if (activeCount < concurrency) {
3472
+ resumeNext();
3473
+ }
3474
+ };
3475
+ const generator = (function_, ...arguments_) => new Promise((resolve) => {
3476
+ enqueue(function_, resolve, arguments_);
3477
+ });
3478
+ Object.defineProperties(generator, {
3479
+ activeCount: {
3480
+ get: () => activeCount
3481
+ },
3482
+ pendingCount: {
3483
+ get: () => queue.size
3484
+ },
3485
+ clearQueue: {
3486
+ value() {
3487
+ queue.clear();
3488
+ }
3489
+ },
3490
+ concurrency: {
3491
+ get: () => concurrency,
3492
+ set(newConcurrency) {
3493
+ validateConcurrency(newConcurrency);
3494
+ concurrency = newConcurrency;
3495
+ queueMicrotask(() => {
3496
+ while (activeCount < concurrency && queue.size > 0) {
3497
+ resumeNext();
3498
+ }
3499
+ });
3500
+ }
3501
+ },
3502
+ map: {
3503
+ async value(iterable, function_) {
3504
+ const promises = Array.from(iterable, (value, index) => this(function_, value, index));
3505
+ return Promise.all(promises);
3506
+ }
3507
+ }
3508
+ });
3509
+ return generator;
3510
+ }
3511
+ function validateConcurrency(concurrency) {
3512
+ if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
3513
+ throw new TypeError("Expected `concurrency` to be a number from 1 and up");
3514
+ }
3515
+ }
3516
+
3517
+ // src/server/interfaces/actions/resources/file/commands/create-many/create-file-create-many-action.ts
3518
+ function createFileCreateManyAction(ctx) {
3519
+ const {
3520
+ repositories: { fileCommandRepository },
3521
+ middlewares: { authMiddleware },
3522
+ action: { executeAction },
3523
+ schemas: { schemas }
3524
+ } = ctx;
3525
+ return async function fileCreateManyAction({
3526
+ formData
3527
+ }) {
3528
+ return executeAction(
3529
+ async () => {
3530
+ await authMiddleware.authenticate();
3531
+ const { uploadResults, folder } = await fileCreateManyValidator(schemas).parseAsync(formData);
3532
+ const limit = pLimit(5);
3533
+ await Promise.all(
3534
+ uploadResults.map(
3535
+ (file) => limit(async () => {
3536
+ await fileCommandRepository.create({
3537
+ key: file.key,
3538
+ fileMeta: file.fileMeta,
3539
+ checksum: file.checksum,
3540
+ width: file.width,
3541
+ height: file.height,
3542
+ duration: file.duration,
3543
+ ...folder !== void 0 ? { folder } : {},
3544
+ translations: file.translations
3545
+ });
3546
+ })
3547
+ )
3548
+ );
3549
+ return {
3550
+ i18nKey: "ok.store-ok",
3551
+ data: { count: uploadResults.length }
3552
+ };
3553
+ },
3554
+ { type: "command" }
3555
+ );
3556
+ };
3557
+ }
3558
+
3559
+ // src/server/interfaces/actions/resources/file/commands/purge-many/file-purge-many-validator.ts
3560
+ var filePurgeManyValidator = (schemas) => schemas.z.object({
3561
+ ids: schemas.array(schemas.id().exist({ table: "files", column: "id" }))
3562
+ });
3563
+
3564
+ // src/server/interfaces/actions/resources/file/commands/purge-many/create-file-purge-many-action.ts
3565
+ function createFilePurgeManyAction(ctx) {
3566
+ const {
3567
+ services: { storageService },
3568
+ repositories: { fileQueryRepository, fileCommandRepository },
3569
+ middlewares: { authMiddleware },
3570
+ action: { executeAction },
3571
+ schemas: { schemas }
3572
+ } = ctx;
3573
+ return async function filePurgeManyAction({
3574
+ formData
3575
+ }) {
3576
+ return executeAction(
3577
+ async () => {
3578
+ await authMiddleware.authenticate();
3579
+ const { ids: ids2 } = await filePurgeManyValidator(schemas).parseAsync(formData);
3580
+ const foundFiles = await fileQueryRepository.findManyByIds({ ids: ids2 });
3581
+ if (foundFiles.length === 0) throw ServerError.notFound();
3582
+ if (foundFiles.some((file) => isFileLocked(file))) {
3583
+ throw new ServerError({ i18nKey: "error.files-destroy-is-locked" });
3584
+ }
3585
+ const keys = foundFiles.map((file) => file.key);
3586
+ await Promise.all(
3587
+ keys.map(async (key) => await storageService.remove({ key }))
3588
+ );
3589
+ await Promise.all(
3590
+ foundFiles.map(
3591
+ async (file) => await fileCommandRepository.delete({ id: file.id })
3592
+ )
3593
+ );
3594
+ return {
3595
+ i18nKey: "ok.destroy-ok"
3596
+ };
3597
+ },
3598
+ { type: "command" }
3599
+ );
3600
+ };
3601
+ }
3602
+
3603
+ // src/server/interfaces/actions/resources/file/commands/restore-many/file-create-restore-validator.ts
3604
+ var fileRestoreManyValidator = (schemas) => schemas.z.object({
3605
+ ids: schemas.array(schemas.id().exist({ table: "files", column: "id" }))
3606
+ });
3607
+
3608
+ // src/server/interfaces/actions/resources/file/commands/restore-many/create-file-restore-many-action.ts
3609
+ function createFileRestoreManyAction(ctx) {
3610
+ const {
3611
+ repositories: { fileCommandRepository },
3612
+ middlewares: { authMiddleware },
3613
+ action: { executeAction },
3614
+ schemas: { schemas }
3615
+ } = ctx;
3616
+ return async function fileRestoreManyAction({
3617
+ formData
3618
+ }) {
3619
+ return executeAction(
3620
+ async () => {
3621
+ await authMiddleware.authenticate();
3622
+ const { ids: ids2 } = await fileRestoreManyValidator(schemas).parseAsync(formData);
3623
+ await fileCommandRepository.restoreMany({ ids: ids2 });
3624
+ return {
3625
+ i18nKey: "ok.restore-ok"
3626
+ };
3627
+ },
3628
+ { type: "command" }
3629
+ );
3630
+ };
3631
+ }
3632
+
3633
+ // src/server/interfaces/actions/resources/file/commands/soft-delete/create-file-soft-delete-action.ts
3634
+ function createFileSoftDeleteAction(ctx) {
3635
+ const {
3636
+ repositories: { fileQueryRepository, fileCommandRepository },
3637
+ middlewares: { authMiddleware },
3638
+ action: { executeAction }
3639
+ } = ctx;
3640
+ return async function fileSoftDeleteAction({ id }) {
3641
+ return executeAction(
3642
+ async () => {
3643
+ await authMiddleware.authenticate();
3644
+ const foundFile = await fileQueryRepository.findFull({ id });
3645
+ if (!foundFile) throw ServerError.notFound();
3646
+ if (isFileLocked(foundFile)) {
3647
+ throw new ServerError({ i18nKey: "error.files-destroy-is-locked" });
3648
+ }
3649
+ await fileCommandRepository.softDelete({ id });
3650
+ return {
3651
+ i18nKey: "ok.destroy-ok"
3652
+ };
3653
+ },
3654
+ { type: "command" }
3655
+ );
3656
+ };
3657
+ }
3658
+
3659
+ // src/server/interfaces/actions/resources/file/commands/soft-delete-many/file-soft-delete-many-validator.ts
3660
+ var fileSoftDeleteManyValidator = (schemas) => schemas.z.object({
3661
+ ids: schemas.array(schemas.id().exist({ table: "files", column: "id" }))
3662
+ });
3663
+
3664
+ // src/server/interfaces/actions/resources/file/commands/soft-delete-many/create-file-soft-delete-many-action.ts
3665
+ function createFileSoftDeleteManyAction(ctx) {
3666
+ const {
3667
+ repositories: { fileQueryRepository, fileCommandRepository },
3668
+ middlewares: { authMiddleware },
3669
+ action: { executeAction },
3670
+ schemas: { schemas }
3671
+ } = ctx;
3672
+ return async function fileSoftDeleteManyAction({
3673
+ formData
3674
+ }) {
3675
+ return executeAction(
3676
+ async () => {
3677
+ await authMiddleware.authenticate();
3678
+ const { ids: ids2 } = await fileSoftDeleteManyValidator(schemas).parseAsync(formData);
3679
+ const foundFiles = await fileQueryRepository.findManyByIds({ ids: ids2 });
3680
+ if (foundFiles.length === 0) throw ServerError.notFound();
3681
+ const targetIds = foundFiles.filter((f) => !f.isLocked).map((f) => f.id);
3682
+ const hasLockedFile = ids2.length !== targetIds.length;
3683
+ const count = await fileCommandRepository.softDeleteMany({
3684
+ ids: targetIds
3685
+ });
3686
+ if (count === 0) {
3687
+ throw new ServerError({ i18nKey: "error.files-batch-destroy-no" });
3688
+ }
3689
+ return {
3690
+ i18nKey: hasLockedFile ? "ok.destroy-ok" : "ok.files-batch-destroy-has-locked"
3691
+ };
3692
+ },
3693
+ { type: "command" }
3694
+ );
3695
+ };
3696
+ }
3697
+
3698
+ // src/server/interfaces/actions/resources/file/querires/create-file-find-full-action.ts
3699
+ function createFileFindFullAction(ctx) {
3700
+ const {
3701
+ repositories: { fileQueryRepository },
3702
+ action: { executeAction }
3703
+ } = ctx;
3704
+ return async function FileFindFullAction(params) {
3705
+ return executeAction(
3706
+ async () => {
3707
+ const file = await fileQueryRepository.findFull(params);
3708
+ if (!file) throw ServerError.notFound();
3709
+ return {
3710
+ data: { file }
3711
+ };
3712
+ },
3713
+ {
3714
+ type: "query",
3715
+ key: ["file", "findFullAction", params.id]
3716
+ }
3717
+ );
3718
+ };
3719
+ }
3720
+
3721
+ // src/server/interfaces/actions/resources/file/querires/create-file-find-list-cards-action.ts
3722
+ function createFileFindListCardsAction(ctx) {
3723
+ const {
3724
+ repositories: { fileQueryRepository },
3725
+ middlewares: { authMiddleware },
3726
+ action: { executeAction }
3727
+ } = ctx;
3728
+ return async function fileFindFullAction(params) {
3729
+ return executeAction(
3730
+ async ({ locale }) => {
3731
+ await authMiddleware.authenticate();
3732
+ const { items, total } = await fileQueryRepository.findListCards({
3733
+ ...params,
3734
+ locale
3735
+ });
3736
+ return {
3737
+ data: { items, total }
3738
+ };
3739
+ },
3740
+ {
3741
+ type: "query",
3742
+ key: [
3743
+ "file",
3744
+ "findListCardsAction",
3745
+ params.searchString,
3746
+ params.type,
3747
+ params.isDeleted,
3748
+ params.isLocked,
3749
+ params.folderId,
3750
+ ...params.fileIds ?? [],
3751
+ params.page,
3752
+ params.pageSize
3753
+ ]
3754
+ }
3755
+ );
3756
+ };
3757
+ }
3758
+
3759
+ // src/server/interfaces/actions/resources/folder/commands/create/folder-create-validator.ts
3760
+ var folderCreateValidator = (schemas) => schemas.z.object({
3761
+ // core
3762
+ key: schemas.key().unique({ table: "folders", column: "key" }),
3763
+ name: schemas.pathSegment(),
3764
+ // ----------------------------------------------------------------------------
3765
+ // relations
3766
+ // ----------------------------------------------------------------------------
3767
+ // Folder
3768
+ parentFolder: schemas.z.object({ id: schemas.id().exist({ table: "folders", column: "id" }) }).nullable()
3769
+ });
3770
+ function createFolderCreateAction(ctx) {
3771
+ const {
3772
+ repositories: { folderCommandRepository },
3773
+ middlewares: { authMiddleware },
3774
+ action: { executeAction },
3775
+ schemas: { schemas }
3776
+ } = ctx;
3777
+ return async function folderCreateAction({
3778
+ formData
3779
+ }) {
3780
+ return executeAction(
3781
+ async () => {
3782
+ await authMiddleware.authenticate();
3783
+ const combinedKey = path2.join(
3784
+ formData.parentFolder?.key ?? "",
3785
+ formData.name
3786
+ );
3787
+ const { name } = await folderCreateValidator(schemas).parseAsync({
3788
+ ...formData,
3789
+ key: combinedKey
3790
+ });
3791
+ const createdFolder = await folderCommandRepository.create({
3792
+ key: combinedKey,
3793
+ name,
3794
+ parentFolder: formData.parentFolder
3795
+ });
3796
+ return {
3797
+ i18nKey: "ok.store-ok",
3798
+ data: { folder: createdFolder }
3799
+ };
3800
+ },
3801
+ { type: "command" }
3802
+ );
3803
+ };
3804
+ }
3805
+
3806
+ // src/server/interfaces/actions/resources/folder/commands/update/folder-update-validator.ts
3807
+ var folderUpdateValidator = (schemas, id) => schemas.z.object({
3808
+ // core
3809
+ key: schemas.key().unique({
3810
+ table: "folders",
3811
+ column: "key",
3812
+ excludeSelf: { name: "id", value: id }
3813
+ }),
3814
+ name: schemas.pathSegment(),
3815
+ // ----------------------------------------------------------------------------
3816
+ // relations
3817
+ // ----------------------------------------------------------------------------
3818
+ // Folder
3819
+ parentFolder: schemas.z.object({ id: schemas.id().exist({ table: "folders", column: "id" }) }).nullable()
3820
+ });
3821
+ function createFolderUpdateAction(ctx) {
3822
+ const {
3823
+ repositories: { folderCommandRepository },
3824
+ middlewares: { authMiddleware },
3825
+ action: { executeAction },
3826
+ schemas: { schemas }
3827
+ } = ctx;
3828
+ return async function folderUpdateAction({
3829
+ id,
3830
+ formData
3831
+ }) {
3832
+ return executeAction(
3833
+ async () => {
3834
+ await authMiddleware.authenticate();
3835
+ const combinedKey = path2.join(
3836
+ formData.parentFolder?.key ?? "",
3837
+ formData.name
3838
+ );
3839
+ const { name } = await folderUpdateValidator(schemas, id).parseAsync({
3840
+ ...formData,
3841
+ key: combinedKey
3842
+ });
3843
+ const updateddFolder = await folderCommandRepository.update({
3844
+ id,
3845
+ key: combinedKey,
3846
+ name,
3847
+ parentFolder: formData.parentFolder
3848
+ });
3849
+ return {
3850
+ i18nKey: "ok.update-ok",
3851
+ data: { folder: updateddFolder }
3852
+ };
3853
+ },
3854
+ { type: "command" }
3855
+ );
3856
+ };
3857
+ }
3858
+
3859
+ // src/server/interfaces/actions/resources/folder/commands/delete/create-folder-delete-action.ts
3860
+ function createFolderDeleteAction(ctx) {
3861
+ const {
3862
+ repositories: { folderQueryRepository, folderCommandRepository },
3863
+ middlewares: { authMiddleware },
3864
+ action: { executeAction }
3865
+ } = ctx;
3866
+ return async function folderDeleteAction({ id }) {
3867
+ return executeAction(
3868
+ async () => {
3869
+ await authMiddleware.authenticate();
3870
+ const foundFolder = await folderQueryRepository.findFull({ id });
3871
+ if (!foundFolder) throw ServerError.notFound();
3872
+ if (isFolderLocked(foundFolder)) {
3873
+ throw ServerError.forbidden();
3874
+ }
3875
+ await folderCommandRepository.delete({ id });
3876
+ return {
3877
+ i18nKey: "ok.destroy-ok"
3878
+ };
3879
+ },
3880
+ { type: "command" }
3881
+ );
3882
+ };
3883
+ }
3884
+
3885
+ // src/server/interfaces/actions/resources/folder/queries/create-folder-find-full-action.ts
3886
+ function createFolderFindFullAction(ctx) {
3887
+ const {
3888
+ repositories: { folderQueryRepository, fileQueryRepository },
3889
+ middlewares: { authMiddleware },
3890
+ action: { executeAction }
3891
+ } = ctx;
3892
+ return async function folderFindFullAction(params) {
3893
+ return executeAction(
3894
+ async (translator) => {
3895
+ await authMiddleware.authenticate();
3896
+ const resolvedKey = params.key || ROOT_FOLDER_ID;
3897
+ const identity = "id" in params ? params.id : resolvedKey;
3898
+ const isAtRoot = identity === ROOT_FOLDER_ID;
3899
+ let folder = ROOT_FOLDER;
3900
+ if (isAtRoot) {
3901
+ const { items: subFolders } = await folderQueryRepository.findListCards({
3902
+ parentFolderId: ROOT_FOLDER_ID
3903
+ });
3904
+ folder.subFolders = subFolders;
3905
+ const { items: files } = await fileQueryRepository.findListCards({
3906
+ locale: translator.locale,
3907
+ folderId: ROOT_FOLDER_ID
3908
+ });
3909
+ folder.files = files;
3910
+ } else {
3911
+ const found = await folderQueryRepository.findFull(params);
3912
+ if (!found) throw ServerError.notFound();
3913
+ folder = found;
3914
+ }
3915
+ return { data: { folder } };
3916
+ },
3917
+ {
3918
+ type: "query",
3919
+ key: ["file", "findFullAction", params.id, params.key]
3920
+ }
3921
+ );
3922
+ };
3923
+ }
3924
+
3925
+ // src/server/interfaces/actions/resources/folder/queries/create-folder-find-list-cards-action.ts
3926
+ function createFolderFindListCardsAction(ctx) {
3927
+ const {
3928
+ repositories: { folderQueryRepository },
3929
+ middlewares: { authMiddleware },
3930
+ action: { executeAction }
3931
+ } = ctx;
3932
+ return async function folderFindFullAction(params) {
3933
+ return executeAction(
3934
+ async () => {
3935
+ await authMiddleware.authenticate();
3936
+ const { items, total } = await folderQueryRepository.findListCards(params);
3937
+ return {
3938
+ data: { items, total }
3939
+ };
3940
+ },
3941
+ {
3942
+ type: "query",
3943
+ key: [
3944
+ "folder",
3945
+ "findListCardsAction",
3946
+ params.searchString,
3947
+ params.parentFolderId,
3948
+ ...params.folderIds ?? [],
3949
+ params.page,
3950
+ params.pageSize
3951
+ ]
3952
+ }
3953
+ );
3954
+ };
3955
+ }
3956
+
3957
+ // src/server/interfaces/actions/resources/post/commands/create/post-create-validator.ts
3958
+ var postCreateValidator = ({
3959
+ type,
3960
+ topicId,
3961
+ schemas,
3962
+ tocItemSchema
3963
+ }) => schemas.z.object({
3964
+ type: schemas.z.enum(POST_TYPES),
3965
+ // ----------------------------------------------------------------------------
3966
+ // states
3967
+ // ----------------------------------------------------------------------------
3968
+ isLocked: schemas.z.boolean(),
3969
+ isActive: schemas.z.boolean(),
3970
+ isIndexActive: schemas.z.boolean(),
3971
+ index: schemas.positiveNumber().nullable(),
3972
+ isSlugActive: schemas.z.boolean(),
3973
+ slug: schemas.slug().unique({
3974
+ table: "posts",
3975
+ column: "slug",
3976
+ scope: [
3977
+ { name: "type", value: type, cast: "PostType" },
3978
+ ...topicId ? [{ name: "topic_id", value: topicId }] : []
3979
+ // Use for: [post], [post:category]
3980
+ ]
3981
+ }).nullable(),
3982
+ isFeatured: schemas.z.boolean(),
3983
+ isShownOnHome: schemas.z.boolean(),
3984
+ // ----------------------------------------------------------------------------
3985
+ // relations
3986
+ // ----------------------------------------------------------------------------
3987
+ // Admin
3988
+ author: schemas.singleItem({ table: "admins", column: "id" }).nullable(),
3989
+ // Picked by modal
3990
+ // Post
3991
+ topicId: schemas.id().exist({ table: "posts", column: "id" }).nullable(),
3992
+ parents: schemas.multiItems({ table: "posts", column: "id" }),
3993
+ // Picked by modal
3994
+ tags: schemas.multiItems({ table: "posts", column: "id" }),
3995
+ // Picked by modal
3996
+ relatedPosts: schemas.multiItems({ table: "posts", column: "id" }),
3997
+ // Picked by modal
3998
+ // File
3999
+ coverImage: schemas.singleItem({ table: "files", column: "id" }),
4000
+ // Picked by modal
4001
+ contentImageIds: schemas.array(
4002
+ schemas.id().exist({ table: "files", column: "id" })
4003
+ ),
4004
+ // -----------------------------------------------------------------------
4005
+ // --- custom fields
4006
+ // -----------------------------------------------------------------------
4007
+ // states
4008
+ state1: schemas.z.boolean(),
4009
+ state2: schemas.z.boolean(),
4010
+ state3: schemas.z.boolean(),
4011
+ state4: schemas.z.boolean(),
4012
+ state5: schemas.z.boolean(),
4013
+ state6: schemas.z.boolean(),
4014
+ state7: schemas.z.boolean(),
4015
+ state8: schemas.z.boolean(),
4016
+ state9: schemas.z.boolean(),
4017
+ state10: schemas.z.boolean(),
4018
+ // multi images
4019
+ images1: schemas.multiItems({ table: "files", column: "id" }),
4020
+ images2: schemas.multiItems({ table: "files", column: "id" }),
4021
+ // single images
4022
+ image1: schemas.singleItem({ table: "files", column: "id" }),
4023
+ image2: schemas.singleItem({ table: "files", column: "id" }),
4024
+ image3: schemas.singleItem({ table: "files", column: "id" }),
4025
+ image4: schemas.singleItem({ table: "files", column: "id" }),
4026
+ // text
4027
+ text1: schemas.text().nullable(),
4028
+ text2: schemas.text().nullable(),
4029
+ text3: schemas.text().nullable(),
4030
+ text4: schemas.text().nullable(),
4031
+ text5: schemas.text().nullable(),
4032
+ text6: schemas.text().nullable(),
4033
+ text7: schemas.text().nullable(),
4034
+ text8: schemas.text().nullable(),
4035
+ text9: schemas.text().nullable(),
4036
+ text10: schemas.text().nullable(),
4037
+ // json array
4038
+ data1: schemas.array(schemas.z.any()),
4039
+ data2: schemas.array(schemas.z.any()),
4040
+ data3: schemas.array(schemas.z.any()),
4041
+ data4: schemas.array(schemas.z.any()),
4042
+ // ----------------------------------------------------------------------------
4043
+ // translation
4044
+ // ----------------------------------------------------------------------------
4045
+ translations: schemas.array(
4046
+ schemas.z.object({
4047
+ locale: schemas.locale(),
4048
+ // text
4049
+ title: schemas.text().nullable(),
4050
+ subtitle: schemas.text().nullable(),
4051
+ summary: schemas.text().nullable(),
4052
+ description: schemas.text().nullable(),
4053
+ content: schemas.text().nullable(),
4054
+ // better seo
4055
+ externalLinks: schemas.array(
4056
+ schemas.z.object({
4057
+ name: schemas.text(),
4058
+ href: schemas.text()
4059
+ })
4060
+ ),
4061
+ faq: schemas.array(
4062
+ schemas.z.object({
4063
+ question: schemas.text(),
4064
+ answer: schemas.text()
4065
+ })
4066
+ ),
4067
+ toc: schemas.array(tocItemSchema),
4068
+ // extra
4069
+ readTime: schemas.positiveNumber().nullable(),
4070
+ wordCount: schemas.positiveNumber(),
4071
+ // -------------------------------------------
4072
+ // --- custom fields
4073
+ // -------------------------------------------
4074
+ // text
4075
+ text1: schemas.text().nullable(),
4076
+ text2: schemas.text().nullable(),
4077
+ text3: schemas.text().nullable(),
4078
+ text4: schemas.text().nullable(),
4079
+ text5: schemas.text().nullable(),
4080
+ text6: schemas.text().nullable(),
4081
+ text7: schemas.text().nullable(),
4082
+ text8: schemas.text().nullable(),
4083
+ text9: schemas.text().nullable(),
4084
+ text10: schemas.text().nullable(),
4085
+ // json array
4086
+ data1: schemas.array(schemas.z.any()),
4087
+ data2: schemas.array(schemas.z.any()),
4088
+ data3: schemas.array(schemas.z.any()),
4089
+ data4: schemas.array(schemas.z.any())
4090
+ })
4091
+ )
4092
+ }).transform((obj) => ({
4093
+ ...obj,
4094
+ // states
4095
+ index: obj.isIndexActive ? obj.index : null,
4096
+ slug: obj.isSlugActive ? obj.slug : null,
4097
+ // translation
4098
+ translations: obj.translations.map((t) => ({
4099
+ ...t,
4100
+ readTime: t.readTime !== 0 ? t.readTime : null
4101
+ }))
4102
+ }));
4103
+
4104
+ // src/server/interfaces/actions/resources/post/commands/create/create-post-create-action.ts
4105
+ function createPostCreateAction(ctx) {
4106
+ const {
4107
+ repositories: { postCommandRepository },
4108
+ middlewares: { authMiddleware },
4109
+ action: { executeAction },
4110
+ schemas: { schemas, tocItemSchema }
4111
+ } = ctx;
4112
+ return async function postCreateAction({
4113
+ formData
4114
+ }) {
4115
+ return executeAction(
4116
+ async () => {
4117
+ await authMiddleware.authenticate();
4118
+ const validatedPayload = await postCreateValidator({
4119
+ schemas,
4120
+ tocItemSchema,
4121
+ type: formData.type,
4122
+ topicId: formData.topicId
4123
+ }).parseAsync(formData);
4124
+ const created = await postCommandRepository.create(validatedPayload);
4125
+ return {
4126
+ i18nKey: "ok.store-ok",
4127
+ data: { post: created }
4128
+ };
4129
+ },
4130
+ { type: "command" }
4131
+ );
4132
+ };
4133
+ }
4134
+
4135
+ // src/server/interfaces/actions/resources/post/commands/update/post-update-validator.ts
4136
+ var postUpdateValidator = ({
4137
+ id,
4138
+ type,
4139
+ topicId,
4140
+ schemas,
4141
+ tocItemSchema
4142
+ }) => schemas.z.object({
4143
+ type: schemas.z.enum(POST_TYPES),
4144
+ // depth: schemas.z.number().nullable(),
4145
+ // ----------------------------------------------------------------------------
4146
+ // states
4147
+ // ----------------------------------------------------------------------------
4148
+ isLocked: schemas.z.boolean(),
4149
+ isActive: schemas.z.boolean(),
4150
+ isIndexActive: schemas.z.boolean(),
4151
+ index: schemas.positiveNumber().nullable(),
4152
+ isSlugActive: schemas.z.boolean(),
4153
+ slug: schemas.slug().unique({
4154
+ table: "posts",
4155
+ column: "slug",
4156
+ scope: [
4157
+ { name: "type", value: type, cast: "PostType" },
4158
+ ...topicId ? [{ name: "topic_id", value: topicId }] : []
4159
+ // Use for: [post], [post:category]
4160
+ ],
4161
+ excludeSelf: { name: "id", value: id }
4162
+ }).nullable(),
4163
+ isFeatured: schemas.z.boolean(),
4164
+ isShownOnHome: schemas.z.boolean(),
4165
+ // ----------------------------------------------------------------------------
4166
+ // relations
4167
+ // ----------------------------------------------------------------------------
4168
+ // Admin
4169
+ author: schemas.singleItem({ table: "admins", column: "id" }),
4170
+ // Picked by modal
4171
+ // Post
4172
+ topicId: schemas.id().exist({ table: "posts", column: "id" }).nullable(),
4173
+ parents: schemas.multiItems({ table: "posts", column: "id" }),
4174
+ // Picked by modal
4175
+ tags: schemas.multiItems({ table: "posts", column: "id" }),
4176
+ // Picked by modal
4177
+ relatedPosts: schemas.multiItems({ table: "posts", column: "id" }),
4178
+ // Picked by modal
4179
+ // File
4180
+ coverImage: schemas.singleItem({ table: "files", column: "id" }),
4181
+ // Picked by modal
4182
+ contentImageIds: schemas.array(
4183
+ schemas.id().exist({ table: "files", column: "id" })
4184
+ ),
4185
+ // -----------------------------------------------------------------------
4186
+ // --- custom fields
4187
+ // -----------------------------------------------------------------------
4188
+ // states
4189
+ state1: schemas.z.boolean(),
4190
+ state2: schemas.z.boolean(),
4191
+ state3: schemas.z.boolean(),
4192
+ state4: schemas.z.boolean(),
4193
+ state5: schemas.z.boolean(),
4194
+ state6: schemas.z.boolean(),
4195
+ state7: schemas.z.boolean(),
4196
+ state8: schemas.z.boolean(),
4197
+ state9: schemas.z.boolean(),
4198
+ state10: schemas.z.boolean(),
4199
+ // multi images
4200
+ images1: schemas.multiItems({ table: "files", column: "id" }),
4201
+ images2: schemas.multiItems({ table: "files", column: "id" }),
4202
+ // single images
4203
+ image1: schemas.singleItem({ table: "files", column: "id" }),
4204
+ image2: schemas.singleItem({ table: "files", column: "id" }),
4205
+ image3: schemas.singleItem({ table: "files", column: "id" }),
4206
+ image4: schemas.singleItem({ table: "files", column: "id" }),
4207
+ // text
4208
+ text1: schemas.text().nullable(),
4209
+ text2: schemas.text().nullable(),
4210
+ text3: schemas.text().nullable(),
4211
+ text4: schemas.text().nullable(),
4212
+ text5: schemas.text().nullable(),
4213
+ text6: schemas.text().nullable(),
4214
+ text7: schemas.text().nullable(),
4215
+ text8: schemas.text().nullable(),
4216
+ text9: schemas.text().nullable(),
4217
+ text10: schemas.text().nullable(),
4218
+ // json array
4219
+ data1: schemas.array(schemas.z.any()),
4220
+ data2: schemas.array(schemas.z.any()),
4221
+ data3: schemas.array(schemas.z.any()),
4222
+ data4: schemas.array(schemas.z.any()),
4223
+ // ----------------------------------------------------------------------------
4224
+ // translation
4225
+ // ----------------------------------------------------------------------------
4226
+ translations: schemas.array(
4227
+ schemas.z.object({
4228
+ locale: schemas.locale(),
4229
+ // text
4230
+ title: schemas.text().nullable(),
4231
+ subtitle: schemas.text().nullable(),
4232
+ summary: schemas.text().nullable(),
4233
+ description: schemas.text().nullable(),
4234
+ content: schemas.text().nullable(),
4235
+ // better seo
4236
+ externalLinks: schemas.array(
4237
+ schemas.z.object({
4238
+ name: schemas.text(),
4239
+ href: schemas.text()
4240
+ })
4241
+ ),
4242
+ faq: schemas.array(
4243
+ schemas.z.object({
4244
+ question: schemas.text(),
4245
+ answer: schemas.text()
4246
+ })
4247
+ ),
4248
+ toc: schemas.array(tocItemSchema),
4249
+ // extra
4250
+ readTime: schemas.positiveNumber().nullable(),
4251
+ wordCount: schemas.positiveNumber(),
4252
+ // -------------------------------------------
4253
+ // --- custom fields
4254
+ // -------------------------------------------
4255
+ // text
4256
+ text1: schemas.text().nullable(),
4257
+ text2: schemas.text().nullable(),
4258
+ text3: schemas.text().nullable(),
4259
+ text4: schemas.text().nullable(),
4260
+ text5: schemas.text().nullable(),
4261
+ text6: schemas.text().nullable(),
4262
+ text7: schemas.text().nullable(),
4263
+ text8: schemas.text().nullable(),
4264
+ text9: schemas.text().nullable(),
4265
+ text10: schemas.text().nullable(),
4266
+ // json array
4267
+ data1: schemas.array(schemas.z.any()),
4268
+ data2: schemas.array(schemas.z.any()),
4269
+ data3: schemas.array(schemas.z.any()),
4270
+ data4: schemas.array(schemas.z.any())
4271
+ })
4272
+ )
4273
+ }).transform((obj) => ({
4274
+ ...obj,
4275
+ // states
4276
+ index: obj.isIndexActive ? obj.index : null,
4277
+ slug: obj.isSlugActive ? obj.slug : null,
4278
+ // translation
4279
+ translations: obj.translations.map((t) => ({
4280
+ ...t,
4281
+ readTime: t.readTime !== 0 ? t.readTime : null
4282
+ }))
4283
+ }));
4284
+
4285
+ // src/server/interfaces/actions/resources/post/commands/update/create-post-update-action.ts
4286
+ function createPostUpdateAction(ctx) {
4287
+ const {
4288
+ repositories: { postCommandRepository },
4289
+ middlewares: { authMiddleware },
4290
+ action: { executeAction },
4291
+ schemas: { schemas, tocItemSchema }
4292
+ } = ctx;
4293
+ return async function postUpdateAction({
4294
+ id,
4295
+ formData
4296
+ }) {
4297
+ return executeAction(
4298
+ async () => {
4299
+ await authMiddleware.authenticate();
4300
+ const validatedPayload = await postUpdateValidator({
4301
+ id,
4302
+ type: formData.type,
4303
+ topicId: formData.topicId,
4304
+ schemas,
4305
+ tocItemSchema
4306
+ }).parseAsync(formData);
4307
+ const updatedPost = await postCommandRepository.update({
4308
+ id,
4309
+ ...validatedPayload
4310
+ });
4311
+ return {
4312
+ i18nKey: "ok.update-ok",
4313
+ data: { post: updatedPost }
4314
+ };
4315
+ },
4316
+ { type: "command" }
4317
+ );
4318
+ };
4319
+ }
4320
+
4321
+ // src/server/interfaces/actions/resources/post/commands/delete/create-post-delete-action.ts
4322
+ function createPostDeleteAction(ctx) {
4323
+ const {
4324
+ repositories: { postQueryRepository, postCommandRepository },
4325
+ middlewares: { authMiddleware },
4326
+ action: { executeAction }
4327
+ } = ctx;
4328
+ return async function postDeleteAction({ id }) {
4329
+ return executeAction(
4330
+ async () => {
4331
+ await authMiddleware.authenticate();
4332
+ const post = await postQueryRepository.find({ id });
4333
+ if (!post) throw ServerError.notFound();
4334
+ await postCommandRepository.delete({ id: post.id });
4335
+ return {
4336
+ i18nKey: "ok.destroy-ok"
4337
+ };
4338
+ },
4339
+ { type: "command" }
4340
+ );
4341
+ };
4342
+ }
4343
+
4344
+ // src/server/interfaces/actions/resources/post/queries/create-post-find-action.ts
4345
+ function createPostFindAction(ctx) {
4346
+ const {
4347
+ repositories: { postQueryRepository },
4348
+ action: { executeAction }
4349
+ } = ctx;
4350
+ return async function postFindAction(params) {
4351
+ return executeAction(
4352
+ async () => {
4353
+ const found = await postQueryRepository.find(params);
4354
+ if (!found) throw ServerError.notFound();
4355
+ return {
4356
+ data: { post: found }
4357
+ };
4358
+ },
4359
+ {
4360
+ type: "query",
4361
+ key: [
4362
+ "post",
4363
+ "findAction",
4364
+ params.id,
4365
+ params.type,
4366
+ params.slug,
4367
+ params.isActive,
4368
+ params.topicSlug
4369
+ ]
4370
+ }
4371
+ );
4372
+ };
4373
+ }
4374
+
4375
+ // src/server/interfaces/actions/resources/post/queries/create-post-find-full-action.ts
4376
+ function createPostFindFullAction(ctx) {
4377
+ const {
4378
+ repositories: { postQueryRepository },
4379
+ action: { executeAction }
4380
+ } = ctx;
4381
+ return async function postFindFullAction(params) {
4382
+ return executeAction(
4383
+ async () => {
4384
+ const found = await postQueryRepository.findFull(params);
4385
+ if (!found) throw ServerError.notFound();
4386
+ return {
4387
+ data: { post: found }
4388
+ };
4389
+ },
4390
+ {
4391
+ type: "query",
4392
+ key: [
4393
+ "post",
4394
+ "findFullAction",
4395
+ params.id,
4396
+ params.type,
4397
+ params.slug,
4398
+ params.isActive,
4399
+ params.topicSlug
4400
+ ]
4401
+ }
4402
+ );
4403
+ };
4404
+ }
4405
+
4406
+ // src/server/interfaces/actions/resources/post/queries/create-post-find-list-cards-action.ts
4407
+ function createPostFindListCardsAction(ctx) {
4408
+ const {
4409
+ repositories: { postQueryRepository },
4410
+ middlewares: { authMiddleware },
4411
+ action: { executeAction }
4412
+ } = ctx;
4413
+ return async function postFindFullAction(params) {
4414
+ return executeAction(
4415
+ async ({ locale }) => {
4416
+ await authMiddleware.authenticate();
4417
+ const { items, total } = await postQueryRepository.findListCards({
4418
+ ...params,
4419
+ locale
4420
+ });
4421
+ return {
4422
+ data: { items, total }
4423
+ };
4424
+ },
4425
+ {
4426
+ type: "query",
4427
+ key: [
4428
+ "post",
4429
+ "findListCardsAction",
4430
+ params.searchString,
4431
+ params.type,
4432
+ params.isActive,
4433
+ params.isIndexActive,
4434
+ params.isSlugActive,
4435
+ params.isFeatured,
4436
+ params.isShownOnHome,
4437
+ params.state1,
4438
+ params.state2,
4439
+ params.state3,
4440
+ params.state4,
4441
+ params.state5,
4442
+ params.state6,
4443
+ params.state7,
4444
+ params.state8,
4445
+ params.state9,
4446
+ params.state10,
4447
+ params.topicId,
4448
+ params.topicSlug,
4449
+ params.categoryId,
4450
+ params.categorySlug,
4451
+ ...params.postIds ?? [],
4452
+ ...params.excludeIds ?? [],
4453
+ params.page,
4454
+ params.pageSize
4455
+ ]
4456
+ }
4457
+ );
4458
+ };
4459
+ }
4460
+
4461
+ // src/server/interfaces/actions/resources/post/queries/create-post-find-many-action.ts
4462
+ function createPostFindManyAction(ctx) {
4463
+ const {
4464
+ repositories: { postQueryRepository },
4465
+ action: { executeAction }
4466
+ } = ctx;
4467
+ return async function postFindManyAction(params) {
4468
+ return executeAction(
4469
+ async () => {
4470
+ const found = await postQueryRepository.findMany(params);
4471
+ return {
4472
+ data: { posts: found }
4473
+ };
4474
+ },
4475
+ {
4476
+ type: "query",
4477
+ key: ["post", "findManyAction", params.type, params.topicId]
4478
+ }
4479
+ );
4480
+ };
4481
+ }
4482
+
4483
+ // src/server/applications/auth/create-auth-use-cases.ts
4484
+ function createAuthUseCases({
4485
+ prisma,
4486
+ adminQueryRepository,
4487
+ adminRefreshTokenCommandRepository,
4488
+ jwtService,
4489
+ argon2Service,
4490
+ cryptoService,
4491
+ cookieService,
4492
+ config
4493
+ }) {
4494
+ async function verifyCredentials({
4495
+ email,
4496
+ password
4497
+ }) {
4498
+ const found = await adminQueryRepository.findWithPasswordHash({ email });
4499
+ if (found) {
4500
+ const isValid = await argon2Service.verify(found.passwordHash, password);
4501
+ if (isValid) return found;
4502
+ }
4503
+ throw new ServerError({ i18nKey: "error.credentials-incorrect" });
4504
+ }
4505
+ async function updatePassword({
4506
+ email,
4507
+ password
4508
+ }) {
4509
+ const updatedAdmin = await prisma.admin.update({
4510
+ where: { email },
4511
+ data: { passwordHash: await argon2Service.hash(password) }
4512
+ });
4513
+ return updatedAdmin;
4514
+ }
4515
+ async function createRefreshToken({
4516
+ admin,
4517
+ deviceInfo,
4518
+ ip
4519
+ }) {
4520
+ const token = cryptoService.generateToken();
4521
+ const tokenHash = cryptoService.hash(token);
4522
+ await adminRefreshTokenCommandRepository.create({
4523
+ tokenHash,
4524
+ ip,
4525
+ deviceInfo,
4526
+ expiresAt: new Date(Date.now() + config.refreshTokenTtl * 1e3),
4527
+ adminId: admin.id,
4528
+ email: admin.email
4529
+ });
4530
+ return token;
4531
+ }
4532
+ async function refreshTokens({
4533
+ admin,
4534
+ deviceInfo,
4535
+ ip
4536
+ }) {
4537
+ const token = await createRefreshToken({ admin, deviceInfo, ip });
4538
+ await cookieService.setSignedCookie({
4539
+ name: config.refreshTokenName,
4540
+ value: token,
4541
+ expireSeconds: config.refreshTokenTtl
4542
+ });
4543
+ const accessToken = jwtService.sign({
4544
+ payload: { id: admin.id },
4545
+ secret: cryptoService.hash(config.accessTokenSecret),
4546
+ expiresIn: config.accessTokenTtl
4547
+ });
4548
+ await cookieService.setSignedCookie({
4549
+ name: config.accessTokenName,
4550
+ value: accessToken,
4551
+ expireSeconds: config.accessTokenTtl
4552
+ });
4553
+ }
4554
+ function signPasswordResetToken({ admin }) {
4555
+ const payload = { email: admin.email };
4556
+ const passwordResetToken = jwtService.sign({
4557
+ payload,
4558
+ secret: config.resetPasswordSecret,
4559
+ expiresIn: config.resetPasswordTtl
4560
+ });
4561
+ return passwordResetToken;
4562
+ }
4563
+ function verifyPasswordResetToken({ token }) {
4564
+ const payload = jwtService.verify({
4565
+ token,
4566
+ secret: config.resetPasswordSecret
4567
+ });
4568
+ return payload;
4569
+ }
4570
+ function signEmailVerificationToken() {
4571
+ const emailVerificationToken = jwtService.sign({
4572
+ payload: {},
4573
+ secret: config.verifyEmailSecret,
4574
+ expiresIn: config.verifyEmailTtl
4575
+ });
4576
+ return emailVerificationToken;
4577
+ }
4578
+ async function verifyEmailAndUpdate({
4579
+ token,
4580
+ admin
4581
+ }) {
4582
+ let updatedAdmin = admin;
4583
+ jwtService.verify({
4584
+ token,
4585
+ secret: config.verifyEmailSecret
4586
+ });
4587
+ updatedAdmin = await prisma.admin.update({
4588
+ where: { email: admin.email },
4589
+ data: { emailVerifiedAt: /* @__PURE__ */ new Date() }
4590
+ });
4591
+ return updatedAdmin;
4592
+ }
4593
+ return {
4594
+ verifyCredentials,
4595
+ updatePassword,
4596
+ createRefreshToken,
4597
+ refreshTokens,
4598
+ // reset password
4599
+ signPasswordResetToken,
4600
+ verifyPasswordResetToken,
4601
+ // verify email
4602
+ signEmailVerificationToken,
4603
+ verifyEmailAndUpdate
4604
+ };
4605
+ }
4606
+
4607
+ // src/server/applications/emails/create-email-verification-email.ts
4608
+ function createEmailVerificationEmail({
4609
+ renderEmailTemplate,
4610
+ sendEmail,
4611
+ authUseCases,
4612
+ webUrl
4613
+ }) {
4614
+ async function generateHtml(replacements) {
4615
+ return await renderEmailTemplate("auth/verify-email", replacements);
4616
+ }
4617
+ async function send({ translator, admin, newEmail }) {
4618
+ const email = newEmail ?? admin.email;
4619
+ const emailVerificationToken = authUseCases.signEmailVerificationToken();
4620
+ const emailVerificationUrl = `${webUrl}/cms/verify-email?email=${email}&emailVerificationToken=${emailVerificationToken}`;
4621
+ const html = await generateHtml({
4622
+ emailVerificationUrl
4623
+ });
4624
+ return sendEmail({
4625
+ to: email,
4626
+ subject: translator.t("email.verify-email.text"),
4627
+ html
4628
+ });
4629
+ }
4630
+ return {
4631
+ generateHtml,
4632
+ send
4633
+ };
4634
+ }
4635
+
4636
+ // src/server/applications/emails/create-forgot-password-email.ts
4637
+ function createForgotPasswordEmail({
4638
+ renderEmailTemplate,
4639
+ sendEmail,
4640
+ authUseCases,
4641
+ webUrl
4642
+ }) {
4643
+ async function generateHtml(replacements) {
4644
+ return await renderEmailTemplate("auth/forgot-password", replacements);
4645
+ }
4646
+ async function send({ translator, admin }) {
4647
+ const passwordResetToken = authUseCases.signPasswordResetToken({ admin });
4648
+ const passwordResetUrl = `${webUrl}/cms/reset-password?passwordResetToken=${passwordResetToken}`;
4649
+ const html = await generateHtml({ passwordResetUrl });
4650
+ return await sendEmail({
4651
+ to: admin.email,
4652
+ subject: translator.t("email.forgot-password.text"),
4653
+ html
4654
+ });
4655
+ }
4656
+ return {
4657
+ generateHtml,
4658
+ send
4659
+ };
4660
+ }
4661
+
4662
+ // src/shared/form/normalizers/json-array-to-db.ts
4663
+ var jsonArrayToDb = (data) => {
4664
+ if (typeof data !== "string" || data.trim() === "") {
4665
+ return [];
4666
+ }
4667
+ try {
4668
+ const parsed = JSON.parse(data);
4669
+ if (Array.isArray(parsed)) return parsed;
4670
+ if (typeof parsed === "object" && parsed !== null) {
4671
+ return [parsed];
4672
+ }
4673
+ return [];
4674
+ } catch {
4675
+ throw new ServerError({ message: "Invalid JSON" });
4676
+ }
4677
+ };
4678
+
4679
+ // src/shared/form/normalizers/json-array-to-ui.ts
4680
+ var jsonArrayToUi = (data) => {
4681
+ try {
4682
+ return JSON.stringify(data ?? []);
4683
+ } catch {
4684
+ return "[]";
4685
+ }
4686
+ };
4687
+
4688
+ // src/shared/units.ts
4689
+ var SIZE = {
4690
+ BYTE: 1,
4691
+ KB: 1024,
4692
+ MB: 1024 ** 2,
4693
+ GB: 1024 ** 3,
4694
+ TB: 1024 ** 4,
4695
+ PB: 1024 ** 5
4696
+ };
4697
+
4698
+ // src/server/infrastructure/zod/schemas/file.ts
4699
+ function createFileSchema({
4700
+ z: z2,
4701
+ maxSizeInMb
4702
+ }) {
4703
+ return function fileSchema({
4704
+ size = maxSizeInMb * SIZE.MB,
4705
+ extensions = []
4706
+ } = {}) {
4707
+ return z2.instanceof(File, { error: "Invalid file" }).refine((file) => file.size <= size, {
4708
+ error: `File is too large, max ${size / SIZE.MB}MB`
4709
+ }).refine(
4710
+ (file) => {
4711
+ if (extensions.length === 0) return true;
4712
+ return extensions.some(
4713
+ (ext) => file.name.toLocaleLowerCase().endsWith(ext)
4714
+ );
4715
+ },
4716
+ { error: `Only ${extensions.join(", ")} files are allowed` }
4717
+ ).transform((file) => file);
4718
+ };
4719
+ }
4720
+ function createMultiFileSchema({
4721
+ z: z2,
4722
+ fileSchema,
4723
+ maxSizeInMb
4724
+ }) {
4725
+ return function multiFilesSchema({
4726
+ size = maxSizeInMb * SIZE.MB,
4727
+ extensions = []
4728
+ } = {}) {
4729
+ return z2.array(fileSchema({ size, extensions }));
4730
+ };
4731
+ }
4732
+
4733
+ export { ADMIN_ORDER_BY, ADMIN_ROLES, FILE_TYPES, OG_TYPE_ARRAY, ORDER_BY, POST_ORDER_BY, POST_TYPES, ROOT_FOLDER, ROOT_FOLDER_ID, ROOT_FOLDER_NAME, SIMPLE_UPLOAD_FOLDER_KEY, SIMPLE_UPLOAD_FOLDER_NAME, SIZE, ServerError, TWITTER_CARD_ARRAY, classifyFileType, createAdminCommandRepository, createAdminCreateAction, createAdminDeleteAction, createAdminFindFullAction, createAdminFindListCardsAction, createAdminQueryRepository, createAdminRefreshTokenCommandRepository, createAdminRefreshTokenDeleteAction, createAdminRefreshTokenFindManyAction, createAdminRefreshTokenQueryRepository, createAdminUpdateAction, createArgon2Service, createAuthMiddleware, createAuthUseCases, createBuildArticleMetadata, createBuildTranslations, createBuildWebsiteMetadata, createCache, createCacheResult, createChangePasswordAction, createCookieService, createCryptoService, createEmailUnverifiedAction, createEmailVerificationEmail, createExecuteAction, createExecuteApi, createExist, createFileCommandRepository, createFileCreateAction, createFileCreateManyAction, createFileFindFullAction, createFileFindListCardsAction, createFilePurgeManyAction, createFileQueryRepository, createFileRestoreManyAction, createFileSchema, createFileSoftDeleteAction, createFileSoftDeleteManyAction, createFileUpdateAction, createFolderCommandRepository, createFolderCreateAction, createFolderDeleteAction, createFolderFindFullAction, createFolderFindListCardsAction, createFolderQueryRepository, createFolderUpdateAction, createForgotPasswordAction, createForgotPasswordEmail, createIpRateLimiter, createJwtService, createMultiFileSchema, createPostCommandRepository, createPostCreateAction, createPostDeleteAction, createPostFindAction, createPostFindFullAction, createPostFindListCardsAction, createPostFindManyAction, createPostQueryRepository, createPostUpdateAction, createRenderEmailTemplate, createResetPasswordAction, createSchemas, createSendEmail, createSeoMetadataCommandRepository, createSignInAction, createSignOutAction, createTocItemSchema, createTransporter, createUnique, createVerifyAccessToken, createVerifyAction, createVerifyEmailAction, createVerifyRefreshToken, createZod, datetimeToDb, datetimeToUi, findTranslation, formatFileSize, getMediaInfo, isFileLocked, isFolderLocked, jsonArrayToDb, jsonArrayToUi, mimeToExtension, normalizeCacheKey, result, serializeJsonLd };