@xuda.io/notification_module 1.1.114 → 1.1.115

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/index.js ADDED
@@ -0,0 +1,697 @@
1
+ console.log('Notifications Module loaded...');
2
+ const path = require('path');
3
+ global._conf = require(path.join(process.env.XUDA_HOME, process.env.XUDA_CONFIG));
4
+
5
+ const _ = require('lodash');
6
+
7
+ const _common = require(path.join(process.env.XUDA_HOME, 'common', 'xuda_node_common.mjs'));
8
+ const module_path = path.join(process.env.XUDA_HOME, 'cpi') + (!_conf.is_debug ? '/node_modules/@xuda.io' : '');
9
+ const db_module = require(`${module_path}/db_module`);
10
+
11
+ //////////////////// FCM /////////////////////
12
+ const admin = require('firebase-admin');
13
+ const serviceAccount = _conf.firebase_serviceAccount;
14
+ admin.initializeApp({
15
+ credential: admin.credential.cert(serviceAccount),
16
+ databaseURL: 'https://your-project-id.firebaseio.com', // Replace with your Firebase project's database URL
17
+ });
18
+ const messaging = admin.messaging();
19
+ //////////////////////////////////////////////
20
+
21
+ exports.get_new_notification_count = async function (req) {
22
+ return await db_module.get_couch_view('xuda_notification', 'new_notifications_count', {
23
+ key: [req.uid],
24
+ reduce: true,
25
+ group_level: 1,
26
+ });
27
+ };
28
+
29
+ exports.set_notification_read = async function (req, job_id, header) {
30
+ const ret = await db_module.get_couch_doc('xuda_notification', req.notification_id);
31
+ var notification_doc = ret.data;
32
+
33
+ if (notification_doc.system) {
34
+ return { code: -1, data: 'system notification not allow read' };
35
+ }
36
+
37
+ notification_doc.read_date = Date.now();
38
+ notification_doc.read = true;
39
+ notification_doc.read_header = header;
40
+ return await db_module.save_couch_doc('xuda_notification', notification_doc);
41
+ };
42
+
43
+ exports.update_notification_status = async function (req) {
44
+ const { notification_id, stat, uid } = req;
45
+ const ret = await db_module.get_couch_doc('xuda_notification', notification_id);
46
+ var notification_doc = ret.data;
47
+ notification_doc.stat_ts = Date.now();
48
+ notification_doc.stat = stat;
49
+
50
+ global[`_notification_module_ch`].sendToQueue(
51
+ 'ws_dashboard_module',
52
+ Buffer.from(
53
+ JSON.stringify({
54
+ method: 'notification',
55
+
56
+ data: {
57
+ to: notification_doc.system ? uid : notification_doc.uid,
58
+ data: notification_doc,
59
+ },
60
+ }),
61
+ ),
62
+ );
63
+
64
+ return await db_module.save_couch_doc('xuda_notification', notification_doc);
65
+ };
66
+
67
+ exports.submit_notification = async function (req, _ch = global[`_notification_module_ch`]) {
68
+ var { type, app_id, to_app_id, uid_arr, subject = '', body = '', delivery_method = [], display_type = 'info', ref, email, sender_uid, system, topic, params } = req;
69
+
70
+ // if (!uid) {
71
+ // return { code: -1, data: "error: uid is mandatory field" };
72
+ // }
73
+
74
+ const { notification_categories } = await import(path.join(process.env.XUDA_HOME, 'notification_templates.mjs'));
75
+
76
+ var icon = 'https://dev.xuda.io/dist/images/xuda_ico.png';
77
+ if (app_id) {
78
+ const app_id_reference = await _common.get_project_app_id(app_id, true);
79
+ const app_ret = await db_module.get_couch_doc('xuda_master', app_id_reference);
80
+ if (app_ret.code > -1) {
81
+ icon = app_ret.data.app_pic;
82
+ }
83
+ }
84
+
85
+ if (!type) {
86
+ return { code: -1, data: 'error: type is mandatory field' };
87
+ }
88
+
89
+ if (topic) {
90
+ const _topic = notification_categories?.[type]?.topics?.[topic];
91
+ if (_topic) {
92
+ if (!subject) {
93
+ subject = _.template(_topic.subject)(params);
94
+ }
95
+ delivery_method = _topic.delivery_method;
96
+ display_type = _topic.display_type;
97
+ if (!body) {
98
+ body = _.template(_topic.body)(params);
99
+ }
100
+ } else {
101
+ return {
102
+ code: -1,
103
+ data: `${topic} topic for ${type} not exist in notification_categories`,
104
+ };
105
+ }
106
+ }
107
+
108
+ var send_result = {};
109
+
110
+ for await (let uid of uid_arr) {
111
+ for await (let delivery_method_item of delivery_method) {
112
+ var account_ret;
113
+ if (!system) {
114
+ account_ret = await db_module.get_couch_doc('xuda_accounts', uid);
115
+ if (account_ret.code < 0) {
116
+ throw new Error(`user ${uid} not found`);
117
+ }
118
+ }
119
+ send_result[uid] = true;
120
+ const uuid = await _common.xuda_get_uuid('notification');
121
+ var obj = {
122
+ _id: uuid,
123
+ docType: 'notification',
124
+ type,
125
+ app_id: to_app_id,
126
+ uid,
127
+ subject,
128
+ body,
129
+ delivery_method: delivery_method_item,
130
+ display_type,
131
+ read: false,
132
+ ref: ref || uuid,
133
+ date_created_ts: Date.now(),
134
+ date_created: Date.now(),
135
+ sender_uid,
136
+ stat: 3,
137
+ stat_ts: Date.now(),
138
+ system,
139
+ topic,
140
+ params,
141
+ };
142
+
143
+ switch (delivery_method_item) {
144
+ case 'email':
145
+ obj.email = email || account_ret.data.account_info ? account_ret.data.account_info.email : '';
146
+ if (!obj.email) {
147
+ send_result[uid] = false;
148
+ return; //{ code: -1, data: "error: email is empty" };
149
+ }
150
+ const email_module = require(`${module_path}/email_module`);
151
+ email_module.send_email({
152
+ email: obj.email,
153
+ subject,
154
+ body,
155
+ ref: obj._id,
156
+ app_id: obj.app_id,
157
+ });
158
+ break;
159
+
160
+ case 'banner':
161
+ case 'consent':
162
+ case 'alert':
163
+ _ch.sendToQueue(
164
+ 'ws_dashboard_module',
165
+ Buffer.from(
166
+ JSON.stringify({
167
+ method: 'notification',
168
+ data: {
169
+ to: uid,
170
+ data: obj,
171
+ },
172
+ }),
173
+ ),
174
+ );
175
+
176
+ break;
177
+ case 'push':
178
+ try {
179
+ const ret = await db_module.find_couch_query('xuda_sessions', {
180
+ selector: {
181
+ docType: 'gtp_session',
182
+ stat: { $lt: 3 },
183
+ uid,
184
+ fcm_token: { $gt: '' },
185
+ },
186
+ });
187
+ var fcm_token_arr = [];
188
+ for await (let session of ret.docs) {
189
+ if (session.fcm_token) {
190
+ if (!fcm_token_arr.includes(session.fcm_token)) {
191
+ fcm_token_arr.push(session.fcm_token);
192
+ }
193
+ }
194
+ }
195
+ for await (let fcm_token of fcm_token_arr) {
196
+ if (fcm_token) {
197
+ const message = {
198
+ notification: {
199
+ title: subject,
200
+ body,
201
+ image: icon,
202
+ },
203
+ token: fcm_token,
204
+ android: {
205
+ notification: {
206
+ imageUrl: icon,
207
+ },
208
+ },
209
+ apns: {
210
+ payload: {
211
+ aps: {
212
+ 'mutable-content': 1,
213
+ },
214
+ },
215
+ fcm_options: {
216
+ image: icon,
217
+ },
218
+ },
219
+ webpush: {
220
+ headers: {
221
+ image: icon,
222
+ },
223
+ },
224
+ };
225
+ try {
226
+ messaging
227
+ .send(message)
228
+ .then((response) => {
229
+ // console.log("Successfully sent message:", response);
230
+ })
231
+ .catch((error) => {
232
+ console.error('Error sending message:', error);
233
+ });
234
+ } catch (err) {
235
+ console.error(err);
236
+ }
237
+ }
238
+ }
239
+ } catch (err) {
240
+ console.error(err);
241
+ }
242
+ break;
243
+
244
+ default:
245
+ send_result[uid] = false;
246
+ break;
247
+ }
248
+ obj.sent = send_result[uid];
249
+ const ret = await db_module.save_couch_doc('xuda_notification', obj);
250
+ }
251
+ }
252
+ return { code: 1, data: obj };
253
+ };
254
+
255
+ exports.get_notifications = async function (req) {
256
+ // uid
257
+ // app_id
258
+ // search
259
+ // date_from
260
+ // date_to
261
+ // type
262
+ // delivery_method
263
+ // read
264
+ // skip
265
+ // limit
266
+ // bookmark
267
+ // ref
268
+ // display_type
269
+ // system
270
+ // outbox
271
+ // to_app_id
272
+
273
+ if (req.date_to < req.date_from) {
274
+ return {
275
+ code: -2,
276
+ data: 'error - invalid range date_from and date_to',
277
+ };
278
+ }
279
+
280
+ let selector = {
281
+ stat: 3,
282
+ };
283
+
284
+ if (req.outbox) {
285
+ selector.stat = { $lt: 4 };
286
+ selector.sender_uid = req.uid;
287
+ } else {
288
+ if (req.system) {
289
+ selector.$or = [
290
+ {
291
+ uid: req.uid,
292
+ },
293
+ {
294
+ uid: 'system',
295
+ },
296
+ {
297
+ uid: req.to_app_id,
298
+ },
299
+ ];
300
+ } else {
301
+ if (!req.to_app_id) {
302
+ selector.uid = req.uid;
303
+ }
304
+ }
305
+ }
306
+
307
+ if (req.date_to) {
308
+ selector.$and = [
309
+ {
310
+ date_created_ts: {
311
+ $lte: req.date_to,
312
+ $gte: req.date_from,
313
+ },
314
+ },
315
+ ];
316
+ }
317
+
318
+ if (req.search) {
319
+ selector = {
320
+ ...selector,
321
+ $or: [
322
+ {
323
+ body: {
324
+ $regex: `(?i)${req.search}`,
325
+ },
326
+ },
327
+ {
328
+ client_headers: {
329
+ $regex: `(?i)${req.search}`,
330
+ },
331
+ },
332
+
333
+ {
334
+ subject: {
335
+ $eq: req.search,
336
+ },
337
+ },
338
+ ],
339
+ };
340
+ }
341
+
342
+ if (req.type) {
343
+ selector.type = req.type;
344
+ }
345
+ if (req.ref) {
346
+ selector.ref = req.ref;
347
+ }
348
+ if (typeof req.to_app_id !== 'undefined') {
349
+ selector.app_id = req.to_app_id;
350
+ }
351
+
352
+ if (req.delivery_method) {
353
+ selector.delivery_method = req.delivery_method;
354
+ }
355
+ if (req.display_type) {
356
+ selector.display_type = req.display_type;
357
+ }
358
+
359
+ if ((_.isBoolean(req.read) && req.read) || req.read == 'true') {
360
+ selector.read = true;
361
+ } else {
362
+ selector.read = false;
363
+ }
364
+
365
+ if (typeof req.read === 'undefined') {
366
+ delete selector.read;
367
+ }
368
+
369
+ const opt = {
370
+ selector,
371
+
372
+ sort: [
373
+ {
374
+ date_created_ts: 'desc',
375
+ },
376
+ ],
377
+ limit: req.limit ? req.limit : 99999,
378
+ };
379
+
380
+ if (req.skip) {
381
+ opt.skip = req.skip;
382
+ }
383
+
384
+ if (req?.bookmark !== 'nil') {
385
+ opt.bookmark = req.bookmark;
386
+ }
387
+
388
+ try {
389
+ var ret = await db_module.find_couch_query('xuda_notification', opt);
390
+ let data = { docs: [] };
391
+ if (ret.docs) {
392
+ for await (let doc of ret.docs) {
393
+ if (await module.exports.notifications_validation_fx(doc._id)) {
394
+ data.docs.push(doc);
395
+ }
396
+ }
397
+ }
398
+
399
+ return {
400
+ code: 1,
401
+ data,
402
+ };
403
+ } catch (e) {
404
+ return {
405
+ code: -400,
406
+ data: e,
407
+ };
408
+ }
409
+ };
410
+
411
+ exports.submit_superuser_notification = async function (req) {
412
+ const {
413
+ uid,
414
+ type,
415
+ subject,
416
+ delivery_method,
417
+ body,
418
+ display_type, // info,warn/error
419
+ to_uid = [],
420
+ to_app_id,
421
+ app_id,
422
+ system,
423
+ } = req;
424
+ try {
425
+ if (!_conf.superuser_account_ids.includes(uid)) {
426
+ throw new Error('user is not authorized for this method');
427
+ }
428
+
429
+ if (!_.isArray(to_uid)) {
430
+ throw new Error('to_uid must be in the form of an array');
431
+ }
432
+
433
+ if (_.isEmpty(to_uid)) {
434
+ if (!system) {
435
+ const all_users = await db_module.find_couch_query('xuda_accounts', {
436
+ selector: {
437
+ docType: 'account',
438
+ stat: { $lt: 4 },
439
+ },
440
+ });
441
+ for await (let user of all_users.docs) {
442
+ to_uid.push(user._id);
443
+ }
444
+ } else {
445
+ to_uid.push('system');
446
+ }
447
+ }
448
+
449
+ let msg_obj = {
450
+ type,
451
+ uid_arr: to_uid,
452
+ app_id,
453
+ to_app_id: '',
454
+ subject,
455
+ body,
456
+ delivery_method: [delivery_method],
457
+ display_type,
458
+ sender_uid: uid,
459
+ ref: '',
460
+ system,
461
+ };
462
+
463
+ return module.exports.submit_notification(msg_obj);
464
+ } catch (err) {
465
+ return { code: -1, data: err.message };
466
+ }
467
+ };
468
+ exports.submit_app_notification = async function (req) {
469
+ const {
470
+ uid,
471
+ subject,
472
+ delivery_method,
473
+ body,
474
+ display_type, // info,warn/error
475
+ to_uid = [],
476
+ to_app_id,
477
+ app_id,
478
+ system,
479
+ type,
480
+ } = req;
481
+ try {
482
+ // if (!_conf.superuser_account_ids.includes(uid)) {
483
+ // throw new Error("user is not authorized for this method");
484
+ // }
485
+
486
+ if (!_.isArray(to_uid)) {
487
+ throw new Error('to_uid must be in the form of an array');
488
+ }
489
+
490
+ if (_.isEmpty(to_uid)) {
491
+ if (!system) {
492
+ throw new Error('to_uid is empty');
493
+ } else {
494
+ to_uid.push(app_id);
495
+ }
496
+ }
497
+
498
+ const app_module = require(`${module_path}/app_module`);
499
+ const users_arr = (
500
+ await app_module.get_app_users({
501
+ app_id_query: to_app_id,
502
+ })
503
+ ).data;
504
+ const exists = _.some(to_uid, (val) => _.includes(users_arr, val));
505
+ if (!exists) {
506
+ throw new Error("some of the uid's are not authorized for the selected app");
507
+ }
508
+
509
+ // if (!_conf.superuser_account_ids.includes(uid)) {
510
+ // throw new Error("user is not authorized for this method");
511
+ // }
512
+
513
+ let msg_obj = {
514
+ type: _conf.superuser_account_ids.includes(uid) && type ? type : 'user',
515
+ uid_arr: to_uid,
516
+ app_id,
517
+ to_app_id: to_app_id,
518
+ subject,
519
+ body,
520
+ delivery_method: [delivery_method],
521
+ display_type,
522
+ sender_uid: uid,
523
+ ref: '',
524
+ system,
525
+ };
526
+
527
+ return module.exports.submit_notification(msg_obj);
528
+ } catch (err) {
529
+ return { code: -1, data: err.message };
530
+ }
531
+ };
532
+
533
+ exports.update_system_notification = async function (req) {
534
+ const { display_type, subject, body, delivery_method, uid, type } = req;
535
+ const ret = await db_module.get_couch_doc('xuda_notification', req._id);
536
+ var notification_doc = ret.data;
537
+
538
+ if (!notification_doc.system) {
539
+ return { code: -1, data: 'only system notification allow update' };
540
+ }
541
+ const allowed_delivery_methods = ['banner', 'alert'];
542
+ if (!allowed_delivery_methods.includes(delivery_method)) {
543
+ return {
544
+ code: -1,
545
+ data: `only ${allowed_delivery_methods.toString()} delivery method allow update`,
546
+ };
547
+ }
548
+
549
+ notification_doc.ts = Date.now();
550
+ notification_doc.delivery_method = delivery_method;
551
+ notification_doc.display_type = display_type;
552
+ notification_doc.subject = subject;
553
+ notification_doc.body = body;
554
+ if (_conf.superuser_account_ids.includes(uid)) {
555
+ notification_doc.type = type;
556
+ }
557
+
558
+ global[`_notification_module_ch`].sendToQueue(
559
+ 'ws_dashboard_module',
560
+ Buffer.from(
561
+ JSON.stringify({
562
+ method: 'notification',
563
+
564
+ data: {
565
+ to: notification_doc.system ? uid : notification_doc.uid,
566
+ data: notification_doc,
567
+ },
568
+ }),
569
+ ),
570
+ );
571
+
572
+ return await db_module.save_couch_doc('xuda_notification', notification_doc);
573
+ };
574
+
575
+ exports.submit_topic_app_notification = async function (req, _ch) {
576
+ const { uid, topic, to_uid = [], to_app_id, app_id, system, type, params } = req;
577
+ try {
578
+ if (!_.isArray(to_uid)) {
579
+ throw new Error('to_uid must be in the form of an array');
580
+ }
581
+
582
+ if (_.isEmpty(to_uid)) {
583
+ if (!system) {
584
+ const app_module = require(`${module_path}/app_module`);
585
+ const users_arr = (
586
+ await app_module.get_app_users({
587
+ app_id_query: to_app_id,
588
+ })
589
+ ).data;
590
+ for await (let user_id of users_arr) {
591
+ if (uid !== user_id) {
592
+ to_uid.push(user_id);
593
+ }
594
+ }
595
+ } else {
596
+ to_uid.push(app_id);
597
+ }
598
+ }
599
+
600
+ let msg_obj = {
601
+ type: type || 'user',
602
+ uid_arr: to_uid,
603
+ app_id,
604
+ to_app_id: to_app_id,
605
+ topic,
606
+ sender_uid: uid,
607
+ ref: '',
608
+ system,
609
+ params,
610
+ };
611
+
612
+ return module.exports.submit_notification(msg_obj, _ch);
613
+ } catch (err) {
614
+ return { code: -1, data: err.message };
615
+ }
616
+ };
617
+
618
+ exports.get_system_notifications = async function (req) {
619
+ req.system = true;
620
+ req.to_app_id = '';
621
+ req.read = false;
622
+ return await module.exports.get_notifications(req);
623
+ };
624
+
625
+ // setTimeout(async () => {
626
+ // // const app_id = "vps4d6ec68e01d97ef65a8bd86088b26ca2",
627
+ // // uid = "341cf2d32991a68e4d8aaba6c16e15cc",
628
+ // // ip = "192.11.22.333",
629
+ // // name = "Boaz Avrahami";
630
+ // // const aa = await module.exports.submit_notification({
631
+ // // type: "deploy",
632
+ // // app_id: app_id,
633
+ // // uid_arr: [uid],
634
+ // // topic: "welcome_aboard_vps",
635
+ // // params: {
636
+ // // name: name,
637
+ // // ip: ip,
638
+ // // dashboard_url: `https://xuda.io/dashboard/project/${app_id}/admin/overview`,
639
+ // // dashboard_ssh_url: `https://xuda.io/dashboard/project/${app_id}/admin/overview`,
640
+ // // dashboard_ssh_assign_url: `https://xuda.io/dashboard/settings/ssh_keys`,
641
+ // // support_url: `https://xuda.io/schedule/sales?topic=Sales&subtopic=General+Inquiries`,
642
+ // // },
643
+ // // ref: null,
644
+ // // email: null,
645
+ // // });
646
+ // // console.log(aa);
647
+ // }, 2000);
648
+
649
+ // class notifications_validation_class {
650
+ // constructor(name) {
651
+ // this.name = name;
652
+ // }
653
+
654
+ // new_version_available(params) {
655
+ // console.log(`Hello, my name is ${this.name}`);
656
+ // }
657
+ // }
658
+
659
+ // module.exports = notifications_validation_class;
660
+
661
+ exports.notifications_validation_fx = async function (notification_id) {
662
+ let { code, data: doc } = await db_module.get_couch_doc('xuda_notification', notification_id);
663
+ const delete_notification = async function () {
664
+ doc.stat = 4;
665
+ await db_module.save_couch_doc('xuda_notification', doc);
666
+ };
667
+ const read_notification = async function () {
668
+ doc.read = true;
669
+ doc.read_ts = Date.now();
670
+ doc.read_note = 'read by notifications_validation_fx';
671
+ await db_module.save_couch_doc('xuda_notification', doc);
672
+ };
673
+
674
+ if (code < 0) return false;
675
+
676
+ if (!doc.topic) {
677
+ await delete_notification();
678
+ return false;
679
+ }
680
+ const fx = {
681
+ new_version_available: async function () {
682
+ const { code, data: app_doc } = await db_module.get_app_obj(doc.app_id);
683
+ if (app_doc.app_build_id >= doc.params.build) {
684
+ await read_notification();
685
+ return false;
686
+ }
687
+
688
+ return true;
689
+ },
690
+ };
691
+
692
+ if (!fx[doc.topic]) {
693
+ return true;
694
+ }
695
+
696
+ return await fx[doc.topic]();
697
+ };
package/index_ms.mjs CHANGED
@@ -41,6 +41,10 @@ export const submit_superuser_notification = async function (...args) {
41
41
  return await broker.send_to_queue("submit_superuser_notification", ...args);
42
42
  };
