@coopenomics/notifications 2025.11.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.
Files changed (63) hide show
  1. package/.cursor/rules/notifications.mdc +20 -0
  2. package/.env-example +2 -0
  3. package/README.md +218 -0
  4. package/build.config.ts +13 -0
  5. package/dist/index.cjs +3027 -0
  6. package/dist/index.d.cts +792 -0
  7. package/dist/index.d.mts +792 -0
  8. package/dist/index.d.ts +792 -0
  9. package/dist/index.mjs +3016 -0
  10. package/dist/sync/novu-sync.service.d.ts +30 -0
  11. package/dist/sync/novu-sync.service.js +128 -0
  12. package/dist/sync/sync-runner.d.ts +4 -0
  13. package/dist/sync/sync-runner.js +130 -0
  14. package/dist/utils/role-utils.d.ts +8 -0
  15. package/dist/utils/role-utils.js +18 -0
  16. package/dist/workflows/incoming-transfer/incoming-transfer-workflow.d.ts +3 -0
  17. package/dist/workflows/incoming-transfer/incoming-transfer-workflow.js +16 -0
  18. package/dist/workflows/incoming-transfer/index.d.ts +3 -0
  19. package/dist/workflows/incoming-transfer/index.js +2 -0
  20. package/dist/workflows/incoming-transfer/types.d.ts +12 -0
  21. package/dist/workflows/incoming-transfer/types.js +5 -0
  22. package/dist/workflows/new-agenda-item/index.d.ts +3 -0
  23. package/dist/workflows/new-agenda-item/index.js +2 -0
  24. package/dist/workflows/new-agenda-item/new-agenda-item-workflow.d.ts +3 -0
  25. package/dist/workflows/new-agenda-item/new-agenda-item-workflow.js +16 -0
  26. package/dist/workflows/new-agenda-item/types.d.ts +27 -0
  27. package/dist/workflows/new-agenda-item/types.js +10 -0
  28. package/dist/workflows/role-based-workflows.d.ts +21 -0
  29. package/dist/workflows/role-based-workflows.js +65 -0
  30. package/package.json +51 -0
  31. package/src/base/defaults.ts +66 -0
  32. package/src/base/workflow-builder.ts +128 -0
  33. package/src/index.ts +9 -0
  34. package/src/sync/README.md +54 -0
  35. package/src/sync/novu-sync.service.ts +246 -0
  36. package/src/sync/sync-runner.ts +154 -0
  37. package/src/types/index.ts +99 -0
  38. package/src/utils/index.ts +1 -0
  39. package/src/utils/slugify/builtinReplacements.ts +2065 -0
  40. package/src/utils/slugify/slugify.ts +284 -0
  41. package/src/utils/slugify/transliterate.ts +48 -0
  42. package/src/workflows/approval-request/index.ts +52 -0
  43. package/src/workflows/approval-response/index.ts +54 -0
  44. package/src/workflows/decision-approved/index.ts +50 -0
  45. package/src/workflows/email-verification/index.ts +35 -0
  46. package/src/workflows/incoming-transfer/index.ts +43 -0
  47. package/src/workflows/index.ts +74 -0
  48. package/src/workflows/invite/index.ts +34 -0
  49. package/src/workflows/meet-ended/index.ts +51 -0
  50. package/src/workflows/meet-initial/index.ts +53 -0
  51. package/src/workflows/meet-reminder-end/index.ts +52 -0
  52. package/src/workflows/meet-reminder-start/index.ts +51 -0
  53. package/src/workflows/meet-restart/index.ts +53 -0
  54. package/src/workflows/meet-started/index.ts +51 -0
  55. package/src/workflows/new-agenda-item/index.ts +51 -0
  56. package/src/workflows/new-deposit-payment-request/index.ts +50 -0
  57. package/src/workflows/new-initial-payment-request/index.ts +50 -0
  58. package/src/workflows/payment-cancelled/index.ts +51 -0
  59. package/src/workflows/payment-paid/index.ts +50 -0
  60. package/src/workflows/reset-key/index.ts +36 -0
  61. package/src/workflows/server-provisioned/index.ts +45 -0
  62. package/src/workflows/welcome/index.ts +43 -0
  63. package/tsconfig.json +18 -0
