@serhii.mazur/directus-gu-logs 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.MD CHANGED
@@ -8,32 +8,32 @@ A lightweight utility class for logging errors, creating activity logs, and send
8
8
 
9
9
  You need to create a collection named `logs` with the following fields:
10
10
 
11
- | Field Name | Type | Description |
12
- | --------------- | -------- | ------------------------ |
13
- | `collection` | string | Name of the collection |
14
- | `date_created` | datetime | Timestamp of the log |
15
- | `extension` | string | Extension identifier |
16
- | `function_name` | string | Name of the function |
17
- | `error` | code | Error message/code block |
11
+ | Field Name | Type | Description |
12
+ | --------------- | --------- | ---------------------------- |
13
+ | `collection` | string | Name of the collection |
14
+ | `date_created` | datetime | Timestamp of the log |
15
+ | `extension` | string | Extension identifier |
16
+ | `function_name` | string | Name of the function |
17
+ | `error` | text/code | Error message or stack trace |
18
18
 
19
19
  ### 2. Global Collection (global)
20
20
 
21
21
  Add the following field to the `global` collection:
22
22
 
23
- | Field Name | Type | Description |
24
- | ---------------------------- | -------------------- | --------------------------------- |
25
- | `notice_recipient` | m2o → directus_users | Recipient for error notifications |
26
- | `developer_notice_recipient` | m2o → directus_users | Recipient for error notifications |
23
+ | Field Name | Type | Description |
24
+ | ------------------------ | ------------------------------- | ---------------------------------- |
25
+ | `error_notice_recipient` | m2m → directus_users (junction) | Recipients for error notifications |
27
26
 
28
- This recipient will be used to send internal notifications when errors occur.
27
+ All linked users from this field will receive internal notifications when errors occur.
29
28
 
30
29
  ## 🚀 Features
31
30
 
32
- - Save error logs to the logs collection
33
-
34
- - Create Directus activity records
35
-
36
- - Send internal Directus notifications
31
+ - Save structured error logs to a collection
32
+ - Create Directus activity records
33
+ - Send internal Directus notifications
34
+ - Automatic recipient resolution from global settings
35
+ - Support for manual recipient override
36
+ - Includes project metadata in notifications (environment, URL, timestamp)
37
37
 
38
38
  ## 📦 Installation
39
39
 
@@ -46,10 +46,77 @@ npm i @serhii.mazur/directus-gu-logs
46
46
  ```ts
47
47
  import { Logs } from "@serhii.mazur/directus-gu-logs";
48
48
 
49
- const logs = new Logs(context, "my-extension", "logs");
49
+ const logs = new Logs(context, "my-extension");
50
+
51
+ // Save error log
52
+ await logs.printLogs("myFunction", "Something went wrong");
50
53
 
51
- await logs.printLogs("myFunction", "message");
52
- await logs.createActivity("create", "collection", "id");
54
+ // Create activity record
55
+ await logs.createActivity("create", "collection_name", "item_id");
56
+
57
+ // Send notification (auto recipients from global settings)
53
58
  await logs.createNotification("An error occurred");
54
- await logs.createNotification("An error occurred", "Custom subject", "recipient_id");
59
+
60
+ // Send notification with custom subject
61
+ await logs.createNotification("An error occurred", "Custom Subject");
62
+
63
+ // Send notification to specific recipient
64
+ await logs.createNotification("An error occurred", "Custom Subject", "user_id");
65
+
66
+ // Send notification with related collection + item
67
+ await logs.createNotification("An error occurred", "Custom Subject", null, "collection_name", "item_id");
68
+ ```
69
+
70
+ ---
71
+
72
+ ## ⚙️ Constructor
73
+
74
+ ```ts
75
+ constructor(
76
+ context: ApiExtensionContext,
77
+ extension: string,
78
+ collectionName: string = "logs"
79
+ )
55
80
  ```
