nextjs-cms 0.5.90 → 0.5.92

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.
Files changed (55) hide show
  1. package/dist/api/index.d.ts +57 -0
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/lib/serverActions.d.ts +2 -1
  4. package/dist/api/lib/serverActions.d.ts.map +1 -1
  5. package/dist/api/lib/serverActions.js +49 -15
  6. package/dist/api/root.d.ts +114 -0
  7. package/dist/api/root.d.ts.map +1 -1
  8. package/dist/api/root.js +2 -0
  9. package/dist/api/routers/accountSettings.d.ts.map +1 -1
  10. package/dist/api/routers/accountSettings.js +65 -3
  11. package/dist/api/routers/admins.d.ts.map +1 -1
  12. package/dist/api/routers/admins.js +54 -2
  13. package/dist/api/routers/auth.d.ts.map +1 -1
  14. package/dist/api/routers/auth.js +27 -2
  15. package/dist/api/routers/categorySection.d.ts.map +1 -1
  16. package/dist/api/routers/categorySection.js +3 -1
  17. package/dist/api/routers/hasItemsSection.d.ts.map +1 -1
  18. package/dist/api/routers/hasItemsSection.js +3 -1
  19. package/dist/api/routers/logs.d.ts +59 -0
  20. package/dist/api/routers/logs.d.ts.map +1 -0
  21. package/dist/api/routers/logs.js +75 -0
  22. package/dist/core/fields/photo.d.ts +6 -6
  23. package/dist/core/fields/richText.d.ts +9 -9
  24. package/dist/core/fields/select.d.ts +1 -1
  25. package/dist/core/sections/category.d.ts +6 -6
  26. package/dist/core/sections/hasItems.d.ts +12 -12
  27. package/dist/core/sections/section.d.ts +3 -3
  28. package/dist/core/sections/simple.d.ts +4 -4
  29. package/dist/core/submit/ItemEditSubmit.d.ts +7 -0
  30. package/dist/core/submit/ItemEditSubmit.d.ts.map +1 -1
  31. package/dist/core/submit/ItemEditSubmit.js +32 -0
  32. package/dist/core/submit/NewItemSubmit.d.ts +2 -0
  33. package/dist/core/submit/NewItemSubmit.d.ts.map +1 -1
  34. package/dist/core/submit/NewItemSubmit.js +3 -0
  35. package/dist/core/submit/submit.d.ts +8 -4
  36. package/dist/core/submit/submit.d.ts.map +1 -1
  37. package/dist/core/submit/submit.js +58 -8
  38. package/dist/db/schema.d.ts +231 -0
  39. package/dist/db/schema.d.ts.map +1 -1
  40. package/dist/db/schema.js +25 -0
  41. package/dist/index.d.ts +1 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +1 -0
  44. package/dist/logging/audit.d.ts +20 -0
  45. package/dist/logging/audit.d.ts.map +1 -0
  46. package/dist/logging/audit.js +48 -0
  47. package/dist/logging/index.d.ts +2 -0
  48. package/dist/logging/index.d.ts.map +1 -0
  49. package/dist/logging/index.js +1 -0
  50. package/dist/logging/log.d.ts +20 -0
  51. package/dist/logging/log.d.ts.map +1 -0
  52. package/dist/logging/log.js +48 -0
  53. package/dist/translations/dictionaries/ar.json +6 -0
  54. package/dist/translations/dictionaries/en.json +6 -0
  55. package/package.json +7 -3
@@ -3,11 +3,13 @@ import type { Field } from '../fields';
3
3
  import type { Section } from '../sections';
4
4
  import { entityKind } from '../helpers';
5
5
  import type { User } from '../../auth';
6
+ import { type LogEventType, type RequestMetadata } from '../../logging/index.js';
6
7
  type ConstructorType = {
7
8
  sectionName: string;
8
9
  user: User;
9
10
  postData: FormData;
10
11
  preSubmit?: boolean;
12
+ requestMetadata?: RequestMetadata;
11
13
  };