@@ -0,0 +1,128 @@
1
+ import { z } from 'zod';
2
+ import { zodToJsonSchema } from 'zod-to-json-schema';
3
+ import {
4
+ WorkflowDefinition,
5
+ BaseWorkflowPayload,
6
+ WorkflowStep,
7
+ PayloadSchema,
8
+ NovuWorkflowData,
9
+ NovuOrigin
10
+ } from '../types';
11
+ import { createDefaultPreferences } from './defaults';
12
+
13
+ export class WorkflowBuilder<T extends BaseWorkflowPayload> {
14
+ private _name: string = '';
15
+ private _workflowId: string = '';
16
+ private _description?: string;
17
+ private _steps: WorkflowStep[] = [];
18
+ private _payloadZodSchema?: z.ZodSchema<T>;
19
+ private _origin?: NovuOrigin;
20
+ private _tags?: string[];
21
+
22
+ static create<T extends BaseWorkflowPayload>(): WorkflowBuilder<T> {
23
+ return new WorkflowBuilder<T>();
24
+ }
25
+
26
+ name(name: string): this {
27
+ this._name = name;
28
+ return this;
29
+ }
30
+
31
+ workflowId(id: string): this {
32
+ this._workflowId = id;
33
+ return this;
34
+ }
35
+
36
+ description(description: string): this {
37
+ this._description = description;
38
+ return this;
39
+ }
40
+
41
+ origin(origin: NovuOrigin): this {
42
+ this._origin = origin;
43
+ return this;
44
+ }
45
+
46
+ tags(tags: string[]): this {
47
+ this._tags = tags;
48
+ return this;
49
+ }
50
+
51
+ addStep(step: WorkflowStep): this {
52
+ this._steps.push(step);
53
+ return this;
54
+ }
55
+
56
+ addSteps(steps: WorkflowStep[]): this {
57
+ this._steps.push(...steps);
58
+ return this;
59
+ }
60
+
61
+ payloadSchema(schema: z.ZodSchema<T>): this {
62
+ this._payloadZodSchema = schema;
63
+ return this;
64
+ }
65
+
66
+ build(): WorkflowDefinition<T> {
67
+ if (!this._name) throw new Error('Workflow name is required');
68
+ if (!this._workflowId) throw new Error('Workflow ID is required');
69
+ if (!this._payloadZodSchema) throw new Error('Payload schema is required');
70
+
71
+ // ΠŸΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΡƒΠ΅ΠΌ Zod схСму Π² JSON Schema для Novu
72
+ const jsonSchema = zodToJsonSchema(this._payloadZodSchema);
73
+
74
+ // ΠŸΡ€Π°Π²ΠΈΠ»ΡŒΠ½Π°Ρ типизация: zodToJsonSchema ΠΌΠΎΠΆΠ΅Ρ‚ Π²Π΅Ρ€Π½ΡƒΡ‚ΡŒ Ρ€Π°Π·Π½Ρ‹Π΅ Ρ‚ΠΈΠΏΡ‹ схСм
75
+ const payloadSchema: PayloadSchema = {
76
+ type: (jsonSchema as any).type || 'object',
77
+ properties: (jsonSchema as any).properties || {},
78
+ required: Array.isArray((jsonSchema as any).required) ? (jsonSchema as any).required : [],
79
+ };
80
+
81
+ const workflow: WorkflowDefinition<T> = {
82
+ name: this._name,
83
+ workflowId: this._workflowId,
84
+ description: this._description,
85
+ payloadSchema,
86
+ steps: this._steps,
87
+ preferences: createDefaultPreferences(),
88
+ payloadZodSchema: this._payloadZodSchema,
89
+ };
90
+
91
+ // ДобавляСм origin Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли ΠΎΠ½ ΡƒΠΊΠ°Π·Π°Π½
92
+ if (this._origin) {
93
+ workflow.origin = this._origin;
94
+ }
95
+
96
+ // ДобавляСм tags Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли ΠΎΠ½ΠΈ ΡƒΠΊΠ°Π·Π°Π½Ρ‹
97
+ if (this._tags) {
98
+ workflow.tags = this._tags;
99
+ }
100
+
101
+ return workflow;
102
+ }
103
+
104
+ // ΠšΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΡ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ для Novu API
105
+ toNovuData(): NovuWorkflowData {
106
+ const workflow = this.build();
107
+ const novuData: NovuWorkflowData = {
108
+ name: workflow.name,
109
+ workflowId: workflow.workflowId,
110
+ description: workflow.description,
111
+ payloadSchema: workflow.payloadSchema,
112
+ steps: workflow.steps,
113
+ preferences: workflow.preferences,
114
+ };
115
+
116
+ // ДобавляСм origin Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли ΠΎΠ½ ΡƒΠΊΠ°Π·Π°Π½
117
+ if (workflow.origin) {
118
+ novuData.origin = workflow.origin;
119
+ }
120
+
121
+ // ДобавляСм tags Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли ΠΎΠ½ΠΈ ΡƒΠΊΠ°Π·Π°Π½Ρ‹
122
+ if (workflow.tags) {
123
+ novuData.tags = workflow.tags;
124
+ }
125
+
126
+ return novuData;
127
+ }
128
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ // ЭкспортируСм Ρ‚ΠΈΠΏΡ‹
2
+ export * as Types from './types';
3
+
4
+ // ЭкспортируСм Π±Π°Π·ΠΎΠ²Ρ‹Π΅ ΡƒΡ‚ΠΈΠ»ΠΈΡ‚Ρ‹
5
+ export * from './base/defaults';
6
+ export { WorkflowBuilder } from './base/workflow-builder';
7
+
8
+ // ЭкспортируСм всС Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
9
+ export * as Workflows from './workflows';
@@ -0,0 +1,54 @@
1
+ # Бинхронизация Novu Workflows
2
+
3
+ Π­Ρ‚Π° ΠΏΠ°ΠΏΠΊΠ° содСрТит ΡΠ΅Ρ€Π²Π΅Ρ€Π½ΡƒΡŽ Π»ΠΎΠ³ΠΈΠΊΡƒ для синхронизации Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ с Novu API.
4
+
5
+ ## Π€Π°ΠΉΠ»Ρ‹
6
+
7
+ - `novu-sync.service.ts` - БСрвис для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Novu API
8
+ - `sync-runner.ts` - Π‘ΠΊΡ€ΠΈΠΏΡ‚ для запуска синхронизации
9
+ - `README.md` - Π­Ρ‚ΠΎΡ‚ Ρ„Π°ΠΉΠ»
10
+
11
+ ## ИспользованиС
12
+
13
+ ### Production Ρ€Π΅ΠΆΠΈΠΌ (ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π·)
14
+ ```bash
15
+ pnpm run sync
16
+ ```
17
+
18
+ ### Development Ρ€Π΅ΠΆΠΈΠΌ (watch)
19
+ ```bash
20
+ pnpm run sync:dev
21
+ # ΠΈΠ»ΠΈ
22
+ pnpm run sync:watch
23
+ ```
24
+
25
+ ## ΠŸΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅ окруТСния
26
+
27
+ Π’Ρ€Π΅Π±ΡƒΡŽΡ‚ΡΡ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Π΅:
28
+ - `NOVU_API_KEY` - API ΠΊΠ»ΡŽΡ‡ для Novu
29
+ - `NOVU_API_URL` - URL Novu API (ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ: https://api.novu.co)
30
+
31
+ ## Π Π΅ΠΆΠΈΠΌΡ‹ Ρ€Π°Π±ΠΎΡ‚Ρ‹
32
+
33
+ ### Production
34
+ - ЗапускаСтся ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π·
35
+ - Π‘ΠΈΠ½Ρ…Ρ€ΠΎΠ½ΠΈΠ·ΠΈΡ€ΡƒΠ΅Ρ‚ всС Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
36
+ - Π—Π°Π²Π΅Ρ€ΡˆΠ°Π΅Ρ‚ΡΡ послС выполнСния
37
+
38
+ ### Development
39
+ - ЗапускаСтся ΠΈ остаСтся Π°ΠΊΡ‚ΠΈΠ²Π½Ρ‹ΠΌ
40
+ - ΠžΡ‚ΡΠ»Π΅ΠΆΠΈΠ²Π°Π΅Ρ‚ измСнСния Π² ΠΏΠ°ΠΏΠΊΠ°Ρ… `workflows/` ΠΈ `types/`
41
+ - АвтоматичСски пСрСзапускаСт ΡΠΈΠ½Ρ…Ρ€ΠΎΠ½ΠΈΠ·Π°Ρ†ΠΈΡŽ ΠΏΡ€ΠΈ измСнСниях
42
+ - ДСбаунс 1 сСкунда для избСТания мноТСствСнных запусков
43
+
44
+ ## Как Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚
45
+
46
+ 1. Π—Π°Π³Ρ€ΡƒΠΆΠ°Π΅Ρ‚ всС Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ ΠΈΠ· `../workflows/index.ts`
47
+ 2. Для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ:
48
+ - ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅Ρ‚ сущСствованиС Π² Novu API
49
+ - Π‘ΠΎΠ·Π΄Π°Π΅Ρ‚ Π½ΠΎΠ²Ρ‹ΠΉ ΠΈΠ»ΠΈ обновляСт ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΉ
50
+ 3. Π›ΠΎΠ³ΠΈΡ€ΡƒΠ΅Ρ‚ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρ‹ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΉ
51
+
52
+ ## Π’Π°ΠΆΠ½ΠΎ
53
+
54
+ ⚠️ Π­Ρ‚ΠΎΡ‚ ΠΌΠΎΠ΄ΡƒΠ»ΡŒ ΠΏΡ€Π΅Π΄Π½Π°Π·Π½Π°Ρ‡Π΅Π½ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ для сСрвСрного использования ΠΈ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΠΈΠΌΠΏΠΎΡ€Ρ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒΡΡ Π½Π° Ρ„Ρ€ΠΎΠ½Ρ‚Π΅Π½Π΄Π΅!
@@ -0,0 +1,246 @@
1
+ import axios, { AxiosInstance } from 'axios';
2
+ import {
3
+ Types,
4
+ Workflows
5
+ } from '../index';
6
+
7
+ export interface NovuSyncConfig {
8
+ apiKey: string;
9
+ apiUrl: string;
10
+ }
11
+
12
+ export class NovuSyncService {
13
+ private readonly client: AxiosInstance;
14
+ private readonly config: NovuSyncConfig;
15
+
16
+ constructor(config: NovuSyncConfig) {
17
+ this.config = config;
18
+
19
+ if (!this.config.apiKey) {
20
+ throw new Error('NOVU_API_KEY is required');
21
+ }
22
+
23
+ if (!this.config.apiUrl) {
24
+ throw new Error('NOVU_API_URL is required');
25
+ }
26
+ this.client = axios.create({
27
+ baseURL: this.config.apiUrl,
28
+ headers: {
29
+ 'Authorization': `ApiKey ${this.config.apiKey}`,
30
+ 'Content-Type': 'application/json',
31
+ },
32
+ });
33
+ }
34
+
35
+ /**
36
+ * ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡŽ ΠΎ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
37
+ */
38
+ async getWorkflow(workflowId: string): Promise<any> {
39
+ try {
40
+ const response = await this.client.get(`/v2/workflows/${workflowId}`);
41
+ return response.data;
42
+ } catch (error: any) {
43
+ if (error.response?.status === 404) {
44
+ return null;
45
+ }
46
+ throw error;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ список всСх Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
52
+ */
53
+ async getAllWorkflows(): Promise<any[]> {
54
+ try {
55
+ const response = await this.client.get('/v2/workflows', {
56
+ params: {
57
+ limit: 10000
58
+ }
59
+ });
60
+
61
+ return response.data.data.workflows || [];
62
+ } catch (error: any) {
63
+ console.error('Ошибка получСния списка Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ:', console.dir(error.response?.data || error.message, {depth: null}));
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ Π½ΠΎΠ²Ρ‹ΠΉ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
70
+ */
71
+ async createWorkflow(data: Types.NovuWorkflowData): Promise<any> {
72
+ try {
73
+ // Для создания НЕ ΠΏΠ΅Ρ€Π΅Π΄Π°Π΅ΠΌ origin (ΠΊΠ°ΠΊ Π² testFramework2.ts)
74
+ const createData = { ...data };
75
+
76
+ delete createData.origin;
77
+
78
+ const response = await this.client.post('/v2/workflows', createData);
79
+ return response.data;
80
+ } catch (error: any) {
81
+ console.error(`Ошибка создания Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ ${data.workflowId}:`, error.response?.data || error.message);
82
+ throw error;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * ΠžΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΉ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
88
+ */
89
+ async updateWorkflow(workflowId: string, data: Types.NovuWorkflowData): Promise<any> {
90
+ try {
91
+ // Для обновлСния ВБЕГДА ΠΏΠ΅Ρ€Π΅Π΄Π°Π΅ΠΌ origin: "external" (ΠΊΠ°ΠΊ Π² testFramework2.ts)
92
+ const updateData = { ...data, origin: 'novu-cloud' as const };
93
+ const response = await this.client.put(`/v2/workflows/${workflowId}`, updateData);
94
+ // console.log('response', response.data);
95
+ return response.data;
96
+ } catch (error: any) {
97
+ console.error(`Ошибка обновлСния Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ ${workflowId}:`, error.response?.data || error.message);
98
+ throw error;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Π£Π΄Π°Π»ΠΈΡ‚ΡŒ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ ΠΏΠΎ ID
104
+ */
105
+ async deleteWorkflow(workflowId: string): Promise<void> {
106
+ try {
107
+ await this.client.delete(`/v2/workflows/${workflowId}`);
108
+ console.log(`Π£Π΄Π°Π»Π΅Π½ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ: ${workflowId}`);
109
+ } catch (error: any) {
110
+ if (error.response?.status === 404) {
111
+ console.log(`Π’ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ ${workflowId} Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½ (ΡƒΠΆΠ΅ ΡƒΠ΄Π°Π»Π΅Π½)`);
112
+ return;
113
+ }
114
+ console.error(`Ошибка удалСния Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ ${workflowId}:`, error.response?.data || error.message);
115
+ throw error;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ ΠΈΠ»ΠΈ ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ (upsert)
121
+ */
122
+ async upsertWorkflow(workflow: Types.WorkflowDefinition): Promise<any> {
123
+ try {
124
+ console.log(`ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ: ${workflow.workflowId}`);
125
+
126
+ const existingWorkflow = await this.getWorkflow(workflow.workflowId);
127
+
128
+ const novuData: Types.NovuWorkflowData = {
129
+ name: workflow.name,
130
+ workflowId: workflow.workflowId,
131
+ description: workflow.description,
132
+ payloadSchema: workflow.payloadSchema,
133
+ steps: workflow.steps,
134
+ preferences: workflow.preferences,
135
+ tags: workflow.tags,
136
+ };
137
+
138
+ if (existingWorkflow) {
139
+ console.log(`ОбновляСм Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ: ${workflow.workflowId}`);
140
+ return await this.updateWorkflow(workflow.workflowId, novuData);
141
+ } else {
142
+ console.log(`Π‘ΠΎΠ·Π΄Π°Ρ‘ΠΌ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ: ${workflow.workflowId}`);
143
+ return await this.createWorkflow(novuData);
144
+ }
145
+ } catch (error: any) {
146
+ console.error(`Ошибка upsert Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ ${workflow.workflowId}:`, error.message);
147
+ throw error;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Π£Π΄Π°Π»ΠΈΡ‚ΡŒ всС ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠ΅ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
153
+ */
154
+ async deleteAllWorkflows(): Promise<void> {
155
+ console.log('ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ список всСх Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ для удалСния...');
156
+
157
+ try {
158
+ const workflows = await this.getAllWorkflows();
159
+ console.log(`НайдСно ${workflows.length} Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ для удалСния`);
160
+
161
+ if (workflows.length === 0) {
162
+ console.log('НСт Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ для удалСния');
163
+ return;
164
+ }
165
+
166
+ const errors: string[] = [];
167
+ let deletedCount = 0;
168
+
169
+ for (const workflow of workflows) {
170
+ try {
171
+ await this.deleteWorkflow(workflow.workflowId || workflow._id);
172
+ deletedCount++;
173
+ } catch (error: any) {
174
+ const errorMessage = `Ошибка удалСния Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ ${workflow.workflowId || workflow._id}: ${error.message}`;
175
+ console.error(`βœ— ${errorMessage}`);
176
+ errors.push(errorMessage);
177
+ }
178
+ }
179
+
180
+ console.log(`\nΠ Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ удалСния:`);
181
+ console.log(`βœ… Π£Π΄Π°Π»Π΅Π½ΠΎ: ${deletedCount}`);
182
+ console.log(`❌ Ошибки: ${errors.length}`);
183
+
184
+ if (errors.length > 0) {
185
+ console.log(`\nБписок ошибок удалСния:`);
186
+ errors.forEach((error, index) => {
187
+ console.log(`${index + 1}. ${error}`);
188
+ });
189
+ throw new Error(`Π£Π΄Π°Π»Π΅Π½ΠΈΠ΅ Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ»ΠΎΡΡŒ с ошибками: ${errors.length} ΠΈΠ· ${workflows.length} Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ`);
190
+ }
191
+
192
+ console.log('βœ… ВсС ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠ΅ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ ΡƒΠ΄Π°Π»Π΅Π½Ρ‹ ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ');
193
+ } catch (error: any) {
194
+ console.error('❌ ΠšΡ€ΠΈΡ‚ΠΈΡ‡Π΅ΡΠΊΠ°Ρ ошибка ΠΏΡ€ΠΈ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΈ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ:', error.message);
195
+ throw error;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ ΠΈΠ»ΠΈ ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ всС Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ (с ΠΏΡ€Π΅Π΄Π²Π°Ρ€ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ΠΌ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠ΅ΠΌ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΡ…)
201
+ */
202
+ async upsertAllWorkflows(): Promise<void> {
203
+ console.log('πŸš€ НачинаСм ΠΏΠΎΠ»Π½ΡƒΡŽ ΡΠΈΠ½Ρ…Ρ€ΠΎΠ½ΠΈΠ·Π°Ρ†ΠΈΡŽ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ...');
204
+
205
+ // Π¨Π°Π³ 1: УдаляСм всС ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠ΅ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
206
+ // try {
207
+ // await this.deleteAllWorkflows();
208
+ // } catch (error: any) {
209
+ // console.error('❌ Ошибка ΠΏΡ€ΠΈ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΈ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΡ… Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ:', error.message);
210
+ // throw error;
211
+ // }
212
+
213
+ // Π¨Π°Π³ 2: Π‘ΠΎΠ·Π΄Π°Π΅ΠΌ Π½ΠΎΠ²Ρ‹Π΅ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
214
+ console.log(`\nπŸ“ НачинаСм созданиС ${Workflows.allWorkflows.length} Π½ΠΎΠ²Ρ‹Ρ… Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ...`);
215
+
216
+ const errors: string[] = [];
217
+ let successCount = 0;
218
+
219
+ for (const workflow of Workflows.allWorkflows) {
220
+ try {
221
+ await this.upsertWorkflow(workflow);
222
+ console.log(`βœ“ Π’ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ ${workflow.workflowId} ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ создан`);
223
+ successCount++;
224
+ } catch (error: any) {
225
+ const errorMessage = `Ошибка создания Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ ${workflow.workflowId}: ${error.message}`;
226
+ console.error(`βœ— ${errorMessage}`);
227
+ errors.push(errorMessage);
228
+ }
229
+ }
230
+
231
+ console.log(`\nΠ Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚ синхронизации:`);
232
+ console.log(`βœ… УспСшно создано: ${successCount}`);
233
+ console.log(`❌ Ошибки: ${errors.length}`);
234
+
235
+ if (errors.length > 0) {
236
+ console.log(`\nБписок ошибок:`);
237
+ errors.forEach((error, index) => {
238
+ console.log(`${index + 1}. ${error}`);
239
+ });
240
+
241
+ throw new Error(`Бинхронизация Π·Π°Π²Π΅Ρ€ΡˆΠΈΠ»Π°ΡΡŒ с ошибками: ${errors.length} ΠΈΠ· ${Workflows.allWorkflows.length} Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ`);
242
+ }
243
+
244
+ console.log('βœ… ВсС Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ синхронизированы ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ');
245
+ }
246
+ }
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { NovuSyncService } from './novu-sync.service';
4
+ import { existsSync } from 'fs';
5
+ import { join, dirname } from 'path';
6
+ import { watch } from 'chokidar';
7
+ import dotenv from 'dotenv';
8
+ import { fileURLToPath } from 'url';
9
+
10
+ dotenv.config();
11
+
12
+ // ΠšΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡ ΠΈΠ· ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… окруТСния
13
+ const config = {
14
+ apiKey: process.env.NOVU_API_KEY || '',
15
+ apiUrl: process.env.NOVU_API_URL || '',
16
+ };
17
+
18
+ async function runSync() {
19
+ console.log('πŸ”„ Запуск синхронизации Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ...');
20
+
21
+ try {
22
+ // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΡŽ
23
+ if (!config.apiKey) {
24
+ throw new Error('❌ NOVU_API_KEY Π½Π΅ установлСн Π² ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… окруТСния');
25
+ }
26
+
27
+ if (!config.apiUrl) {
28
+ throw new Error('❌ NOVU_API_URL Π½Π΅ установлСн Π² ΠΏΠ΅Ρ€Π΅ΠΌΠ΅Π½Π½Ρ‹Ρ… окруТСния');
29
+ }
30
+
31
+ const syncService = new NovuSyncService(config);
32
+ await syncService.upsertAllWorkflows();
33
+ console.log('βœ… Бинхронизация Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½Π° ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ');
34
+ return true;
35
+ } catch (error: any) {
36
+ console.error('❌ Ошибка синхронизации:', error.message);
37
+
38
+ return false;
39
+ }
40
+ }
41
+
42
+ async function main() {
43
+ const args = process.argv.slice(2);
44
+ const isDev = args.includes('--dev') || process.env.NODE_ENV === 'development';
45
+
46
+ if (isDev) {
47
+ console.log('πŸ“‘ Π Π΅ΠΆΠΈΠΌ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ: отслСТиваСм измСнСния...');
48
+
49
+ // ЗапускаСм ΡΠΈΠ½Ρ…Ρ€ΠΎΠ½ΠΈΠ·Π°Ρ†ΠΈΡŽ сразу
50
+ const initialSyncSuccess = await runSync();
51
+
52
+ if (!initialSyncSuccess) {
53
+ console.error('❌ ΠŸΠ΅Ρ€Π²ΠΎΠ½Π°Ρ‡Π°Π»ΡŒΠ½Π°Ρ синхронизация Π½Π΅ ΡƒΠ΄Π°Π»Π°ΡΡŒ');
54
+ process.exit(1);
55
+ }
56
+
57
+ // ΠžΡ‚ΡΠ»Π΅ΠΆΠΈΠ²Π°Π΅ΠΌ измСнСния Π² Ρ„Π°ΠΉΠ»Π°Ρ… Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ ΠΈ Ρ‚ΠΈΠΏΠΎΠ²
58
+ const __dirname = dirname(fileURLToPath(import.meta.url));
59
+ const workflowsPath = join(__dirname, '../workflows');
60
+ const typesPath = join(__dirname, '../types');
61
+
62
+ const watchPaths = [];
63
+ if (existsSync(workflowsPath)) {
64
+ watchPaths.push(workflowsPath);
65
+ }
66
+ if (existsSync(typesPath)) {
67
+ watchPaths.push(typesPath);
68
+ }
69
+
70
+ if (watchPaths.length > 0) {
71
+ console.log('πŸ‘€ ΠžΡ‚ΡΠ»Π΅ΠΆΠΈΠ²Π°Π΅ΠΌ измСнСния Π²:', watchPaths.join(', '));
72
+
73
+ let syncTimeout: NodeJS.Timeout | null = null;
74
+
75
+ const watcher = watch(watchPaths, {
76
+ ignored: /node_modules/,
77
+ persistent: true,
78
+ ignoreInitial: true,
79
+ });
80
+
81
+ watcher.on('change', (path: string) => {
82
+ console.log(`πŸ“ ИзмСнСн Ρ„Π°ΠΉΠ»: ${path}`);
83
+
84
+ // ДСбаунс для избСТания мноТСствСнных запусков
85
+ if (syncTimeout) {
86
+ clearTimeout(syncTimeout);
87
+ }
88
+
89
+ syncTimeout = setTimeout(async () => {
90
+ console.log('⏰ ЗапускаСм ΡΠΈΠ½Ρ…Ρ€ΠΎΠ½ΠΈΠ·Π°Ρ†ΠΈΡŽ...');
91
+ const success = await runSync();
92
+ if (!success) {
93
+ console.error('⚠️ Бинхронизация Π½Π΅ ΡƒΠ΄Π°Π»Π°ΡΡŒ, Π½ΠΎ ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Π΅ΠΌ отслСТиваниС...');
94
+ }
95
+ }, 1000);
96
+ });
97
+
98
+ watcher.on('add', (path: string) => {
99
+ console.log(`βž• Π”ΠΎΠ±Π°Π²Π»Π΅Π½ Ρ„Π°ΠΉΠ»: ${path}`);
100
+ if (syncTimeout) {
101
+ clearTimeout(syncTimeout);
102
+ }
103
+ syncTimeout = setTimeout(async () => {
104
+ console.log('⏰ ЗапускаСм ΡΠΈΠ½Ρ…Ρ€ΠΎΠ½ΠΈΠ·Π°Ρ†ΠΈΡŽ...');
105
+ const success = await runSync();
106
+ if (!success) {
107
+ console.error('⚠️ Бинхронизация Π½Π΅ ΡƒΠ΄Π°Π»Π°ΡΡŒ, Π½ΠΎ ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Π΅ΠΌ отслСТиваниС...');
108
+ }
109
+ }, 1000);
110
+ });
111
+
112
+ watcher.on('unlink', (path: string) => {
113
+ console.log(`βž– Π£Π΄Π°Π»Π΅Π½ Ρ„Π°ΠΉΠ»: ${path}`);
114
+ if (syncTimeout) {
115
+ clearTimeout(syncTimeout);
116
+ }
117
+ syncTimeout = setTimeout(async () => {
118
+ console.log('⏰ ЗапускаСм ΡΠΈΠ½Ρ…Ρ€ΠΎΠ½ΠΈΠ·Π°Ρ†ΠΈΡŽ...');
119
+ const success = await runSync();
120
+ if (!success) {
121
+ console.error('⚠️ Бинхронизация Π½Π΅ ΡƒΠ΄Π°Π»Π°ΡΡŒ, Π½ΠΎ ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Π΅ΠΌ отслСТиваниС...');
122
+ }
123
+ }, 1000);
124
+ });
125
+
126
+ console.log('πŸ’‘ НаТмитС Ctrl+C для Π²Ρ‹Ρ…ΠΎΠ΄Π°');
127
+
128
+ // Π”Π΅Ρ€ΠΆΠΈΠΌ процСсс ΠΆΠΈΠ²Ρ‹ΠΌ
129
+ process.on('SIGINT', () => {
130
+ console.log('\nπŸ‘‹ Π’Ρ‹Ρ…ΠΎΠ΄ ΠΈΠ· Ρ€Π΅ΠΆΠΈΠΌΠ° Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ');
131
+ watcher.close();
132
+ process.exit(0);
133
+ });
134
+ } else {
135
+ console.log('⚠️ Папки для отслСТивания Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½Ρ‹');
136
+ process.exit(1);
137
+ }
138
+
139
+ } else {
140
+ // Production Ρ€Π΅ΠΆΠΈΠΌ - запускаСм ΠΎΠ΄ΠΈΠ½ Ρ€Π°Π·
141
+ const success = await runSync();
142
+ process.exit(success ? 0 : 1);
143
+ }
144
+ }
145
+
146
+ // ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ, Π·Π°ΠΏΡƒΡ‰Π΅Π½ Π»ΠΈ Ρ„Π°ΠΉΠ» ΠΊΠ°ΠΊ основной ΠΌΠΎΠ΄ΡƒΠ»ΡŒ
147
+ if (import.meta.url === `file://${process.argv[1]}`) {
148
+ main().catch((error) => {
149
+ console.error('❌ ΠšΡ€ΠΈΡ‚ΠΈΡ‡Π΅ΡΠΊΠ°Ρ ошибка:', error);
150
+ process.exit(1);
151
+ });
152
+ }
153
+
154
+ export { runSync, NovuSyncService };
@@ -0,0 +1,99 @@
1
+ import { z } from 'zod';
2
+
3
+ // Π‘Π°Π·ΠΎΠ²Ρ‹Π΅ Ρ‚ΠΈΠΏΡ‹ для ΠΊΠ°Π½Π°Π»ΠΎΠ² ΡƒΠ²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠΉ
4
+ export interface ChannelConfig {
5
+ enabled: boolean;
6
+ readOnly?: boolean;
7
+ }
8
+
9
+ export interface ChannelsConfig {
10
+ email: ChannelConfig;
11
+ sms: ChannelConfig;
12
+ in_app: ChannelConfig;
13
+ push: ChannelConfig;
14
+ chat: ChannelConfig;
15
+ }
16
+
17
+ export interface PreferencesConfig {
18
+ user: {
19
+ all: ChannelConfig;
20
+ channels: ChannelsConfig;
21
+ };
22
+ workflow: {
23
+ all: ChannelConfig;
24
+ channels: ChannelsConfig;
25
+ };
26
+ }
27
+
28
+ // Π‘Π°Π·ΠΎΠ²Ρ‹Π΅ Ρ‚ΠΈΠΏΡ‹ для шагов Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
29
+ export interface StepControlValues {
30
+ subject?: string;
31
+ body?: string;
32
+ title?: string;
33
+ content?: string;
34
+ avatar?: string;
35
+ editorType?: 'html' | 'text';
36
+ [key: string]: any;
37
+ }
38
+
39
+ export interface WorkflowStep {
40
+ name: string;
41
+ type: 'email' | 'sms' | 'in_app' | 'push' | 'chat' | 'delay' | 'digest';
42
+ controlValues: StepControlValues;
43
+ }
44
+
45
+ // Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ интСрфСйс для payload Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
46
+ export interface BaseWorkflowPayload {
47
+ [key: string]: any;
48
+ }
49
+
50
+ // Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ для схСмы payload (JSON Schema)
51
+ export interface PayloadSchema {
52
+ type: string;
53
+ properties: Record<string, any>;
54
+ required: string[];
55
+ }
56
+
57
+ // ДопустимыС значСния для origin Π² Novu
58
+ export type NovuOrigin = 'novu-cloud' | 'novu-cloud-v1' | 'external';
59
+
60
+ // Π“Π»Π°Π²Π½Ρ‹ΠΉ интСрфСйс для Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
61
+ export interface WorkflowDefinition<T extends BaseWorkflowPayload = BaseWorkflowPayload> {
62
+ name: string;
63
+ workflowId: string;
64
+ description?: string;
65
+ payloadSchema: PayloadSchema;
66
+ steps: WorkflowStep[];
67
+ preferences: PreferencesConfig;
68
+ origin?: NovuOrigin; // Π”Π΅Π»Π°Π΅ΠΌ optional
69
+ tags?: string[]; // Π’Π΅Π³ΠΈ для Π³Ρ€ΡƒΠΏΠΏΠΈΡ€ΠΎΠ²ΠΊΠΈ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
70
+ // Випизированная схСма для Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ payload
71
+ payloadZodSchema: z.ZodSchema<T>;
72
+ }
73
+
74
+ // Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ для создания Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ Π² Novu (Π±Π΅Π· origin для создания)
75
+ export interface NovuWorkflowData {
76
+ name: string;
77
+ workflowId: string;
78
+ description?: string;
79
+ payloadSchema: PayloadSchema;
80
+ steps: WorkflowStep[];
81
+ preferences: PreferencesConfig;
82
+ origin?: NovuOrigin; // Optional - Ρ‚ΠΎΠ»ΡŒΠΊΠΎ для ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΉ
83
+ tags?: string[]; // Π’Π΅Π³ΠΈ для Π³Ρ€ΡƒΠΏΠΏΠΈΡ€ΠΎΠ²ΠΊΠΈ Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
84
+ }
85
+
86
+ // Π˜Π½Ρ‚Π΅Ρ€Ρ„Π΅ΠΉΡ для Ρ‚Ρ€ΠΈΠ³Π³Π΅Ρ€Π° Π²ΠΎΡ€ΠΊΡ„Π»ΠΎΡƒ
87
+ export interface WorkflowTriggerData<T extends BaseWorkflowPayload> {
88
+ workflowId: string;
89
+ subscriberId: string;
90
+ payload: T;
91
+ actor?: {
92
+ subscriberId: string;
93
+ email?: string;
94
+ };
95
+ }
96
+
97
+ // Π£Ρ‚ΠΈΠ»ΠΈΡ‚Π°Ρ€Π½Ρ‹Π΅ Ρ‚ΠΈΠΏΡ‹
98
+ export type WorkflowStepType = WorkflowStep['type'];
99
+ export type ChannelType = keyof ChannelsConfig;
@@ -0,0 +1 @@
1
+ export { slugify } from './slugify/slugify';