43
43
 
44
+ export const submit_error_resolver_alert = async function (...args) {
45
+ return await broker.send_to_queue("submit_error_resolver_alert", ...args);
46
+ };
47
+
44
48
  export const submit_app_notification = async function (...args) {
45
49
  return await broker.send_to_queue("submit_app_notification", ...args);
46
50
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/notification_module",
3
- "version": "1.1.114",
3
+ "version": "1.1.115",
4
4
  "description": "Xuda Notification Server Module",
5
5
  "main": "index.mjs",
6
6
  "dependencies": {
package/templates.js ADDED
@@ -0,0 +1,245 @@
1
+ exports.notification_categories = {
2
+ account: {
3
+ icon_ph: "ph:user-duotone",
4
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" /><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /></svg>',
5
+ topics: {
6
+ account_verification: {
7
+ subject: "Account verification",
8
+ delivery_method: ["email"],
9
+ locked: true,
10
+ body: '<h1>Account Verification</h1><br><a href="https://<%= hostname %>/dashboard/verify?id=<%= ref %>">Click here to verify your account</a>',
11
+ },
12
+ reset_password: {
13
+ subject: "Reset your password",
14
+ delivery_method: ["email"],
15
+ locked: true,
16
+ body: '<h1>Account</h1><br><a href="https://<%= hostname %>//dashboard/change?id=<%= temp_pass %>">Click here to change your password</a>',
17
+ },
18
+ password_changed: {
19
+ subject: "Password changed",
20
+ delivery_method: ["email"],
21
+ locked: true,
22
+ body: "<h1>Account</h1><br><p Password successfully changed</p>",
23
+ },
24
+ account_billing_hold_applied: {
25
+ subject: "Suspension risk",
26
+ delivery_method: ["email"],
27
+ locked: true,
28
+ body: '<b>Account at risk of Suspension</b>Due to a payment failure, your account was placed on a billing hold, which may restrict your ability to manage resources on our platform. To keep your account active, you need to log in to your <a href="https://<%= domain %>/dashboard/settings/billing">Billing Settings page</a> and manually make a payment or add a valid credit card to your Xuda account. If we do not receive a payment from you soon, our system will automatically suspend your account. This will lead to termination of your service and deletion of associated data. We appreciate your business and are here to assist if you have any questions or need any help.',
29
+ display_type: "warning",
30
+ },
31
+ account_suspension_applied: {
32
+ subject: "Termination risk",
33
+ delivery_method: ["email"],
34
+ locked: true,
35
+ body: '<b>Account at risk of Termination</b>Because of a unresolved payment failure, your account is now suspended, which restrict your ability to work on our platform. To keep your account active, you need to log in to your <a href="https://<%= domain %>/dashboard/settings/billing">Billing Settings page</a> and manually make a payment or add a valid credit card to your Xuda account. If we do not receive a payment from you soon, our system will automatically terminate your account. This will lead to deletion of associated data and turn all projects to public with no further notice. We appreciate your business and are here to assist if you have any questions or need any help.',
36
+ display_type: "error",
37
+ },
38
+ account_termination_applied: {
39
+ subject: "Account termination notice",
40
+ delivery_method: ["email"],
41
+ locked: true,
42
+ body: '<b>Private projects are now public and resources are at risk of use by others<b> Due to unresolved payment, your account is now terminated, which restrict your ability to work on our platform. All Instances, apps and associate data has been deleted permanently. <a href="https://<%= domain %>/terms">learn more</a>. We are here to assist if you have any questions or need any help.',
43
+ display_type: "error",
44
+ },
45
+ account_plan_changed: {
46
+ subject: "Account plan change",
47
+ delivery_method: ["email"],
48
+ locked: true,
49
+ body: '<b>Plan has been successfully changed to <%= new_plan %><b><br><br><a href="https://<%= domain %>/pricing">Learn more</a> regarding the new plan options.',
50
+ display_type: "info",
51
+ },
52
+ account_plan_changed_to_free: {
53
+ subject: "Plan has been downgraded",
54
+ delivery_method: ["email"],
55
+ locked: true,
56
+ body: "<b>Account's projects at risk to be public<b>Due to a downgrading plan to Free plan in your account, your projects are no longer private. We appreciate your business and are here to assist if you have any questions or need any help.",
57
+ display_type: "info",
58
+ },
59
+ },
60
+ },
61
+ team: {
62
+ icon_ph: "ph:users-three-duotone",
63
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-users-group" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M10 13a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M8 21v-1a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v1" /><path d="M15 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M17 10h2a2 2 0 0 1 2 2v1" /><path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M3 13v-1a2 2 0 0 1 2 -2h2" /></svg>',
64
+ topics: {
65
+ access_invitation_tbd: {
66
+ subject: "Access invitation",
67
+ delivery_method: ["email"],
68
+ locked: true,
69
+ body: '<h1>Access invitation</h1><h2><%= subject %></h2><p>By <%= email %> To join this Project, all you need to do is create a username and password or login with your existing Xuda user credentials.</p><a href="https://<%= domain %>/dashboard/project/<%= app_id %>">Click here to view</a>',
70
+ },
71
+ access_invitation_user_not_exist: {
72
+ subject: "Invitation for Access",
73
+ delivery_method: ["email"],
74
+ locked: true,
75
+ body: '<h1>Invitation for Access</h1><h2><%= subject %></h2><p>Sent from <%= email %>. To join this Project, simply use your email along with the temporary password <b><%= password %></b> that we have generated for you, or alternatively, you can log in using your Google or Github account.</p><a href="https://<%= domain %>/dashboard/login">Click here to proceed</a>',
76
+ },
77
+ contact_invitation_user_not_exist: {
78
+ subject: "<%= subject %>",
79
+ delivery_method: ["email"],
80
+ locked: true,
81
+ body: '<h2><%= subject %></h2><p>Sent from <%= email %>. To join this Project, simply use your email along with the temporary password <b><%= password %></b> that we have generated for you, or alternatively, you can log in using your Google or Github account.</p><a href="https://<%= domain %>/dashboard/login">Click here to proceed</a>',
82
+ },
83
+ access_invitation_user_exist: {
84
+ subject: "Invitation for Access",
85
+ delivery_method: ["email"],
86
+ locked: true,
87
+ body: '<h1>Invitation for Access</h1><h2><%= subject %></h2><p>By <%= email %> To join <%= first_name %>\'s connections, simply head over to your dashboard and accept the invitation.</p><a href="https://<%= domain %>/dashboard">Click here to view</a>',
88
+ },
89
+ contact_invitation_user_exist: {
90
+ subject: "<%= subject %>",
91
+ delivery_method: ["email"],
92
+ locked: true,
93
+ body: '<h2><%= subject %></h2><p>By <%= email %> To join <%= first_name %>\'s connections, simply head over to your dashboard and accept the invitation.</p><a href="https://<%= domain %>/dashboard/">Click here to view</a>',
94
+ },
95
+ access_invitation_confirmed: {
96
+ subject: "Access invitation confirmed",
97
+ delivery_method: ["email"],
98
+ locked: false,
99
+ display_type: "info",
100
+ body: "<h1>Invitation Accepted</h1><h2>The user <%= email %> has accepted the invitation to join <%= desc %>.</h2>",
101
+ },
102
+ access_invitation_declined: {
103
+ subject: "Access invitation declined",
104
+ delivery_method: ["email"],
105
+ locked: false,
106
+ display_type: "info",
107
+ body: "<h1>Invitation Declined</h1><h2>The user <%= email %> has declined the invitation to join <%= desc %>.</h2>",
108
+ },
109
+ access_invitation_cancelled: {
110
+ subject: "Access invitation cancelled",
111
+ delivery_method: ["email"],
112
+ locked: false,
113
+ display_type: "info",
114
+ body: "<h1>Invitation Cancelled</h1><h2>The user <%= email %> has cancelled the invitation to join <%= desc %>.</h2>",
115
+ },
116
+ access_deleted: {
117
+ subject: "Access deleted",
118
+ delivery_method: ["email"],
119
+ locked: false,
120
+ display_type: "info",
121
+ body: "<h1>Access Deleted</h1><h2>The user <%= email %> has deleted <%= desc %>.</h2>",
122
+ },
123
+ member_left_the_team: {
124
+ subject: "Member left team",
125
+ delivery_method: ["email"],
126
+ locked: false,
127
+ display_type: "info",
128
+ body: "<h1>Member left team</h1><h2>The user <%= email %> has left team <%= desc %>.</h2>",
129
+ },
130
+ connection_disconnected_by_member: {
131
+ subject: "Connection Disconnected by Member",
132
+ delivery_method: ["email"],
133
+ locked: false,
134
+ display_type: "info",
135
+ body: "<h1>Connection Disconnected by Member</h1><h2>User <%= email %> has terminated their connection with you.</h2>",
136
+ },
137
+ },
138
+ },
139
+ billing: {
140
+ icon_ph: "ph:credit-card-duotone",
141
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-credit-card" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 5m0 3a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3z" /><path d="M3 10l18 0" /><path d="M7 15l.01 0" /><path d="M11 15l2 0" /></svg>',
142
+ topics: {},
143
+ },
144
+ app: {
145
+ icon_ph: "ph:app-store-logo-duotone",
146
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-layout-board-split" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" /><path d="M4 12h8" /><path d="M12 15h8" /><path d="M12 9h8" /><path d="M12 4v16" /></svg>',
147
+ topics: {
148
+ transfer_ownership_request: {
149
+ subject: "Transfer ownership request",
150
+ delivery_method: ["email"],
151
+ locked: true,
152
+ body: "<h1>Transfer</h1><h2><%= subject %></h2><br><p><%= msg %> </p>",
153
+ },
154
+ app_turned_on: {
155
+ subject: "App turned on",
156
+ delivery_method: ["push"],
157
+ display_type: "info",
158
+ body: "<h1>App <%= app_obj.app_name %></h1><h2>Turned on</h2><br><p></p>",
159
+ },
160
+ app_turned_off: {
161
+ subject: "App turned off",
162
+ delivery_method: ["push"],
163
+ display_type: "warning",
164
+ body: "<h1>App <%= app_obj.app_name %></h1><h2>Turned off</h2><br><p></p>",
165
+ },
166
+ new_version_available: {
167
+ subject: "New version available",
168
+ delivery_method: ["alert"],
169
+ display_type: "info",
170
+ body: "The build number <%= build %> is now prepared. Please navigate to the Advanced tab and choose the Update Version option.",
171
+ },
172
+ delete_validation_code: {
173
+ subject: "Confirmation Code for Deleting #<%= app_id %>",
174
+ delivery_method: ["email"],
175
+ body: "<h1>Confirmation Code for Deleting</h1>To confirm the deletion of the app, please enter the following code for verification: <%= validation_code %>.",
176
+ display_type: "info",
177
+ },
178
+ },
179
+ },
180
+ system: {
181
+ icon_ph: "ph:cloud-duotone",
182
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-cloud-computing" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M6.657 16c-2.572 0 -4.657 -2.007 -4.657 -4.483c0 -2.475 2.085 -4.482 4.657 -4.482c.393 -1.762 1.794 -3.2 3.675 -3.773c1.88 -.572 3.956 -.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.913 0 3.464 1.56 3.464 3.486c0 1.927 -1.551 3.487 -3.465 3.487h-11.878" /><path d="M12 16v5" /><path d="M16 16v4a1 1 0 0 0 1 1h4" /><path d="M8 16v4a1 1 0 0 1 -1 1h-4" /></svg>',
183
+ topics: {
184
+ security_event: {
185
+ subject: "Security event",
186
+ delivery_method: [],
187
+ body: "A security event occurred that could require your attention:<br> <%= details %>",
188
+ display_type: "warning",
189
+ },
190
+ },
191
+ },
192
+ deploy: {
193
+ icon_ph: "ph:rocket-duotone",
194
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-rocket" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3" /><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3" /><path d="M15 9m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /></svg>',
195
+ topics: {
196
+ welcome_aboard_vps: {
197
+ subject: "Welcome Aboard! Your New VPS is Ready",
198
+ delivery_method: ["email"],
199
+ body: "<h2>Thank You for Submitting Your Support Ticket</h2><br>Dear <%= first_name %> , <br>Thank you for reaching out to <%= _conf_name %> support and submitting your ticket. We've successfully received your inquiry regarding <%= ticket_topic %>. <br>Our support team will review the details you've provided, and we aim to respond within <%= response_time %>. <br> Your ticket reference number is <%= user_code %>. In the meantime, you can view the status of your ticket or add any additional information at this link: <a href=\"<%= ticket_url %>\">Your Support Ticket</a>. <br>We appreciate your trust in <%= _conf_name %> and thank you for giving us the opportunity to assist you. Best regards,<%= _conf_name %> support team ",
200
+ display_type: "info",
201
+ },
202
+ },
203
+ },
204
+ support: {
205
+ icon_ph: "ph:lifebuoy-duotone",
206
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-lifebuoy" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" /><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M15 15l3.35 3.35" /><path d="M9 15l-3.35 3.35" /><path d="M5.65 5.65l3.35 3.35" /><path d="M18.35 5.65l-3.35 3.35" /></svg>',
207
+ topics: {
208
+ support_ticket_request: {
209
+ subject: "Support ticket #<%= id %>",
210
+ delivery_method: ["email"],
211
+ body: '<div style="margin: auto; border: 1px solid #ccc; border-radius: 5px; width: 300px; padding: 15px; box-shadow: 0px 0px 5px rgba(0,0,0,0.1); text-align: center; font-family: Arial, sans-serif;"><img src="<%= profile_picture %>" alt="User Image" style="width: 100px; height: 100px; border-radius: 50%;"><h3 style="margin: 10px 0; font-size: 18px; text-align: center"> <%= first_name %> <%= last_name %></h3><p style="margin: 0; color: #777;"><%= email %></p><p style="margin: 0; color: #777;">Phone: <%= phone %></p><p style="margin: 0; color: #777;">User Plan: <%= developer_plan %></p><p style="margin: 0; color: #777;">User Support Plan: <%= support_plan %></p><div style="text-align: left;"><p><strong>Communication Type:</strong> <%= communication_type %></p><p><strong>Support Topic:</strong> <%= ticket_topic %></p><p><strong>Support Sub Topic:</strong> <%= ticket_sub_topic %></p><p><strong>Tertiary Subtopic:</strong> <%= tertiary_sub_topic %>}</p><p><strong>Support Subject:</strong> <%= subject %></p><p><strong>Support Description:</strong> <%= description %></p><p><strong>Agent code:</strong><%= agent_code %></p><p><strong>User code:</strong> <%= user_code %></p></div><button style="padding: 10px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;" onclick="location.href=\'<%= ticket_url %>\'">View Support Ticket</button>',
212
+ display_type: "info",
213
+ },
214
+ support_ticket_reply: {
215
+ subject: "<%= ticket_topic %> support ticket #<%= id %>",
216
+ delivery_method: ["email"],
217
+ body: "<h2>Thank You for Submitting Your Support Ticket</h2><br>Dear <%= first_name %> , <br>Thank you for reaching out to <%= _conf_name %> support and submitting your ticket. We've successfully received your inquiry regarding <%= ticket_topic %>. <br>Our support team will review the details you've provided, and we aim to respond within <%= response_time %>. <br> Your ticket reference number is <%= user_code %>. In the meantime, you can view the status of your ticket or add any additional information at this link: <a href=\"<%= ticket_url %>\">Your Support Ticket</a>. <br>We appreciate your trust in <%= _conf_name %> and thank you for giving us the opportunity to assist you. Best regards,<%= _conf_name %> support team ",
218
+ display_type: "info",
219
+ },
220
+ },
221
+ },
222
+ inquiries: {
223
+ icon_ph: "ph:ticket-duotone",
224
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-lifebuoy" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" /><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M15 15l3.35 3.35" /><path d="M9 15l-3.35 3.35" /><path d="M5.65 5.65l3.35 3.35" /><path d="M18.35 5.65l-3.35 3.35" /></svg>',
225
+ topics: {
226
+ inquiry_ticket_request: {
227
+ subject: "Support ticket #<%= id %>",
228
+ delivery_method: ["email"],
229
+ body: '<div style="margin: auto; border: 1px solid #ccc; border-radius: 5px; width: 300px; padding: 15px; box-shadow: 0px 0px 5px rgba(0,0,0,0.1); text-align: center; font-family: Arial, sans-serif;"><img src="<%= profile_picture %>" alt="User Image" style="width: 100px; height: 100px; border-radius: 50%;"><h3 style="margin: 10px 0; font-size: 18px; text-align: center"> <%= first_name %> <%= last_name %></h3><p style="margin: 0; color: #777;"><%= email %></p><p style="margin: 0; color: #777;">Phone: <%= phone %></p><p style="margin: 0; color: #777;">User Plan: <%= developer_plan %></p><p style="margin: 0; color: #777;">User Support Plan: <%= support_plan %></p><div style="text-align: left;"><p><strong>Communication Type:</strong> <%= communication_type %></p><p><strong>Support Topic:</strong> <%= ticket_topic %></p><p><strong>Support Sub Topic:</strong> <%= ticket_sub_topic %></p><p><strong>Tertiary Subtopic:</strong> <%= tertiary_sub_topic %>}</p><p><strong>Support Subject:</strong> <%= subject %></p><p><strong>Support Description:</strong> <%= description %></p><p><strong>Agent code:</strong><%= agent_code %></p><p><strong>User code:</strong> <%= user_code %></p></div><button style="padding: 10px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;" onclick="location.href=\'<%= ticket_url %>\'">View Support Ticket</button>',
230
+ display_type: "info",
231
+ },
232
+ inquiry_ticket_reply: {
233
+ subject: "<%= ticket_topic %> inquiry #<%= id %>",
234
+ delivery_method: ["email"],
235
+ body: '<h2>Inquiry Submission Acknowledged</h2><br>Dear <%= first_name %>,<br>We are grateful to you for reaching out to the <%= _conf_name %> team with your inquiry. Rest assured, we have successfully received your request regarding <%= ticket_topic %>.<br>Our dedicated team is currently reviewing the information you provided, and we are committed to responding promptly.<br>Your unique inquiry reference number is <%= user_code %>. If you have an account with us, you can track the progress of your inquiry or add further details using the following link: <a href="<%= ticket_url %>">Track Your Inquiry Ticket</a>.<br>Thank you for placing your trust in <%= _conf_name %>. We are honored to serve you and look forward to assisting you. Warm regards, <%= _conf_name %> Team',
236
+ display_type: "info",
237
+ },
238
+ },
239
+ },
240
+ news: {
241
+ icon_ph: "ph:newspaper-duotone",
242
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-news" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M16 6h3a1 1 0 0 1 1 1v11a2 2 0 0 1 -4 0v-13a1 1 0 0 0 -1 -1h-10a1 1 0 0 0 -1 1v12a3 3 0 0 0 3 3h11" /><path d="M8 8l4 0" /><path d="M8 12l4 0" /><path d="M8 16l4 0" /></svg>',
243
+ topics: {},
244
+ },
245
+ };