81
+
82
+ | Param | Type | Description |
83
+ | ---------------- | ------------------- | ------------------------------------ |
84
+ | `context` | ApiExtensionContext | Directus extension context |
85
+ | `extension` | string | Name of your extension |
86
+ | `collectionName` | string (optional) | Logs collection name (default: logs) |
87
+
88
+ ## 🧠 How It Works
89
+
90
+ ### Logging
91
+
92
+ - Saves structured logs into the logs collection
93
+ - Automatically attaches timestamp and extension name
94
+ - Fallback to console if DB write fails
95
+
96
+ ---
97
+
98
+ ### Activity Tracking
99
+
100
+ - Uses Directus ActivityService
101
+ - Automatically includes:
102
+ - user
103
+ - IP address
104
+ - user agent
105
+ - origin
106
+
107
+ ---
108
+
109
+ ### Notifications
110
+
111
+ - Resolves recipients in this order:
112
+ 1. Explicit recipientOverride
113
+ 2. global.notice_recipient
114
+ 3. global.developer_notice_recipient
115
+ - Automatically:
116
+ - Deduplicates recipients
117
+ - Includes project metadata:
118
+ - Project name
119
+ - Backend URL
120
+ - Environment (branch)
121
+ - UTC timestamp
122
+ - Sends one notification per recipient
package/dist/index.js CHANGED
@@ -1,3 +1,14 @@
1
+ function extractRecipientIds(rows) {
2
+ const recipients = rows
3
+ ?.map(row => {
4
+ const user = row?.directus_users_id;
5
+ if (typeof user === "string")
6
+ return user;
7
+ return typeof user?.id === "string" ? user.id : null;
8
+ })
9
+ .filter((value) => Boolean(value)) ?? [];
10
+ return Array.from(new Set(recipients));
11
+ }
1
12
  export class Logs {
2
13
  constructor(context, extension, collectionName = "logs") {
3
14
  this.context = context;
@@ -59,17 +70,20 @@ export class Logs {
59
70
  try {
60
71
  const schema = await this.getSchema();
61
72
  const { database, services } = this.context;
73
+ const globalService = new services.ItemsService("global", {
74
+ database,
75
+ schema,
76
+ });
62
77
  // Check for passed recipient, fallback to global settings
63
78
  let recipients = [];
64
79
  if (recipientOverride) {
65
80
  recipients = [recipientOverride];
66
81
  }
67
82
  else {
68
- const globalSettings = await database
69
- .select("notice_recipient", "developer_notice_recipient")
70
- .from("global")
71
- .first();
72
- recipients = [globalSettings?.notice_recipient, globalSettings?.developer_notice_recipient].filter(Boolean);
83
+ const globalSettings = await globalService.readSingleton({
84
+ fields: ["error_notice_recipient.directus_users_id.id"],
85
+ });
86
+ recipients = extractRecipientIds(globalSettings?.error_notice_recipient);
73
87
  }
74
88
  if (recipients.length === 0) {
75
89
  this.printLogs(this.extension, "No recipients defined (override or global settings)");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serhii.mazur/directus-gu-logs",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Helper class Logs for using in Directus extensions",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -9,6 +9,23 @@ interface LogEntry {
9
9
  error: string;
10
10
  }
11
11
 
12
+ interface RecipientLinkRow {
13
+ directus_users_id?: string | { id?: string | null } | null;
14
+ }
15
+
16
+ function extractRecipientIds(rows: RecipientLinkRow[] | undefined): string[] {
17
+ const recipients =
18
+ rows
19
+ ?.map(row => {
20
+ const user = row?.directus_users_id;
21
+ if (typeof user === "string") return user;
22
+ return typeof user?.id === "string" ? user.id : null;
23
+ })
24
+ .filter((value): value is string => Boolean(value)) ?? [];
25
+
26
+ return Array.from(new Set(recipients));
27
+ }
28
+
12
29
  export class Logs {
13
30
  protected context: ApiExtensionContext;
14
31
  protected extension: string;
@@ -88,6 +105,10 @@ export class Logs {
88
105
  try {
89
106
  const schema = await this.getSchema();
90
107
  const { database, services } = this.context;
108
+ const globalService = new services.ItemsService("global", {
109
+ database,
110
+ schema,
111
+ });
91
112
 
92
113
  // Check for passed recipient, fallback to global settings
93
114
  let recipients: string[] = [];
@@ -95,14 +116,11 @@ export class Logs {
95
116
  if (recipientOverride) {
96
117
  recipients = [recipientOverride];
97
118
  } else {
98
- const globalSettings = await database
99
- .select("notice_recipient", "developer_notice_recipient")
100
- .from("global")
101
- .first();
102
-
103
- recipients = [globalSettings?.notice_recipient, globalSettings?.developer_notice_recipient].filter(
104
- Boolean
105
- );
119
+ const globalSettings = await globalService.readSingleton({
120
+ fields: ["error_notice_recipient.directus_users_id.id"],
121
+ });
122
+
123
+ recipients = extractRecipientIds(globalSettings?.error_notice_recipient);
106
124
  }
107
125
 
108
126
  if (recipients.length === 0) {