12
14
  export declare abstract class Submit {
13
15
  static readonly [entityKind]: string;
@@ -22,10 +24,11 @@ export declare abstract class Submit {
22
24
  protected preSubmit: boolean;
23
25
  protected sqlNamesAndValues: Record<string, any>;
24
26
  protected fields: Field[];
27
+ protected requestMetadata?: RequestMetadata;
25
28
  /**
26
29
  * Constructor
27
30
  */
28
- constructor({ preSubmit, sectionName, user, postData }: ConstructorType);
31
+ constructor({ preSubmit, sectionName, user, postData, requestMetadata }: ConstructorType);
29
32
  /**
30
33
  * Run custom hooks before the item is updated
31
34
  * @protected
@@ -41,9 +44,10 @@ export declare abstract class Submit {
41
44
  * @protected
42
45
  */
43
46
  protected runErrorHooks(error?: unknown): Promise<void>;
44
- /**
45
- * Log operation to the database
46
- */
47
+ protected getLogEventType(): LogEventType | null;
48
+ protected getChangedFields(): string[];
49
+ protected getExistingFieldValue(_fieldName: string): unknown;
50
+ protected getEntityLabel(): string | null;
47
51
  private logOperation;
48
52
  /**
49
53
  * Must be called after the constructor
@@ -1 +1 @@
1
- {"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../../src/core/submit/submit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAGtC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAE1C,OAAO,EAAE,UAAU,EAAM,MAAM,YAAY,CAAA;AAG3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC,KAAK,eAAe,GAAG;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,IAAI,CAAA;IACV,QAAQ,EAAE,QAAQ,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,CAAA;CACtB,CAAA;AAED,8BAAsB,MAAM;IACxB,MAAM,CAAC,QAAQ,CAAC,CAAC,UAAU,CAAC,EAAE,MAAM,CAAW;IAC/C,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IAC7B,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAY;IAC1D,SAAS,CAAC,MAAM,EAAE,OAAO,CAAQ;IACjC,SAAS,CAAC,aAAa,EAAE,MAAM,CAAK;IACpC,OAAO,CAAC,QAAQ,CAAwB;IACxC,SAAS,CAAC,WAAW,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAA;IAC5B,SAAS,CAAC,YAAY,EAAG,OAAO,CAAA;IAChC,SAAS,CAAC,SAAS,EAAE,OAAO,CAAQ;IACpC,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAK;IACrD,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,CAAK;IAE9B;;OAEG;gBACS,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,eAAe;IAOvE;;;OAGG;cACa,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAclD;;;OAGG;cACa,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAcnD;;;OAGG;cACa,aAAa,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB7D;;OAEG;YACW,YAAY;IAM1B;;OAEG;IACU,UAAU,CAAC,YAAY,GAAE,GAAG,GAAG,GAAS;IAIrD;;;;;OAKG;YACW,qBAAqB;IA4BnC;;;;OAIG;YACW,UAAU;IAqDxB;;;;OAIG;cACa,kBAAkB;IAMlC;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAElD;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IAqB7B;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,aAAa,IAAI,GAAG,GAAG,SAAS;IAEnD;;;;OAIG;IACH,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK;IAIpC;;;;OAIG;IACH,SAAS,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK;IAqBpC,SAAS,CAAC,uBAAuB,CAAC,KAAK,EAAE,KAAK;IAM9C,SAAS,CAAC,iBAAiB,CAAC,KAAK,EAAE,KAAK;IAUxC;;;;OAIG;cACa,WAAW,CAAC,KAAK,EAAE,KAAK;cAoDxB,YAAY;IAU5B;;;;OAIG;YACW,YAAY;IA6B1B;;OAEG;IACU,MAAM;YAsFL,aAAa;IAkF3B,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,OAAO,IAAI,MAAM,EAAE,GAAG,IAAI,CAE7B;IAED,OAAO,CAAC,SAAS;CAMpB"}
1
+ {"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../../src/core/submit/submit.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import { SectionFactory } from '../factories';
4
4
  import { entityKind, is } from '../helpers';
5
5
  import { NumberField, PhotoField, SelectField, TextField } from '../fields';
6
6
  import { MysqlTableChecker } from '../db';
7
+ import { recordLog } from '../../logging/index.js';
7
8
  export class Submit {
8
9
  static [entityKind] = 'Submit';
9
10
  user;
@@ -17,14 +18,16 @@ export class Submit {
17
18
  preSubmit = false;
18
19
  sqlNamesAndValues = {};
19
20
  fields = [];
21
+ requestMetadata;
20
22
  /**
21
23
  * Constructor
22
24
  */
23
- constructor({ preSubmit, sectionName, user, postData }) {
25
+ constructor({ preSubmit, sectionName, user, postData, requestMetadata }) {
24
26
  this.sectionName = sectionName;
25
27
  this.user = user;
26
28
  this._postData = postData;
27
29
  this.preSubmit = preSubmit ?? false;
30
+ this.requestMetadata = requestMetadata;
28
31
  }
29
32
  /**
30
33
  * Run custom hooks before the item is updated
@@ -88,13 +91,54 @@ export class Submit {
88
91
  console.error('onError hook failed:', e);
89
92
  }
90
93
  }
91
- /**
92
- * Log operation to the database
93
- */
94
- async logOperation({ operation, message }) {
95
- // TODO: Log the operation to the database,
96
- // get the heading input name and its value to identify the item,
97
- // then generate the log message
94
+ getLogEventType() {
95
+ return null;
96
+ }
97
+ getChangedFields() {
98
+ return Object.keys(this.sqlNamesAndValues).filter((fieldName) => !['created_by', 'updated_by'].includes(fieldName));
99
+ }
100
+ getExistingFieldValue(_fieldName) {
101
+ return undefined;
102
+ }
103
+ getEntityLabel() {
104
+ if (!this._sectionInfo || this._sectionInfo.type === 'simple')
105
+ return null;
106
+ const sectionWithHeading = this._sectionInfo;
107
+ if (!sectionWithHeading.headingField?.name)
108
+ return null;
109
+ const headingFieldName = sectionWithHeading.headingField.name;
110
+ const currentValue = this.sqlNamesAndValues[headingFieldName];
111
+ if (currentValue !== undefined && currentValue !== null && String(currentValue).trim() !== '') {
112
+ return String(currentValue);
113
+ }
114
+ const existingValue = this.getExistingFieldValue(headingFieldName);
115
+ if (existingValue !== undefined && existingValue !== null && String(existingValue).trim() !== '') {
116
+ return String(existingValue);
117
+ }
118
+ return null;
119
+ }
120
+ async logOperation() {
121
+ const eventType = this.getLogEventType();
122
+ if (!eventType || !this._sectionInfo?.name || !this._itemId)
123
+ return;
124
+ const changedFields = this.getChangedFields();
125
+ const metadata = changedFields.length > 0
126
+ ? {
127
+ fields: changedFields,
128
+ sectionType: this._sectionInfo.type,
129
+ }
130
+ : undefined;
131
+ await recordLog({
132
+ eventType,
133
+ actorId: this.user.id,
134
+ actorUsername: this.user.name,
135
+ entityType: 'section_item',
136
+ entityId: this._itemId,
137
+ entityLabel: this.getEntityLabel(),
138
+ sectionName: this._sectionInfo.name,
139
+ metadata,
140
+ requestMetadata: this.requestMetadata,
141
+ });
98
142
  }
99
143
  /**
100
144
  * Must be called after the constructor
@@ -444,6 +488,12 @@ export class Submit {
444
488
  * Handle gallery photos
445
489
  */
446
490
  await this.handleGallery();
491
+ /**
492
+ * Log the operation
493
+ */
494
+ if (!this._error && !this.preSubmit) {
495
+ await this.logOperation();
496
+ }
447
497
  return true;
448
498
  }
449
499
  async handleGallery() {
@@ -558,4 +558,235 @@ export declare const EditorPhotosTable: import("drizzle-orm/mysql-core").MySqlTa
558
558
  };
559
559
  dialect: "mysql";
560
560
  }>;
561
+ /**
562
+ * This table is used to store logs for admin actions
563
+ */
564
+ export declare const LogsTable: import("drizzle-orm/mysql-core").MySqlTableWithColumns<{
565
+ name: "logs";
566
+ schema: undefined;
567
+ columns: {
568
+ id: import("drizzle-orm/mysql-core").MySqlColumn<{
569
+ name: "id";
570
+ tableName: "logs";
571
+ dataType: "number";
572
+ columnType: "MySqlInt";
573
+ data: number;
574
+ driverParam: string | number;
575
+ notNull: true;
576
+ hasDefault: true;
577
+ isPrimaryKey: true;
578
+ isAutoincrement: true;
579
+ hasRuntimeDefault: false;
580
+ enumValues: undefined;
581
+ baseColumn: never;
582
+ identity: undefined;
583
+ generated: undefined;
584
+ }, {}, {}>;
585
+ eventType: import("drizzle-orm/mysql-core").MySqlColumn<{
586
+ name: "event_type";
587
+ tableName: "logs";
588
+ dataType: "string";
589
+ columnType: "MySqlVarChar";
590
+ data: string;
591
+ driverParam: string | number;
592
+ notNull: true;
593
+ hasDefault: false;
594
+ isPrimaryKey: false;
595
+ isAutoincrement: false;
596
+ hasRuntimeDefault: false;
597
+ enumValues: [string, ...string[]];
598
+ baseColumn: never;
599
+ identity: undefined;
600
+ generated: undefined;
601
+ }, {}, {}>;
602
+ actorId: import("drizzle-orm/mysql-core").MySqlColumn<{
603
+ name: "actor_id";
604
+ tableName: "logs";
605
+ dataType: "string";
606
+ columnType: "MySqlVarChar";
607
+ data: string;
608
+ driverParam: string | number;
609
+ notNull: false;
610
+ hasDefault: false;
611
+ isPrimaryKey: false;
612
+ isAutoincrement: false;
613
+ hasRuntimeDefault: false;
614
+ enumValues: [string, ...string[]];
615
+ baseColumn: never;
616
+ identity: undefined;
617
+ generated: undefined;
618
+ }, {}, {}>;
619
+ actorUsername: import("drizzle-orm/mysql-core").MySqlColumn<{
620
+ name: "actor_username";
621
+ tableName: "logs";
622
+ dataType: "string";
623
+ columnType: "MySqlVarChar";
624
+ data: string;
625
+ driverParam: string | number;
626
+ notNull: false;
627
+ hasDefault: false;
628
+ isPrimaryKey: false;
629
+ isAutoincrement: false;
630
+ hasRuntimeDefault: false;
631
+ enumValues: [string, ...string[]];
632
+ baseColumn: never;
633
+ identity: undefined;
634
+ generated: undefined;
635
+ }, {}, {}>;
636
+ entityType: import("drizzle-orm/mysql-core").MySqlColumn<{
637
+ name: "entity_type";
638
+ tableName: "logs";
639
+ dataType: "string";
640
+ columnType: "MySqlVarChar";
641
+ data: string;
642
+ driverParam: string | number;
643
+ notNull: false;
644
+ hasDefault: false;
645
+ isPrimaryKey: false;
646
+ isAutoincrement: false;
647
+ hasRuntimeDefault: false;
648
+ enumValues: [string, ...string[]];
649
+ baseColumn: never;
650
+ identity: undefined;
651
+ generated: undefined;
652
+ }, {}, {}>;
653
+ entityId: import("drizzle-orm/mysql-core").MySqlColumn<{
654
+ name: "entity_id";
655
+ tableName: "logs";
656
+ dataType: "string";
657
+ columnType: "MySqlVarChar";
658
+ data: string;
659
+ driverParam: string | number;
660
+ notNull: false;
661
+ hasDefault: false;
662
+ isPrimaryKey: false;
663
+ isAutoincrement: false;
664
+ hasRuntimeDefault: false;
665
+ enumValues: [string, ...string[]];
666
+ baseColumn: never;
667
+ identity: undefined;
668
+ generated: undefined;
669
+ }, {}, {}>;
670
+ entityLabel: import("drizzle-orm/mysql-core").MySqlColumn<{
671
+ name: "entity_label";
672
+ tableName: "logs";
673
+ dataType: "string";
674
+ columnType: "MySqlVarChar";
675
+ data: string;
676
+ driverParam: string | number;
677
+ notNull: false;
678
+ hasDefault: false;
679
+ isPrimaryKey: false;
680
+ isAutoincrement: false;
681
+ hasRuntimeDefault: false;
682
+ enumValues: [string, ...string[]];
683
+ baseColumn: never;
684
+ identity: undefined;
685
+ generated: undefined;
686
+ }, {}, {}>;
687
+ sectionName: import("drizzle-orm/mysql-core").MySqlColumn<{
688
+ name: "section_name";
689
+ tableName: "logs";
690
+ dataType: "string";
691
+ columnType: "MySqlVarChar";
692
+ data: string;
693
+ driverParam: string | number;
694
+ notNull: false;
695
+ hasDefault: false;
696
+ isPrimaryKey: false;
697
+ isAutoincrement: false;
698
+ hasRuntimeDefault: false;
699
+ enumValues: [string, ...string[]];
700
+ baseColumn: never;
701
+ identity: undefined;
702
+ generated: undefined;
703
+ }, {}, {}>;
704
+ metadata: import("drizzle-orm/mysql-core").MySqlColumn<{
705
+ name: "metadata";
706
+ tableName: "logs";
707
+ dataType: "string";
708
+ columnType: "MySqlText";
709
+ data: string;
710
+ driverParam: string;
711
+ notNull: false;
712
+ hasDefault: false;
713
+ isPrimaryKey: false;
714
+ isAutoincrement: false;
715
+ hasRuntimeDefault: false;
716
+ enumValues: [string, ...string[]];
717
+ baseColumn: never;
718
+ identity: undefined;
719
+ generated: undefined;
720
+ }, {}, {}>;
721
+ ipAddress: import("drizzle-orm/mysql-core").MySqlColumn<{
722
+ name: "ip_address";
723
+ tableName: "logs";
724
+ dataType: "string";
725
+ columnType: "MySqlVarChar";
726
+ data: string;
727
+ driverParam: string | number;
728
+ notNull: false;
729
+ hasDefault: false;
730
+ isPrimaryKey: false;
731
+ isAutoincrement: false;
732
+ hasRuntimeDefault: false;
733
+ enumValues: [string, ...string[]];
734
+ baseColumn: never;
735
+ identity: undefined;
736
+ generated: undefined;
737
+ }, {}, {}>;
738
+ userAgent: import("drizzle-orm/mysql-core").MySqlColumn<{
739
+ name: "user_agent";
740
+ tableName: "logs";
741
+ dataType: "string";
742
+ columnType: "MySqlVarChar";
743
+ data: string;
744
+ driverParam: string | number;
745
+ notNull: false;
746
+ hasDefault: false;
747
+ isPrimaryKey: false;
748
+ isAutoincrement: false;
749
+ hasRuntimeDefault: false;
750
+ enumValues: [string, ...string[]];
751
+ baseColumn: never;
752
+ identity: undefined;
753
+ generated: undefined;
754
+ }, {}, {}>;
755
+ source: import("drizzle-orm/mysql-core").MySqlColumn<{
756
+ name: "source";
757
+ tableName: "logs";
758
+ dataType: "string";
759
+ columnType: "MySqlVarChar";
760
+ data: string;
761
+ driverParam: string | number;
762
+ notNull: false;
763
+ hasDefault: false;
764
+ isPrimaryKey: false;
765
+ isAutoincrement: false;
766
+ hasRuntimeDefault: false;
767
+ enumValues: [string, ...string[]];
768
+ baseColumn: never;
769
+ identity: undefined;
770
+ generated: undefined;
771
+ }, {}, {}>;
772
+ createdAt: import("drizzle-orm/mysql-core").MySqlColumn<{
773
+ name: "created_at";
774
+ tableName: "logs";
775
+ dataType: "date";
776
+ columnType: "MySqlTimestamp";
777
+ data: Date;
778
+ driverParam: string | number;
779
+ notNull: true;
780
+ hasDefault: true;
781
+ isPrimaryKey: false;
782
+ isAutoincrement: false;
783
+ hasRuntimeDefault: false;
784
+ enumValues: undefined;
785
+ baseColumn: never;
786
+ identity: undefined;
787
+ generated: undefined;
788
+ }, {}, {}>;
789
+ };
790
+ dialect: "mysql";
791
+ }>;
561
792
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAYA;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYtB,CAAA;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAchC,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAa7B,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAI/B,CAAA;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQ5B,CAAA"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAYA;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYtB,CAAA;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAchC,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAa7B,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAI/B,CAAA;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQ5B,CAAA;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyBrB,CAAA"}
package/dist/db/schema.js CHANGED
@@ -62,3 +62,28 @@ export const EditorPhotosTable = mysqlTable('editor_photos', {
62
62
  linked: boolean('linked').default(false),
63
63
  createdAt: timestamp('created_at').defaultNow().notNull(),
64
64
  });
65
+ /**
66
+ * This table is used to store logs for admin actions
67
+ */
68
+ export const LogsTable = mysqlTable('logs', {
69
+ id: int('id').primaryKey().autoincrement(),
70
+ eventType: varchar('event_type', { length: 100 }).notNull(),
71
+ actorId: varchar('actor_id', { length: 50 }),
72
+ actorUsername: varchar('actor_username', { length: 100 }),
73
+ entityType: varchar('entity_type', { length: 50 }),
74
+ entityId: varchar('entity_id', { length: 100 }),
75
+ entityLabel: varchar('entity_label', { length: 255 }),
76
+ sectionName: varchar('section_name', { length: 100 }),
77
+ metadata: longtext('metadata'),
78
+ ipAddress: varchar('ip_address', { length: 45 }),
79
+ userAgent: varchar('user_agent', { length: 255 }),
80
+ source: varchar('source', { length: 50 }),
81
+ createdAt: timestamp('created_at').defaultNow().notNull(),
82
+ }, (table) => [
83
+ index('logsEventTypeIdx').on(table.eventType),
84
+ index('logsActorIdIdx').on(table.actorId),
85
+ index('logsSectionNameIdx').on(table.sectionName),
86
+ index('logsEntityTypeIdx').on(table.entityType),
87
+ index('logsEntityIdIdx').on(table.entityId),
88
+ index('logsCreatedAtIdx').on(table.createdAt),
89
+ ]);
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export * from './api/index.js';
2
2
  export * from './auth/index.js';
3
3
  export * from './core/index.js';
4
4
  export * from './db/index.js';
5
+ export * from './logging/index.js';
5
6
  export * from './utils/index.js';
6
7
  export * from './validators/index.js';
7
8
  export * from './translations/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,eAAe,CAAA;AAC7B,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,uBAAuB,CAAA;AACrC,cAAc,yBAAyB,CAAA"}
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ export * from './api/index.js';
2
2
  export * from './auth/index.js';
3
3
  export * from './core/index.js';
4
4
  export * from './db/index.js';
5
+ export * from './logging/index.js';
5
6
  export * from './utils/index.js';
6
7
  export * from './validators/index.js';
7
8
  export * from './translations/index.js';
@@ -0,0 +1,20 @@
1
+ export type AuditEventType = 'auth.login' | 'auth.logout' | 'section.item.create' | 'section.item.update' | 'section.item.delete' | 'admin.section.create' | 'admin.section.update' | 'admin.section.delete' | 'admin.username.change' | 'admin.settings.change';
2
+ export type RequestMetadata = {
3
+ ipAddress?: string | null;
4
+ userAgent?: string | null;
5
+ source?: string | null;
6
+ };
7
+ export type AuditLogInput = {
8
+ eventType: AuditEventType;
9
+ actorId?: string | number | null;
10
+ actorUsername?: string | null;
11
+ entityType?: string | null;
12
+ entityId?: string | number | null;
13
+ entityLabel?: string | null;
14
+ sectionName?: string | null;
15
+ metadata?: Record<string, unknown>;
16
+ requestMetadata?: RequestMetadata;
17
+ };
18
+ export declare const getRequestMetadataFromHeaders: (headers?: Headers | null) => RequestMetadata;
19
+ export declare const recordAuditLog: (input: AuditLogInput) => Promise<void>;
20
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/logging/audit.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GACpB,YAAY,GACZ,aAAa,GACb,qBAAqB,GACrB,qBAAqB,GACrB,qBAAqB,GACrB,sBAAsB,GACtB,sBAAsB,GACtB,sBAAsB,GACtB,uBAAuB,GACvB,uBAAuB,CAAA;AAE7B,MAAM,MAAM,eAAe,GAAG;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IACxB,SAAS,EAAE,cAAc,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAChC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,eAAe,CAAC,EAAE,eAAe,CAAA;CACpC,CAAA;AAcD,eAAO,MAAM,6BAA6B,GAAI,UAAU,OAAO,GAAG,IAAI,KAAG,eAaxE,CAAA;AAED,eAAO,MAAM,cAAc,GAAU,OAAO,aAAa,KAAG,OAAO,CAAC,IAAI,CAqBvE,CAAA"}
@@ -0,0 +1,48 @@
1
+ import { db } from '../db/client.js';
2
+ import { AuditLogsTable } from '../db/schema.js';
3
+ const toNullableString = (value) => value === null || value === undefined ? null : String(value);
4
+ const safeJsonStringify = (value) => {
5
+ if (!value)
6
+ return null;
7
+ try {
8
+ return JSON.stringify(value);
9
+ }
10
+ catch (error) {
11
+ return JSON.stringify({ error: 'metadata_serialization_failed' });
12
+ }
13
+ };
14
+ export const getRequestMetadataFromHeaders = (headers) => {
15
+ if (!headers)
16
+ return {};
17
+ const forwardedFor = headers.get('x-forwarded-for');
18
+ const realIp = headers.get('x-real-ip');
19
+ const cfIp = headers.get('cf-connecting-ip');
20
+ const ipAddress = (forwardedFor?.split(',')[0] || realIp || cfIp || '').trim() || null;
21
+ return {
22
+ ipAddress,
23
+ userAgent: headers.get('user-agent') || null,
24
+ source: headers.get('x-trpc-source') || headers.get('x-requested-with') || null,
25
+ };
26
+ };
27
+ export const recordAuditLog = async (input) => {
28
+ const metadata = safeJsonStringify(input.metadata);
29
+ const request = input.requestMetadata ?? {};
30
+ try {
31
+ await db.insert(AuditLogsTable).values({
32
+ eventType: input.eventType,
33
+ actorId: toNullableString(input.actorId),
34
+ actorUsername: input.actorUsername ?? null,
35
+ entityType: input.entityType ?? null,
36
+ entityId: toNullableString(input.entityId),
37
+ entityLabel: input.entityLabel ?? null,
38
+ sectionName: input.sectionName ?? null,
39
+ metadata,
40
+ ipAddress: request.ipAddress ?? null,
41
+ userAgent: request.userAgent ?? null,
42
+ source: request.source ?? null,
43
+ });
44
+ }
45
+ catch (error) {
46
+ console.error('Failed to write audit log entry', error);
47
+ }
48
+ };
@@ -0,0 +1,2 @@
1
+ export { recordLog, getRequestMetadataFromHeaders, type LogEventType, type LogInput, type RequestMetadata, } from './log.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/logging/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,SAAS,EACT,6BAA6B,EAC7B,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,eAAe,GACvB,MAAM,UAAU,CAAA"}
@@ -0,0 +1 @@
1
+ export { recordLog, getRequestMetadataFromHeaders, } from './log.js';
@@ -0,0 +1,20 @@
1
+ export type LogEventType = 'auth.login' | 'auth.logout' | 'section.item.create' | 'section.item.update' | 'section.item.delete' | 'admin.section.create' | 'admin.section.update' | 'admin.section.delete' | 'admin.username.change' | 'admin.settings.change';
2
+ export type RequestMetadata = {
3
+ ipAddress?: string | null;
4
+ userAgent?: string | null;
5
+ source?: string | null;
6
+ };
7
+ export type LogInput = {
8
+ eventType: LogEventType;
9
+ actorId?: string | number | null;
10
+ actorUsername?: string | null;
11
+ entityType?: string | null;
12
+ entityId?: string | number | null;
13
+ entityLabel?: string | null;
14
+ sectionName?: string | null;
15
+ metadata?: Record<string, unknown>;
16
+ requestMetadata?: RequestMetadata;
17
+ };
18
+ export declare const getRequestMetadataFromHeaders: (headers?: Headers | null) => RequestMetadata;
19
+ export declare const recordLog: (input: LogInput) => Promise<void>;
20
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/logging/log.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,YAAY,GAClB,YAAY,GACZ,aAAa,GACb,qBAAqB,GACrB,qBAAqB,GACrB,qBAAqB,GACrB,sBAAsB,GACtB,sBAAsB,GACtB,sBAAsB,GACtB,uBAAuB,GACvB,uBAAuB,CAAA;AAE7B,MAAM,MAAM,eAAe,GAAG;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACnB,SAAS,EAAE,YAAY,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAChC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,eAAe,CAAC,EAAE,eAAe,CAAA;CACpC,CAAA;AAcD,eAAO,MAAM,6BAA6B,GAAI,UAAU,OAAO,GAAG,IAAI,KAAG,eAaxE,CAAA;AAED,eAAO,MAAM,SAAS,GAAU,OAAO,QAAQ,KAAG,OAAO,CAAC,IAAI,CAqB7D,CAAA"}
@@ -0,0 +1,48 @@
1
+ import { db } from '../db/client.js';
2
+ import { LogsTable } from '../db/schema.js';
3
+ const toNullableString = (value) => value === null || value === undefined ? null : String(value);
4
+ const safeJsonStringify = (value) => {
5
+ if (!value)
6
+ return null;
7
+ try {
8
+ return JSON.stringify(value);
9
+ }
10
+ catch (error) {
11
+ return JSON.stringify({ error: 'metadata_serialization_failed' });
12
+ }
13
+ };
14
+ export const getRequestMetadataFromHeaders = (headers) => {
15
+ if (!headers)
16
+ return {};
17
+ const forwardedFor = headers.get('x-forwarded-for');
18
+ const realIp = headers.get('x-real-ip');
19
+ const cfIp = headers.get('cf-connecting-ip');
20
+ const ipAddress = (forwardedFor?.split(',')[0] || realIp || cfIp || '').trim() || null;
21
+ return {
22
+ ipAddress,
23
+ userAgent: headers.get('user-agent') || null,
24
+ source: headers.get('x-trpc-source') || headers.get('x-requested-with') || null,
25
+ };
26
+ };
27
+ export const recordLog = async (input) => {
28
+ const metadata = safeJsonStringify(input.metadata);
29
+ const request = input.requestMetadata ?? {};
30
+ try {
31
+ await db.insert(LogsTable).values({
32
+ eventType: input.eventType,
33
+ actorId: toNullableString(input.actorId),
34
+ actorUsername: input.actorUsername ?? null,
35
+ entityType: input.entityType ?? null,
36
+ entityId: toNullableString(input.entityId),
37
+ entityLabel: input.entityLabel ?? null,
38
+ sectionName: input.sectionName ?? null,
39
+ metadata,
40
+ ipAddress: request.ipAddress ?? null,
41
+ userAgent: request.userAgent ?? null,
42
+ source: request.source ?? null,
43
+ });
44
+ }
45
+ catch (error) {
46
+ console.error('Failed to write log entry', error);
47
+ }
48
+ };
@@ -9,6 +9,12 @@
9
9
  "delete_admin_text": "هل أنت متأكد من رغبتك في حذف هذا المدير؟",
10
10
  "edit_admin": "تعديل مدير",
11
11
  "edit_admin_text": "يمكنك تعديل هذا المدير هنا",
12
+ "admin": "المسؤول",
13
+ "action": "الإجراء",
14
+ "date": "التاريخ",
15
+ "details": "التفاصيل",
16
+ "section": "القسم",
17
+ "no_data": "لا توجد بيانات",
12
18
  "adminNotFound": "المدير غير موجود",
13
19
  "sectionNotFound": "القسم غير موجود",
14
20
  "adminDeletedSuccessfully": "تم حذف المدير بنجاح",
@@ -9,11 +9,17 @@
9
9
  "delete_admin_text": "Are you sure you want to delete this admin?",
10
10
  "edit_admin": "Edit Admin",
11
11
  "edit_admin_text": "Edit Admin",
12
+ "admin": "Admin",
12
13
  "adminNotFound": "Admin not found",
13
14
  "sectionNotFound": "Section not found",
14
15
  "adminDeletedSuccessfully": "Admin deleted successfully",
15
16
  "masterAdminCannotBeDeleted": "Master admin cannot be deleted",
16
17
  "masterAdminCannotBeModified": "Master admin cannot be modified",
18
+ "action": "Action",
19
+ "date": "Date",
20
+ "details": "Details",
21
+ "section": "Section",
22
+ "no_data": "No data available",
17
23
  "login_to_your_account": "Login to your account",
18
24
  "login": "Login",
19
25
  "logout": "Logout",