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