nextjs-cms 0.5.89 → 0.5.91

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 (57) 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/factories/FieldFactory.d.ts.map +1 -1
  23. package/dist/core/factories/FieldFactory.js +16 -1
  24. package/dist/core/fields/photo.d.ts +6 -6
  25. package/dist/core/fields/richText.d.ts +9 -9
  26. package/dist/core/fields/select.d.ts +1 -1
  27. package/dist/core/sections/category.d.ts +6 -6
  28. package/dist/core/sections/hasItems.d.ts +12 -12
  29. package/dist/core/sections/section.d.ts +3 -3
  30. package/dist/core/sections/simple.d.ts +4 -4
  31. package/dist/core/submit/ItemEditSubmit.d.ts +7 -0
  32. package/dist/core/submit/ItemEditSubmit.d.ts.map +1 -1
  33. package/dist/core/submit/ItemEditSubmit.js +32 -0
  34. package/dist/core/submit/NewItemSubmit.d.ts +2 -0
  35. package/dist/core/submit/NewItemSubmit.d.ts.map +1 -1
  36. package/dist/core/submit/NewItemSubmit.js +3 -0
  37. package/dist/core/submit/submit.d.ts +8 -4
  38. package/dist/core/submit/submit.d.ts.map +1 -1
  39. package/dist/core/submit/submit.js +58 -8
  40. package/dist/db/schema.d.ts +231 -0
  41. package/dist/db/schema.d.ts.map +1 -1
  42. package/dist/db/schema.js +25 -0
  43. package/dist/index.d.ts +1 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +1 -0
  46. package/dist/logging/audit.d.ts +20 -0
  47. package/dist/logging/audit.d.ts.map +1 -0
  48. package/dist/logging/audit.js +48 -0
  49. package/dist/logging/index.d.ts +2 -0
  50. package/dist/logging/index.d.ts.map +1 -0
  51. package/dist/logging/index.js +1 -0
  52. package/dist/logging/log.d.ts +20 -0
  53. package/dist/logging/log.d.ts.map +1 -0
  54. package/dist/logging/log.js +48 -0
  55. package/dist/translations/dictionaries/ar.json +6 -0
  56. package/dist/translations/dictionaries/en.json +6 -0
  57. package/package.json +7 -3
@@ -1,8 +1,10 @@
1
1
  import type { SQL } from 'drizzle-orm';
2
2
  import { Submit } from './submit';
3
3
  import { entityKind } from '../helpers';
4
+ import type { LogEventType } from '../../logging/index.js';
4
5
  export declare class NewSubmit extends Submit {
5
6
  static readonly [entityKind]: string;
7
+ protected getLogEventType(): LogEventType;
6
8
  /**
7
9
  * Rollback submit
8
10
  * This will rollback the submit for this item
@@ -1 +1 @@
1
- {"version":3,"file":"NewItemSubmit.d.ts","sourceRoot":"","sources":["../../../src/core/submit/NewItemSubmit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAC,MAAM,aAAa,CAAC;AAGtC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvC,qBAAa,SAAU,SAAQ,MAAM;IACjC,gBAAyB,CAAC,UAAU,CAAC,EAAE,MAAM,CAAc;IAC3D;;;;OAIG;cACa,cAAc;IAY9B,SAAS,CAAC,aAAa,IAAI,GAAG,GAAG,SAAS;CAsE7C"}
1
+ {"version":3,"file":"NewItemSubmit.d.ts","sourceRoot":"","sources":["../../../src/core/submit/NewItemSubmit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAC,MAAM,aAAa,CAAC;AAGtC,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,KAAI,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAEzD,qBAAa,SAAU,SAAQ,MAAM;IACjC,gBAAyB,CAAC,UAAU,CAAC,EAAE,MAAM,CAAc;cACxC,eAAe,IAAI,YAAY;IAGlD;;;;OAIG;cACa,cAAc;IAY9B,SAAS,CAAC,aAAa,IAAI,GAAG,GAAG,SAAS;CAsE7C"}
@@ -4,6 +4,9 @@ import { Submit } from './submit';
4
4
  import { entityKind } from '../helpers';
5
5
  export class NewSubmit extends Submit {
6
6
  static [entityKind] = 'NewSubmit';
7
+ getLogEventType() {
8
+ return 'section.item.create';
9
+ }
7
10
  /**
8
11
  * Rollback submit
9
12
  * This will rollback the submit for this item
@@ -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":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAGtC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,KAAK,EAAE,OAAO,EAAoC,MAAM,aAAa,CAAA;AAE5E,OAAO,EAAE,UAAU,EAAM,MAAM,YAAY,CAAA;AAG3C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AACtC,OAAO,EAAa,KAAK,YAAY,EAAE,KAAK,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE3F,KAAK,eAAe,GAAG;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,IAAI,CAAA;IACV,QAAQ,EAAE,QAAQ,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,eAAe,CAAC,EAAE,eAAe,CAAA;CACpC,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;IAC9B,SAAS,CAAC,eAAe,CAAC,EAAE,eAAe,CAAA;IAE3C;;OAEG;gBACS,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,eAAe;IAQxF;;;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,SAAS,CAAC,eAAe,IAAI,YAAY,GAAG,IAAI;IAIhD,SAAS,CAAC,gBAAgB,IAAI,MAAM,EAAE;IAMtC,SAAS,CAAC,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAI5D,SAAS,CAAC,cAAc,IAAI,MAAM,GAAG,IAAI;YAkB3B,YAAY;IA0B1B;;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;YA6FL,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"}
@@ -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": "تم حذف المدير بنجاح",