akeyless-server-commons 1.0.203 → 1.0.205
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/dist/cjs/helpers/index.d.ts +1 -1
- package/dist/cjs/helpers/index.js +1 -1
- package/dist/cjs/helpers/index.js.map +1 -1
- package/dist/cjs/helpers/postponed_actions.d.ts +113 -0
- package/dist/cjs/helpers/postponed_actions.js +363 -0
- package/dist/cjs/helpers/postponed_actions.js.map +1 -0
- package/dist/cjs/helpers/start.js +16 -18
- package/dist/cjs/helpers/start.js.map +1 -1
- package/dist/cjs/managers/index.d.ts +1 -0
- package/dist/cjs/managers/index.js +1 -0
- package/dist/cjs/managers/index.js.map +1 -1
- package/dist/cjs/managers/rabbitmq_manager.d.ts +60 -0
- package/dist/cjs/managers/rabbitmq_manager.js +190 -0
- package/dist/cjs/managers/rabbitmq_manager.js.map +1 -0
- package/dist/esm/helpers/index.d.ts +1 -1
- package/dist/esm/helpers/index.js +1 -1
- package/dist/esm/helpers/index.js.map +1 -1
- package/dist/esm/helpers/postponed_actions.d.ts +113 -0
- package/dist/esm/helpers/postponed_actions.js +359 -0
- package/dist/esm/helpers/postponed_actions.js.map +1 -0
- package/dist/esm/helpers/start.js +16 -18
- package/dist/esm/helpers/start.js.map +1 -1
- package/dist/esm/managers/index.d.ts +1 -0
- package/dist/esm/managers/index.js +1 -0
- package/dist/esm/managers/index.js.map +1 -1
- package/dist/esm/managers/rabbitmq_manager.d.ts +60 -0
- package/dist/esm/managers/rabbitmq_manager.js +183 -0
- package/dist/esm/managers/rabbitmq_manager.js.map +1 -0
- package/dist/helpers/boards_helpers.js +62 -0
- package/dist/helpers/email_helpers.js +153 -0
- package/dist/helpers/firebase_helpers.js +601 -0
- package/dist/helpers/global_helpers.js +147 -0
- package/dist/helpers/index.js +13 -0
- package/dist/helpers/location_helpers.js +15 -0
- package/dist/helpers/login_helpers.js +18 -0
- package/dist/helpers/notification_helpers.js +248 -0
- package/dist/helpers/phone_number_helpers.js +96 -0
- package/dist/helpers/postponed_actions.js +358 -0
- package/dist/helpers/redis/helpers.js +215 -0
- package/dist/helpers/redis/index.js +4 -0
- package/dist/helpers/redis/initialize.js +139 -0
- package/dist/helpers/redis/keys.js +23 -0
- package/dist/helpers/redis/snapshot.js +166 -0
- package/dist/helpers/start.js +83 -0
- package/dist/helpers/tasks_helpers.js +139 -0
- package/dist/helpers/time_helpers.js +167 -0
- package/dist/index.js +5 -0
- package/dist/managers/cache_manager.js +37 -0
- package/dist/managers/index.js +4 -0
- package/dist/managers/logger_manager.js +68 -0
- package/dist/managers/rabbitmq_manager.js +182 -0
- package/dist/managers/translation_manager.js +38 -0
- package/dist/middlewares/auth_mw.js +50 -0
- package/dist/middlewares/error_handling.js +20 -0
- package/dist/middlewares/global_mw.js +119 -0
- package/dist/middlewares/index.js +4 -0
- package/dist/middlewares/trim_mw.js +7 -0
- package/dist/test.js +1 -0
- package/dist/types/enums/global.js +8 -0
- package/dist/types/enums/index.js +1 -0
- package/dist/types/helpers/boards_helpers.d.ts +10 -0
- package/dist/types/helpers/email_helpers.d.ts +12 -0
- package/dist/types/helpers/firebase_helpers.d.ts +56 -0
- package/dist/types/helpers/global_helpers.d.ts +16 -0
- package/dist/types/helpers/index.d.ts +13 -0
- package/dist/types/helpers/location_helpers.d.ts +3 -0
- package/dist/types/helpers/login_helpers.d.ts +4 -0
- package/dist/types/helpers/notification_helpers.d.ts +11 -0
- package/dist/types/helpers/phone_number_helpers.d.ts +19 -0
- package/dist/types/helpers/postponed_actions.d.ts +113 -0
- package/dist/types/helpers/redis/helpers.d.ts +11 -0
- package/dist/types/helpers/redis/index.d.ts +4 -0
- package/dist/types/helpers/redis/initialize.d.ts +6 -0
- package/dist/types/helpers/redis/keys.d.ts +5 -0
- package/dist/types/helpers/redis/snapshot.d.ts +2 -0
- package/dist/types/helpers/start.d.ts +4 -0
- package/dist/types/helpers/tasks_helpers.d.ts +25 -0
- package/dist/types/helpers/time_helpers.d.ts +40 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +3 -0
- package/dist/types/interfaces/email.js +1 -0
- package/dist/types/interfaces/global.js +1 -0
- package/dist/types/interfaces/index.js +2 -0
- package/dist/types/managers/cache_manager.d.ts +12 -0
- package/dist/types/managers/index.d.ts +4 -0
- package/dist/types/managers/logger_manager.d.ts +14 -0
- package/dist/types/managers/rabbitmq_manager.d.ts +60 -0
- package/dist/types/managers/translation_manager.d.ts +14 -0
- package/dist/types/middlewares/auth_mw.d.ts +4 -0
- package/dist/types/middlewares/error_handling.d.ts +11 -0
- package/dist/types/middlewares/global_mw.d.ts +5 -0
- package/dist/types/middlewares/index.d.ts +4 -0
- package/dist/types/middlewares/trim_mw.d.ts +2 -0
- package/dist/types/test.d.ts +0 -0
- package/dist/types/types/enums/global.d.ts +9 -0
- package/dist/types/types/enums/index.d.ts +1 -0
- package/dist/types/types/firebase_types.js +1 -0
- package/dist/types/types/global.js +1 -0
- package/dist/types/types/index.d.ts +3 -0
- package/dist/types/types/index.js +2 -0
- package/dist/types/types/interfaces/email.d.ts +33 -0
- package/dist/types/types/interfaces/global.d.ts +27 -0
- package/dist/types/types/interfaces/index.d.ts +2 -0
- package/dist/types/types/types/firebase_types.d.ts +50 -0
- package/dist/types/types/types/global.d.ts +17 -0
- package/dist/types/types/types/index.d.ts +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Timestamp } from "firebase-admin/firestore";
|
|
2
|
+
import moment from "moment";
|
|
3
|
+
export function sort_by_timestamp(a, b, reverse = false) {
|
|
4
|
+
return reverse ? any_datetime_to_millis(b) - any_datetime_to_millis(a) : any_datetime_to_millis(a) - any_datetime_to_millis(b);
|
|
5
|
+
}
|
|
6
|
+
export const calculate_time_passed = (datetime) => {
|
|
7
|
+
const now = new Date();
|
|
8
|
+
const diff_in_milliseconds = Math.max(0, now.getTime() - datetime.getTime());
|
|
9
|
+
const seconds_passed = Math.floor(diff_in_milliseconds / 1000);
|
|
10
|
+
const minutes_passed = Math.floor(seconds_passed / 60);
|
|
11
|
+
const hours_passed = Math.floor(minutes_passed / 60);
|
|
12
|
+
const days_passed = Math.floor(hours_passed / 24);
|
|
13
|
+
const remaining_hours = hours_passed % 24;
|
|
14
|
+
const remaining_minutes = minutes_passed % 60;
|
|
15
|
+
const remaining_seconds = seconds_passed % 60;
|
|
16
|
+
const pad_zero = (value) => (value < 10 ? `0${value}` : `${value}`);
|
|
17
|
+
let parts = [];
|
|
18
|
+
if (days_passed > 0)
|
|
19
|
+
parts.push(days_passed);
|
|
20
|
+
if (remaining_hours > 0 || days_passed > 0)
|
|
21
|
+
parts.push(pad_zero(remaining_hours));
|
|
22
|
+
parts.push(pad_zero(remaining_minutes));
|
|
23
|
+
parts.push(pad_zero(remaining_seconds));
|
|
24
|
+
const time_passed_formatted_short = parts.join(":");
|
|
25
|
+
parts = [];
|
|
26
|
+
if (days_passed > 0)
|
|
27
|
+
parts.push(`${days_passed} d`);
|
|
28
|
+
if (days_passed > 0 || remaining_hours > 0)
|
|
29
|
+
parts.push(`${remaining_hours} h`);
|
|
30
|
+
if (days_passed > 0 || remaining_hours > 0 || remaining_minutes > 0)
|
|
31
|
+
parts.push(`${remaining_minutes} min`);
|
|
32
|
+
parts.push(`${remaining_seconds} sec`);
|
|
33
|
+
const time_passed_formatted_long = parts.join(" ");
|
|
34
|
+
return {
|
|
35
|
+
seconds_passed,
|
|
36
|
+
minutes_passed,
|
|
37
|
+
hours_passed,
|
|
38
|
+
days_passed,
|
|
39
|
+
time_passed_formatted_short,
|
|
40
|
+
time_passed_formatted_long,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export function any_datetime_to_string(firebaseTimestamp, options) {
|
|
44
|
+
const { defaultReturnedValue, format, fromFormat, tz, debug } = {
|
|
45
|
+
defaultReturnedValue: options?.defaultReturnedValue ?? "-",
|
|
46
|
+
format: options?.format ?? "DD/MM/YYYY HH:mm:ss",
|
|
47
|
+
fromFormat: options?.fromFormat ?? "DD/MM/YYYY HH:mm:ss",
|
|
48
|
+
tz: options?.tz,
|
|
49
|
+
debug: options?.debug ?? false,
|
|
50
|
+
};
|
|
51
|
+
let date;
|
|
52
|
+
switch (true) {
|
|
53
|
+
case !firebaseTimestamp: {
|
|
54
|
+
return defaultReturnedValue;
|
|
55
|
+
}
|
|
56
|
+
case firebaseTimestamp instanceof Timestamp: {
|
|
57
|
+
date = firebaseTimestamp.toDate();
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case firebaseTimestamp instanceof Date: {
|
|
61
|
+
date = firebaseTimestamp;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case typeof firebaseTimestamp === "string" || typeof firebaseTimestamp === "number": {
|
|
65
|
+
const m = moment.utc(firebaseTimestamp, fromFormat);
|
|
66
|
+
switch (m.isValid()) {
|
|
67
|
+
case true:
|
|
68
|
+
date = m.toDate();
|
|
69
|
+
break;
|
|
70
|
+
default:
|
|
71
|
+
return defaultReturnedValue;
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case !!firebaseTimestamp._seconds: {
|
|
76
|
+
const ft = firebaseTimestamp;
|
|
77
|
+
date = new Date(ft._seconds * 1000 + (ft._nanoseconds ?? 0) / 1000000);
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case !!firebaseTimestamp.seconds: {
|
|
81
|
+
const ft = firebaseTimestamp;
|
|
82
|
+
date = new Date(ft.seconds * 1000 + ft.nanoseconds / 1000000);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
default: {
|
|
86
|
+
if (debug) {
|
|
87
|
+
console.error("Invalid timestamp format: ", firebaseTimestamp);
|
|
88
|
+
}
|
|
89
|
+
return defaultReturnedValue;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
switch (Boolean(tz)) {
|
|
93
|
+
case true:
|
|
94
|
+
return moment(date)
|
|
95
|
+
.tz(tz)
|
|
96
|
+
.format(format);
|
|
97
|
+
default:
|
|
98
|
+
return moment.utc(date).format(format);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export function any_datetime_to_millis(firebaseTimestamp, options) {
|
|
102
|
+
const { defaultReturnedValue, fromFormat, tz, debug } = {
|
|
103
|
+
defaultReturnedValue: options?.defaultReturnedValue ?? 0,
|
|
104
|
+
fromFormat: options?.fromFormat ?? "DD/MM/YYYY HH:mm:ss",
|
|
105
|
+
tz: options?.tz,
|
|
106
|
+
debug: options?.debug ?? false,
|
|
107
|
+
};
|
|
108
|
+
switch (true) {
|
|
109
|
+
case !firebaseTimestamp: {
|
|
110
|
+
return defaultReturnedValue;
|
|
111
|
+
}
|
|
112
|
+
case firebaseTimestamp instanceof Timestamp: {
|
|
113
|
+
return firebaseTimestamp.toMillis();
|
|
114
|
+
}
|
|
115
|
+
case firebaseTimestamp instanceof Date: {
|
|
116
|
+
const ms = firebaseTimestamp.getTime();
|
|
117
|
+
return isNaN(ms) ? defaultReturnedValue : ms;
|
|
118
|
+
}
|
|
119
|
+
case typeof firebaseTimestamp === "string" || typeof firebaseTimestamp === "number": {
|
|
120
|
+
const m = moment.utc(firebaseTimestamp, fromFormat);
|
|
121
|
+
switch (m.isValid()) {
|
|
122
|
+
case true:
|
|
123
|
+
return m.valueOf();
|
|
124
|
+
default:
|
|
125
|
+
return defaultReturnedValue;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
case !!firebaseTimestamp._seconds: {
|
|
129
|
+
const seconds = firebaseTimestamp._seconds;
|
|
130
|
+
const nanos = firebaseTimestamp._nanoseconds ?? 0;
|
|
131
|
+
return seconds * 1000 + Math.floor(nanos / 1000000);
|
|
132
|
+
}
|
|
133
|
+
case !!firebaseTimestamp.seconds: {
|
|
134
|
+
const seconds = firebaseTimestamp.seconds;
|
|
135
|
+
const nanos = firebaseTimestamp.nanoseconds ?? 0;
|
|
136
|
+
return seconds * 1000 + Math.floor(nanos / 1000000);
|
|
137
|
+
}
|
|
138
|
+
default: {
|
|
139
|
+
if (debug) {
|
|
140
|
+
console.error("Invalid timestamp format: ", firebaseTimestamp);
|
|
141
|
+
}
|
|
142
|
+
return defaultReturnedValue;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// old versions of the functions kept for backward compatibility
|
|
147
|
+
/**
|
|
148
|
+
* Converts a Firebase Timestamp object into a formatted string.
|
|
149
|
+
*
|
|
150
|
+
* @param {firebase_timestamp} firebaseTimestamp - The Firebase timestamp object containing _seconds and _nanoseconds.
|
|
151
|
+
* @param {string} [format="DD-MM-YYYY HH:mm:ss"] - Optional the format string used to format the date. Default is "DD-MM-YYYY HH:mm:ss".
|
|
152
|
+
* @returns {string} - A formatted date string according to the specified format or the default format.
|
|
153
|
+
*/
|
|
154
|
+
export function timestamp_to_string(firebaseTimestamp, format = "DD-MM-YYYY HH:mm:ss") {
|
|
155
|
+
const timestamp = new Timestamp(firebaseTimestamp._seconds, firebaseTimestamp._nanoseconds);
|
|
156
|
+
return moment(timestamp.toDate()).utc().format(format);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Converts a Firebase Timestamp object into milliseconds since the Unix epoch.
|
|
160
|
+
*
|
|
161
|
+
* @param {firebase_timestamp} firebaseTimestamp - The Firebase timestamp object containing _seconds and _nanoseconds.
|
|
162
|
+
* @returns {number} - Time in milliseconds
|
|
163
|
+
*/
|
|
164
|
+
export function timestamp_to_millis(firebaseTimestamp) {
|
|
165
|
+
const timestamp = new Timestamp(firebaseTimestamp._seconds, firebaseTimestamp._nanoseconds);
|
|
166
|
+
return timestamp.toMillis();
|
|
167
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export class CacheManager {
|
|
2
|
+
static instance;
|
|
3
|
+
data = {};
|
|
4
|
+
constructor() { }
|
|
5
|
+
static getInstance() {
|
|
6
|
+
if (!CacheManager.instance) {
|
|
7
|
+
CacheManager.instance = new CacheManager();
|
|
8
|
+
}
|
|
9
|
+
return CacheManager.instance;
|
|
10
|
+
}
|
|
11
|
+
setArrayData(key, data) {
|
|
12
|
+
this.data[key] = data;
|
|
13
|
+
}
|
|
14
|
+
getArrayData(key) {
|
|
15
|
+
if (!this.data[key]) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
return this.data[key];
|
|
19
|
+
}
|
|
20
|
+
setObjectData(key, data) {
|
|
21
|
+
this.data[key] = data;
|
|
22
|
+
}
|
|
23
|
+
getObjectData(key, default_value = null) {
|
|
24
|
+
return this.data[key] || default_value;
|
|
25
|
+
}
|
|
26
|
+
getObjectDataAsArray(key, default_value = []) {
|
|
27
|
+
const value = this.data[key];
|
|
28
|
+
if (!value) {
|
|
29
|
+
return default_value;
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
return Object.values(value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export const cache_manager = CacheManager.getInstance();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import moment_timezone from "moment-timezone";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import { isObject } from "lodash";
|
|
4
|
+
import { parse_error } from "../helpers";
|
|
5
|
+
class LoggerManager {
|
|
6
|
+
static instance;
|
|
7
|
+
constructor() { }
|
|
8
|
+
static getInstance() {
|
|
9
|
+
if (!LoggerManager.instance) {
|
|
10
|
+
LoggerManager.instance = new LoggerManager();
|
|
11
|
+
}
|
|
12
|
+
return LoggerManager.instance;
|
|
13
|
+
}
|
|
14
|
+
get_date() {
|
|
15
|
+
return moment_timezone().tz("Asia/Jerusalem").format("DD/MM/YYYY HH:mm:ss.SS");
|
|
16
|
+
}
|
|
17
|
+
is_gcp() {
|
|
18
|
+
return !!(process.env.KUBERNETES_SERVICE_HOST || process.env.K_SERVICE);
|
|
19
|
+
}
|
|
20
|
+
emit(severity, text) {
|
|
21
|
+
if (this.is_gcp()) {
|
|
22
|
+
// GCP: structured JSON on stdout so Cloud Logging reads `severity`, not the stream
|
|
23
|
+
console.log(JSON.stringify({ severity, message: text }));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const method = severity === "ERROR" ? console.error : severity === "WARNING" ? console.warn : console.log;
|
|
27
|
+
method(text);
|
|
28
|
+
}
|
|
29
|
+
log(msg, data) {
|
|
30
|
+
const is_table = !process.env.KUBERNETES_SERVICE_HOST &&
|
|
31
|
+
Array.isArray(data) &&
|
|
32
|
+
data.length > 1 &&
|
|
33
|
+
data.every((val) => {
|
|
34
|
+
if (typeof val === "object" && !Array.isArray(val)) {
|
|
35
|
+
return Object.values(val).every((v) => ["string", "number", "boolean"].includes(typeof v) || v === null);
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}) &&
|
|
39
|
+
data.some((val) => Object.values(val).length > 1);
|
|
40
|
+
if (is_table) {
|
|
41
|
+
console.log(`${this.get_date()} - `, msg, ": ");
|
|
42
|
+
console.table(data);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const text = `${this.get_date()} - ${msg}${data === undefined ? "" : `: ${isObject(data) || Array.isArray(data) ? JSON.stringify(data) : data}`}`;
|
|
46
|
+
this.emit("INFO", text);
|
|
47
|
+
}
|
|
48
|
+
error(msg, data) {
|
|
49
|
+
let text;
|
|
50
|
+
if (axios.isAxiosError(data)) {
|
|
51
|
+
if (!!data.response?.data) {
|
|
52
|
+
text = `${this.get_date()} - ${msg}, axios error: ${data.message}, data: ${JSON.stringify(data)}`;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
text = `${this.get_date()} - ${msg}, axios error: ${data.message}`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
text = `${this.get_date()} - ${msg}${data === undefined ? "" : `: ${JSON.stringify(parse_error(data))}`}`;
|
|
60
|
+
}
|
|
61
|
+
this.emit("ERROR", text);
|
|
62
|
+
}
|
|
63
|
+
warn(msg, data) {
|
|
64
|
+
const text = `${this.get_date()} - ${msg}${data === undefined ? "" : `: ${JSON.stringify(data)}`}`;
|
|
65
|
+
this.emit("WARNING", text);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export const logger = LoggerManager.getInstance();
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import amqp from "amqplib";
|
|
2
|
+
import { randomUUID as random_uuid } from "node:crypto";
|
|
3
|
+
import { logger } from "./logger_manager";
|
|
4
|
+
const SAFE_NAME = /^[A-Za-z0-9_.:-]{1,120}$/;
|
|
5
|
+
/**
|
|
6
|
+
* JSON event transport backed by durable fanout exchanges and durable queues.
|
|
7
|
+
*
|
|
8
|
+
* Each channel name maps to an exchange. Each distinct subscriber name gets a
|
|
9
|
+
* queue and therefore its own copy of every event. Instances sharing the same
|
|
10
|
+
* subscriber name compete for work, which supports horizontal scaling.
|
|
11
|
+
*/
|
|
12
|
+
export class RabbitManager {
|
|
13
|
+
prefetch_count = 10;
|
|
14
|
+
connection;
|
|
15
|
+
channel;
|
|
16
|
+
connecting;
|
|
17
|
+
constructor() {
|
|
18
|
+
if (!(process.env.RABBITMQ_URL ?? "")) {
|
|
19
|
+
logger.warn("Missing ENV parameter RABBITMQ_URL");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async connect() {
|
|
23
|
+
if (this.connection && this.channel)
|
|
24
|
+
return;
|
|
25
|
+
this.connecting ??= this.open_connection().finally(() => {
|
|
26
|
+
this.connecting = undefined;
|
|
27
|
+
});
|
|
28
|
+
await this.connecting;
|
|
29
|
+
}
|
|
30
|
+
async close() {
|
|
31
|
+
const channel = this.channel;
|
|
32
|
+
const connection = this.connection;
|
|
33
|
+
this.channel = undefined;
|
|
34
|
+
this.connection = undefined;
|
|
35
|
+
if (channel)
|
|
36
|
+
await channel.close().catch(() => undefined);
|
|
37
|
+
if (connection)
|
|
38
|
+
await connection.close().catch(() => undefined);
|
|
39
|
+
}
|
|
40
|
+
async push_event(channel_name, payload, options = {}) {
|
|
41
|
+
const channel = await this.declare_exchange(channel_name);
|
|
42
|
+
const { ttl_seconds, headers = {} } = options;
|
|
43
|
+
if (ttl_seconds !== undefined && (!Number.isFinite(ttl_seconds) || ttl_seconds <= 0)) {
|
|
44
|
+
throw new Error("ttl_seconds must be a finite number greater than zero");
|
|
45
|
+
}
|
|
46
|
+
let encoded;
|
|
47
|
+
try {
|
|
48
|
+
encoded = JSON.stringify(payload);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw new Error("payload must be JSON serializable", { cause: error });
|
|
52
|
+
}
|
|
53
|
+
if (encoded === undefined)
|
|
54
|
+
throw new Error("payload must be JSON serializable");
|
|
55
|
+
const event_id = random_uuid();
|
|
56
|
+
const created_at = new Date().toISOString();
|
|
57
|
+
channel.publish(channel_name, "", Buffer.from(encoded, "utf8"), {
|
|
58
|
+
contentType: "application/json",
|
|
59
|
+
contentEncoding: "utf-8",
|
|
60
|
+
persistent: true,
|
|
61
|
+
messageId: event_id,
|
|
62
|
+
timestamp: Math.floor(Date.now() / 1_000),
|
|
63
|
+
...(ttl_seconds === undefined ? {} : { expiration: String(Math.round(ttl_seconds * 1_000)) }),
|
|
64
|
+
headers: { created_at, ...headers },
|
|
65
|
+
});
|
|
66
|
+
await channel.waitForConfirms();
|
|
67
|
+
return event_id;
|
|
68
|
+
}
|
|
69
|
+
subscriptions = [];
|
|
70
|
+
async subscribe_for_channel(channel_name, subscriber_name, handler, options = {}) {
|
|
71
|
+
const { channel, queue_name } = await this.declare_subscription(channel_name, subscriber_name);
|
|
72
|
+
const requeue_on_error = options.requeue_on_error ?? true;
|
|
73
|
+
const reply = await channel.consume(queue_name, (message) => {
|
|
74
|
+
if (!message)
|
|
75
|
+
return;
|
|
76
|
+
void this.handle_message(channel, message, handler, requeue_on_error);
|
|
77
|
+
}, { noAck: false });
|
|
78
|
+
const subscription = {
|
|
79
|
+
queue_name,
|
|
80
|
+
consumer_tag: reply.consumerTag,
|
|
81
|
+
cancel: async () => {
|
|
82
|
+
await channel.cancel(reply.consumerTag);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
this.subscriptions.push(subscription);
|
|
86
|
+
return subscription;
|
|
87
|
+
}
|
|
88
|
+
async cancel_subscriptions() {
|
|
89
|
+
await Promise.all(this.subscriptions.map((subscription) => subscription.cancel()));
|
|
90
|
+
}
|
|
91
|
+
async pull_and_delete(channel_name, subscriber_name) {
|
|
92
|
+
const { channel, queue_name } = await this.declare_subscription(channel_name, subscriber_name);
|
|
93
|
+
const message = await channel.get(queue_name, { noAck: false });
|
|
94
|
+
if (!message)
|
|
95
|
+
return undefined;
|
|
96
|
+
const event = this.to_event(message);
|
|
97
|
+
channel.ack(message);
|
|
98
|
+
return event;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Diagnostic peek implemented as get + nack + requeue.
|
|
102
|
+
* It is not a reliable queue browser: the same event can repeat or move.
|
|
103
|
+
*/
|
|
104
|
+
async pull_and_leave(channel_name, subscriber_name) {
|
|
105
|
+
const { channel, queue_name } = await this.declare_subscription(channel_name, subscriber_name);
|
|
106
|
+
const message = await channel.get(queue_name, { noAck: false });
|
|
107
|
+
if (!message)
|
|
108
|
+
return undefined;
|
|
109
|
+
const event = this.to_event(message);
|
|
110
|
+
channel.nack(message, false, true);
|
|
111
|
+
return event;
|
|
112
|
+
}
|
|
113
|
+
async open_connection() {
|
|
114
|
+
if (!(process.env.RABBITMQ_URL ?? "")) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const connection_url = new URL(process.env.RABBITMQ_URL);
|
|
118
|
+
if (!connection_url.searchParams.has("heartbeat")) {
|
|
119
|
+
connection_url.searchParams.set("heartbeat", "30");
|
|
120
|
+
}
|
|
121
|
+
const connection = await amqp.connect(connection_url.toString(), { timeout: 30_000 });
|
|
122
|
+
const channel = await connection.createConfirmChannel();
|
|
123
|
+
await channel.prefetch(this.prefetch_count);
|
|
124
|
+
this.connection = connection;
|
|
125
|
+
this.channel = channel;
|
|
126
|
+
}
|
|
127
|
+
async get_channel() {
|
|
128
|
+
await this.connect();
|
|
129
|
+
if (!this.channel)
|
|
130
|
+
throw new Error("RabbitMQ channel is not available");
|
|
131
|
+
return this.channel;
|
|
132
|
+
}
|
|
133
|
+
async declare_exchange(channel_name) {
|
|
134
|
+
this.validate_name(channel_name, "channel_name");
|
|
135
|
+
const channel = await this.get_channel();
|
|
136
|
+
await channel.assertExchange(channel_name, "fanout", { durable: true });
|
|
137
|
+
return channel;
|
|
138
|
+
}
|
|
139
|
+
async declare_subscription(channel_name, subscriber_name) {
|
|
140
|
+
this.validate_name(channel_name, "channel_name");
|
|
141
|
+
this.validate_name(subscriber_name, "subscriber_name");
|
|
142
|
+
const channel = await this.declare_exchange(channel_name);
|
|
143
|
+
const queue_name = `${channel_name}.${subscriber_name}`;
|
|
144
|
+
await channel.assertQueue(queue_name, { durable: true });
|
|
145
|
+
await channel.bindQueue(queue_name, channel_name, "");
|
|
146
|
+
return { channel, queue_name };
|
|
147
|
+
}
|
|
148
|
+
async handle_message(channel, message, handler, requeue_on_error) {
|
|
149
|
+
try {
|
|
150
|
+
await handler(this.to_event(message));
|
|
151
|
+
channel.ack(message);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
console.error(`RabbitMQ handler failed for ${message.properties.messageId ?? "unknown"}`, error);
|
|
155
|
+
channel.nack(message, false, requeue_on_error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
to_event(message) {
|
|
159
|
+
let body;
|
|
160
|
+
try {
|
|
161
|
+
body = JSON.parse(message.content.toString("utf8"));
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
throw new Error("received a message that is not valid JSON", { cause: error });
|
|
165
|
+
}
|
|
166
|
+
const headers = { ...message.properties.headers };
|
|
167
|
+
const created_at = headers.created_at;
|
|
168
|
+
return {
|
|
169
|
+
body,
|
|
170
|
+
redelivered: message.fields.redelivered,
|
|
171
|
+
headers,
|
|
172
|
+
...(message.properties.messageId ? { event_id: message.properties.messageId } : {}),
|
|
173
|
+
...(typeof created_at === "string" ? { created_at } : {}),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
validate_name(value, parameter) {
|
|
177
|
+
if (!SAFE_NAME.test(value)) {
|
|
178
|
+
throw new Error(`${parameter} must be 1-120 characters using letters, digits, _, ., :, or -`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export const rabbitmq = new RabbitManager();
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export class TranslationManager {
|
|
2
|
+
static instance;
|
|
3
|
+
data = {};
|
|
4
|
+
constructor() { }
|
|
5
|
+
static getInstance() {
|
|
6
|
+
if (!TranslationManager.instance) {
|
|
7
|
+
TranslationManager.instance = new TranslationManager();
|
|
8
|
+
}
|
|
9
|
+
return TranslationManager.instance;
|
|
10
|
+
}
|
|
11
|
+
setTranslationData(data) {
|
|
12
|
+
this.data = data;
|
|
13
|
+
}
|
|
14
|
+
getTranslationData() {
|
|
15
|
+
return this.data;
|
|
16
|
+
}
|
|
17
|
+
get_translation(scope, lang, entity, key) {
|
|
18
|
+
key = entity + (entity === "" ? "" : "__") + key;
|
|
19
|
+
return this.data[scope][lang][key] || key;
|
|
20
|
+
}
|
|
21
|
+
get_sms(lang, entity, key) {
|
|
22
|
+
try {
|
|
23
|
+
return this.data["sms"][lang][entity + "__" + key];
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return "N/A";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
get_email(lang, entity, key) {
|
|
30
|
+
try {
|
|
31
|
+
return this.data["email"][lang][entity + "__" + key];
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return "N/A";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export const translation_manager = TranslationManager.getInstance();
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { verify_token, json_failed, query_document_optional, get_user_by_identifier } from "../helpers";
|
|
2
|
+
import { logger } from "../managers";
|
|
3
|
+
export const verify_user_auth = async (req, res, next) => {
|
|
4
|
+
try {
|
|
5
|
+
const user = await verify_token(req.headers.authorization);
|
|
6
|
+
req.body.firebase_user = user;
|
|
7
|
+
next();
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
logger.error("error from verify user auth", error);
|
|
11
|
+
res.status(403).send(json_failed(error));
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export const nx_user_login = async (req, res, next) => {
|
|
15
|
+
try {
|
|
16
|
+
const authorization = req.headers.authorization;
|
|
17
|
+
const user_data = await verify_token(authorization);
|
|
18
|
+
const { phone_number, email } = user_data;
|
|
19
|
+
if (!phone_number && !email) {
|
|
20
|
+
throw "Invalid authorization token";
|
|
21
|
+
}
|
|
22
|
+
const nx_user = await get_user_by_identifier(phone_number || email);
|
|
23
|
+
if (!nx_user) {
|
|
24
|
+
throw `No user found with phone number: ${phone_number} or email: ${email}.`;
|
|
25
|
+
}
|
|
26
|
+
const full_name = `${nx_user.first_name || ""} ${nx_user.last_name || ""}`.trim();
|
|
27
|
+
req.body.user = { ...nx_user, full_name };
|
|
28
|
+
next();
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
res.status(403).send(json_failed(error));
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
export const client_login = async (req, res, next) => {
|
|
35
|
+
try {
|
|
36
|
+
const token = req.headers.authorization;
|
|
37
|
+
if (!token) {
|
|
38
|
+
throw new Error("Authorization token not found.");
|
|
39
|
+
}
|
|
40
|
+
const client_data = (await query_document_optional("nx-clients", "api_token", "==", token));
|
|
41
|
+
if (!client_data) {
|
|
42
|
+
throw new Error(`No client found with token: ${token} .`);
|
|
43
|
+
}
|
|
44
|
+
req.body.client = client_data;
|
|
45
|
+
next();
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
res.status(403).send(json_failed(error.message || error));
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { logger } from "../managers";
|
|
2
|
+
/**
|
|
3
|
+
* Utility to handle async errors
|
|
4
|
+
*/
|
|
5
|
+
const async_error_handler = (service) => {
|
|
6
|
+
return (req, res, next) => {
|
|
7
|
+
Promise.resolve(service(req, res)).catch(next);
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Global error-handling middleware.
|
|
12
|
+
*/
|
|
13
|
+
const error_handler = (err, req, res, next) => {
|
|
14
|
+
logger.error("Global Error Handler:", err.stack);
|
|
15
|
+
res.status(500).json({
|
|
16
|
+
status: "error",
|
|
17
|
+
message: err.message || "Internal Server Error",
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
export { error_handler, async_error_handler };
|