akeyless-server-commons 1.0.203 → 1.0.204
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 +0 -1
- package/dist/cjs/helpers/index.js +0 -1
- package/dist/cjs/helpers/index.js.map +1 -1
- 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 +0 -1
- package/dist/esm/helpers/index.js +0 -1
- package/dist/esm/helpers/index.js.map +1 -1
- 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 +12 -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/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 +179 -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 +12 -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/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,215 @@
|
|
|
1
|
+
import { logger } from "../../managers";
|
|
2
|
+
import { get_redis_commander, redis_commander_connected } from "./initialize";
|
|
3
|
+
import { get_doc_key, get_collection_keys, scan_redis_keys } from "./keys";
|
|
4
|
+
import { get_nx_settings, get_all_documents, query_documents, query_documents_by_conditions, query_document_by_conditions, query_document, query_document_optional, get_document_by_id, get_document_by_id_optional, } from "../firebase_helpers";
|
|
5
|
+
const should_use_redis = async (collection_path) => {
|
|
6
|
+
if (!redis_commander_connected) {
|
|
7
|
+
logger.warn(`⚠️ Redis commander not connected, falling back to Firebase for collection: "${collection_path}"`);
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const nx_settings = await get_nx_settings();
|
|
12
|
+
const cache_collections_config = nx_settings.cache_collections_config || {};
|
|
13
|
+
const config = cache_collections_config[collection_path];
|
|
14
|
+
if (!config) {
|
|
15
|
+
logger.warn(`⚠️ Collection "${collection_path}" not found in cache_collections_config, falling back to Firebase`);
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
logger.warn(`⚠️ Failed to validate Redis config for collection: "${collection_path}", falling back to Firebase`);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
const parse_redis_value = (raw) => {
|
|
26
|
+
const parsed = JSON.parse(raw);
|
|
27
|
+
return parsed.data ?? parsed;
|
|
28
|
+
};
|
|
29
|
+
const get_all_collection_docs = async (collection_path) => {
|
|
30
|
+
const commander = get_redis_commander();
|
|
31
|
+
const keys = await scan_redis_keys(get_collection_keys(collection_path), commander);
|
|
32
|
+
if (keys.length === 0)
|
|
33
|
+
return [];
|
|
34
|
+
const values = await commander.mget(keys);
|
|
35
|
+
return values.filter(Boolean).map((v) => {
|
|
36
|
+
const data = parse_redis_value(v);
|
|
37
|
+
const key = keys[values.indexOf(v)];
|
|
38
|
+
/// example key: "nx-users(collection):123(doc_id)"
|
|
39
|
+
const id = key.split(":").slice(1).join(":");
|
|
40
|
+
return { ...data, id };
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
const apply_operator = (doc_value, operator, value) => {
|
|
44
|
+
switch (operator) {
|
|
45
|
+
case "==":
|
|
46
|
+
return doc_value === value;
|
|
47
|
+
case "!=":
|
|
48
|
+
return doc_value !== value;
|
|
49
|
+
case "<":
|
|
50
|
+
return doc_value < value;
|
|
51
|
+
case "<=":
|
|
52
|
+
return doc_value <= value;
|
|
53
|
+
case ">":
|
|
54
|
+
return doc_value > value;
|
|
55
|
+
case ">=":
|
|
56
|
+
return doc_value >= value;
|
|
57
|
+
case "in":
|
|
58
|
+
return Array.isArray(value) && value.includes(doc_value);
|
|
59
|
+
case "not-in":
|
|
60
|
+
return Array.isArray(value) && !value.includes(doc_value);
|
|
61
|
+
case "array-contains":
|
|
62
|
+
return Array.isArray(doc_value) && doc_value.includes(value);
|
|
63
|
+
case "array-contains-any":
|
|
64
|
+
return Array.isArray(doc_value) && Array.isArray(value) && value.some((v) => doc_value.includes(v));
|
|
65
|
+
default:
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const filter_by_condition = (docs, field_name, operator, value) => {
|
|
70
|
+
return docs.filter((doc) => apply_operator(doc[field_name], operator, value));
|
|
71
|
+
};
|
|
72
|
+
const filter_by_conditions = (docs, conditions) => {
|
|
73
|
+
return conditions.reduce((filtered, { field_name, operator, value }) => {
|
|
74
|
+
return filter_by_condition(filtered, field_name, operator, value);
|
|
75
|
+
}, docs);
|
|
76
|
+
};
|
|
77
|
+
// ── extract ──
|
|
78
|
+
export const redis_simple_extract_data = (raw) => {
|
|
79
|
+
const data = parse_redis_value(raw);
|
|
80
|
+
return data;
|
|
81
|
+
};
|
|
82
|
+
// ── documents ──
|
|
83
|
+
export const redis_get_all_documents = async (collection_path) => {
|
|
84
|
+
if (!(await should_use_redis(collection_path))) {
|
|
85
|
+
return get_all_documents(collection_path);
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return await get_all_collection_docs(collection_path);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
logger.error("Error fetching documents from Redis:", error);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
export const redis_query_documents = async (collection_path, field_name, operator, value) => {
|
|
96
|
+
if (!(await should_use_redis(collection_path))) {
|
|
97
|
+
return query_documents(collection_path, field_name, operator, value);
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const all_docs = await get_all_collection_docs(collection_path);
|
|
101
|
+
return filter_by_condition(all_docs, field_name, operator, value);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
logger.error(`Error querying documents from Redis: ${collection_path} - ${field_name} - ${operator} - ${value} `, error);
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
export const redis_query_documents_by_conditions = async (collection_path, where_conditions) => {
|
|
109
|
+
if (!(await should_use_redis(collection_path))) {
|
|
110
|
+
return query_documents_by_conditions(collection_path, where_conditions);
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const all_docs = await get_all_collection_docs(collection_path);
|
|
114
|
+
return filter_by_conditions(all_docs, where_conditions);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
logger.error(`Error querying documents from Redis: ${collection_path} - ${JSON.stringify(where_conditions)} `, error);
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
export const redis_query_document_by_conditions = async (collection_path, where_conditions, ignore_logs = false) => {
|
|
122
|
+
if (!(await should_use_redis(collection_path))) {
|
|
123
|
+
return query_document_by_conditions(collection_path, where_conditions, !ignore_logs);
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const all_docs = await get_all_collection_docs(collection_path);
|
|
127
|
+
const documents = filter_by_conditions(all_docs, where_conditions);
|
|
128
|
+
if (!documents[0]) {
|
|
129
|
+
throw "no data returned from Redis";
|
|
130
|
+
}
|
|
131
|
+
return documents[0];
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (!ignore_logs) {
|
|
135
|
+
logger.error(`Error querying document from Redis: ${collection_path} - ${JSON.stringify(where_conditions)} `, error);
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
// ── document ──
|
|
141
|
+
export const redis_query_document = async (collection_path, field_name, operator, value, ignore_log = false) => {
|
|
142
|
+
if (!(await should_use_redis(collection_path))) {
|
|
143
|
+
return query_document(collection_path, field_name, operator, value, ignore_log);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const all_docs = await get_all_collection_docs(collection_path);
|
|
147
|
+
const documents = filter_by_condition(all_docs, field_name, operator, value);
|
|
148
|
+
const base_error = `collection: ${collection_path}, field_name: ${field_name}, operator: ${operator}, value:${value}`;
|
|
149
|
+
if (documents.length < 1) {
|
|
150
|
+
throw `[Redis] No data to return from: ${base_error}`;
|
|
151
|
+
}
|
|
152
|
+
if (documents.length > 1) {
|
|
153
|
+
throw `[Redis] Multiple documents found in: ${base_error}`;
|
|
154
|
+
}
|
|
155
|
+
return documents[0];
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
if (!ignore_log) {
|
|
159
|
+
logger.error("Error querying document from Redis: " + JSON.stringify({ collection_path, field_name, operator, value }), error);
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
export const redis_query_document_optional = async (collection_path, field_name, operator, value, ignore_log = true) => {
|
|
165
|
+
if (!(await should_use_redis(collection_path))) {
|
|
166
|
+
return query_document_optional(collection_path, field_name, operator, value, ignore_log);
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const all_docs = await get_all_collection_docs(collection_path);
|
|
170
|
+
const documents = filter_by_condition(all_docs, field_name, operator, value);
|
|
171
|
+
return documents[0] || null;
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (!ignore_log) {
|
|
175
|
+
logger.error("Error querying optional document from Redis: " + JSON.stringify({ collection_path, field_name, operator, value }), error);
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
export const redis_get_document_by_id = async (collection_path, doc_id) => {
|
|
181
|
+
if (!(await should_use_redis(collection_path))) {
|
|
182
|
+
return get_document_by_id(collection_path, doc_id);
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const commander = get_redis_commander();
|
|
186
|
+
const key = get_doc_key(collection_path, doc_id);
|
|
187
|
+
const raw = await commander.get(key);
|
|
188
|
+
if (!raw) {
|
|
189
|
+
throw "Document not found in Redis, document id: " + doc_id;
|
|
190
|
+
}
|
|
191
|
+
return redis_simple_extract_data(raw);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
logger.error("error from redis_get_document_by_id", error);
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
export const redis_get_document_by_id_optional = async (collection_path, doc_id) => {
|
|
199
|
+
if (!(await should_use_redis(collection_path))) {
|
|
200
|
+
return get_document_by_id_optional(collection_path, doc_id);
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const commander = get_redis_commander();
|
|
204
|
+
const key = get_doc_key(collection_path, doc_id);
|
|
205
|
+
const raw = await commander.get(key);
|
|
206
|
+
if (!raw) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
return redis_simple_extract_data(raw);
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
logger.error("error from redis_get_document_by_id_optional", error);
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import Redis from "ioredis";
|
|
2
|
+
import { REDIS_UPDATES_PREFIX } from "akeyless-types-commons";
|
|
3
|
+
import { get_collection_keys } from "./keys";
|
|
4
|
+
import { init_env_variables } from "../global_helpers";
|
|
5
|
+
import { logger } from "../../managers";
|
|
6
|
+
import dotenv from "dotenv";
|
|
7
|
+
dotenv.config();
|
|
8
|
+
export let redis_commander_connected = false;
|
|
9
|
+
export let redis_listener_connected = false;
|
|
10
|
+
const MAX_RETRY_ATTEMPTS = 2;
|
|
11
|
+
let redis_commander;
|
|
12
|
+
let redis_listener;
|
|
13
|
+
let redis_initialized = false;
|
|
14
|
+
function create_redis_instance(role) {
|
|
15
|
+
let retry_attempts = 0;
|
|
16
|
+
const base_config = {
|
|
17
|
+
host: init_env_variables(["redis_ip"]).redis_ip,
|
|
18
|
+
lazyConnect: true,
|
|
19
|
+
enableOfflineQueue: false,
|
|
20
|
+
maxRetriesPerRequest: 0,
|
|
21
|
+
reconnectOnError: () => false,
|
|
22
|
+
retryStrategy(times) {
|
|
23
|
+
retry_attempts = times;
|
|
24
|
+
if (retry_attempts >= MAX_RETRY_ATTEMPTS) {
|
|
25
|
+
logger.error(`❌ Redis ${role} failed after ${MAX_RETRY_ATTEMPTS} attempts. Stopping retries.`);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const delay = 1000;
|
|
29
|
+
logger.warn(`⚠️ Redis ${role} retry attempt ${retry_attempts}/${MAX_RETRY_ATTEMPTS} in ${delay}ms...`);
|
|
30
|
+
return delay;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
// Listener needs enableReadyCheck: false because it will be in subscriber mode
|
|
34
|
+
// and cannot respond to PING commands
|
|
35
|
+
const config = role === "listener"
|
|
36
|
+
? { ...base_config, enableReadyCheck: false }
|
|
37
|
+
: base_config;
|
|
38
|
+
const client = new Redis(config);
|
|
39
|
+
client.on("connect", () => {
|
|
40
|
+
logger.log(`✅ Redis ${role} connected`);
|
|
41
|
+
if (role === "commander")
|
|
42
|
+
redis_commander_connected = true;
|
|
43
|
+
if (role === "listener")
|
|
44
|
+
redis_listener_connected = true;
|
|
45
|
+
});
|
|
46
|
+
client.on("error", (err) => logger.error(`❌ Redis ${role} error`, err));
|
|
47
|
+
client.on("close", () => {
|
|
48
|
+
logger.warn(`ℹ️ Redis ${role} connection closed`);
|
|
49
|
+
if (role === "commander")
|
|
50
|
+
redis_commander_connected = false;
|
|
51
|
+
if (role === "listener")
|
|
52
|
+
redis_listener_connected = false;
|
|
53
|
+
});
|
|
54
|
+
client.on("end", () => {
|
|
55
|
+
logger.warn(`ℹ️ Redis ${role} connection ended`);
|
|
56
|
+
if (role === "commander")
|
|
57
|
+
redis_commander_connected = false;
|
|
58
|
+
if (role === "listener")
|
|
59
|
+
redis_listener_connected = false;
|
|
60
|
+
});
|
|
61
|
+
client.connect().catch((err) => {
|
|
62
|
+
logger.error(`❌ Redis ${role} initial connect failed`, err);
|
|
63
|
+
});
|
|
64
|
+
return client;
|
|
65
|
+
}
|
|
66
|
+
export const init_redis = () => {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
if (redis_initialized) {
|
|
69
|
+
resolve();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
redis_commander = create_redis_instance("commander");
|
|
73
|
+
redis_listener = create_redis_instance("listener");
|
|
74
|
+
redis_initialized = true;
|
|
75
|
+
let commander_ready = false;
|
|
76
|
+
let listener_ready = false;
|
|
77
|
+
let listener_subscribed = false;
|
|
78
|
+
const check_and_resolve = () => {
|
|
79
|
+
if (commander_ready && listener_ready) {
|
|
80
|
+
resolve();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
redis_commander.on("connect", () => {
|
|
84
|
+
commander_ready = true;
|
|
85
|
+
check_and_resolve();
|
|
86
|
+
});
|
|
87
|
+
redis_listener.on("connect", () => {
|
|
88
|
+
listener_ready = true;
|
|
89
|
+
// Only subscribe once, not on every reconnect
|
|
90
|
+
// ioredis will automatically resubscribe on reconnection
|
|
91
|
+
if (!listener_subscribed) {
|
|
92
|
+
listener_subscribed = true;
|
|
93
|
+
const redis_pattern = get_collection_keys(REDIS_UPDATES_PREFIX);
|
|
94
|
+
redis_listener
|
|
95
|
+
.psubscribe(redis_pattern)
|
|
96
|
+
.then(() => logger.log(`✅ Subscribed to Redis pattern: ${redis_pattern}`))
|
|
97
|
+
.catch((err) => logger.error(`❌ Failed to psubscribe to ${redis_pattern}`, err));
|
|
98
|
+
}
|
|
99
|
+
check_and_resolve();
|
|
100
|
+
});
|
|
101
|
+
// Handle case where Redis connections fail
|
|
102
|
+
const connection_timeout = setTimeout(() => {
|
|
103
|
+
if (!commander_ready || !listener_ready) {
|
|
104
|
+
logger.warn("⚠️ Redis connection timeout, proceeding without Redis");
|
|
105
|
+
resolve();
|
|
106
|
+
}
|
|
107
|
+
}, 5000);
|
|
108
|
+
redis_commander.on("error", () => {
|
|
109
|
+
if (!commander_ready && !listener_ready) {
|
|
110
|
+
clearTimeout(connection_timeout);
|
|
111
|
+
reject();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
redis_listener.on("error", () => {
|
|
115
|
+
if (!commander_ready && !listener_ready) {
|
|
116
|
+
clearTimeout(connection_timeout);
|
|
117
|
+
reject();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
process.on("unhandledRejection", (err) => {
|
|
121
|
+
logger.error("❌ Unhandled promise rejection", err);
|
|
122
|
+
});
|
|
123
|
+
process.on("uncaughtException", (err) => {
|
|
124
|
+
logger.error("❌ Uncaught exception", err);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
export const get_redis_commander = () => {
|
|
129
|
+
if (!redis_commander) {
|
|
130
|
+
throw new Error("Redis commander not initialized. Call init_redis() first.");
|
|
131
|
+
}
|
|
132
|
+
return redis_commander;
|
|
133
|
+
};
|
|
134
|
+
export const get_redis_listener = () => {
|
|
135
|
+
if (!redis_listener) {
|
|
136
|
+
throw new Error("Redis listener not initialized. Call init_redis() first.");
|
|
137
|
+
}
|
|
138
|
+
return redis_listener;
|
|
139
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { REDIS_UPDATES_PREFIX } from "akeyless-types-commons";
|
|
2
|
+
const get_key = (...args) => {
|
|
3
|
+
return args.join(":");
|
|
4
|
+
};
|
|
5
|
+
export const get_doc_key = (collection, doc_id) => {
|
|
6
|
+
return get_key(collection, doc_id);
|
|
7
|
+
};
|
|
8
|
+
export const get_collection_keys = (collection) => {
|
|
9
|
+
return get_key(collection, "*");
|
|
10
|
+
};
|
|
11
|
+
export const get_channel = (...args) => {
|
|
12
|
+
return get_key(REDIS_UPDATES_PREFIX, ...args);
|
|
13
|
+
};
|
|
14
|
+
export const scan_redis_keys = async (pattern, redis_publisher) => {
|
|
15
|
+
const found_keys = [];
|
|
16
|
+
let cursor = "0";
|
|
17
|
+
do {
|
|
18
|
+
const [next_cursor, keys] = await redis_publisher.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
19
|
+
cursor = next_cursor;
|
|
20
|
+
found_keys.push(...keys);
|
|
21
|
+
} while (cursor !== "0");
|
|
22
|
+
return found_keys;
|
|
23
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { get_redis_commander, get_redis_listener, redis_commander_connected, redis_listener_connected } from "./initialize";
|
|
2
|
+
import { get_nx_settings, parse_add_update_as_array, parse_add_update_as_object, parse_delete_as_array, parse_delete_as_object, snapshot, } from "../firebase_helpers";
|
|
3
|
+
import { get_collection_keys, scan_redis_keys } from "./keys";
|
|
4
|
+
import { Timestamp } from "firebase-admin/firestore";
|
|
5
|
+
import { logger } from "../../managers";
|
|
6
|
+
const subscription_collections = new Set();
|
|
7
|
+
export const redis_snapshots_bulk = async (configs) => {
|
|
8
|
+
if (configs.length === 0) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
/// on first time
|
|
12
|
+
for (const config of configs) {
|
|
13
|
+
const validation = await validate_config(config);
|
|
14
|
+
if (!validation.success) {
|
|
15
|
+
logger.warn(validation.message, config);
|
|
16
|
+
if (validation.trigger_firebase_snapshot) {
|
|
17
|
+
await snapshot(config);
|
|
18
|
+
}
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
await parse_redis_snapshot(config);
|
|
22
|
+
}
|
|
23
|
+
/// on update
|
|
24
|
+
get_redis_listener().on("pmessage", async (pattern, channel, message) => {
|
|
25
|
+
const { collection_name, update_type, data: doc } = JSON.parse(message);
|
|
26
|
+
const update = [convert_object_timestamps(doc)];
|
|
27
|
+
const relevant_configs = configs.filter((config) => config.collection_name === collection_name);
|
|
28
|
+
for (const config of relevant_configs) {
|
|
29
|
+
await parse_redis_snapshot(config, { update_type, update });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
const validate_config = async (config) => {
|
|
34
|
+
const nx_settings = await get_nx_settings();
|
|
35
|
+
const cache_collections_config = nx_settings.cache_collections_config || {};
|
|
36
|
+
const { collection_name, cache_name = collection_name, subscription_type = "firebase" } = config;
|
|
37
|
+
if (!redis_commander_connected || !redis_listener_connected) {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
trigger_firebase_snapshot: true,
|
|
41
|
+
message: `⚠️ Redis commander or listener not connected, triggered firebase snapshot for collection: "${collection_name}"`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (subscription_type === "firebase") {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
trigger_firebase_snapshot: true,
|
|
48
|
+
message: `⚠️ subscription type is "firebase", triggered firebase snapshot for collection: "${collection_name}"`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (!cache_collections_config[collection_name] || cache_collections_config[collection_name].sync_direction === "redis_to_firebase") {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
trigger_firebase_snapshot: true,
|
|
55
|
+
message: `⚠️ collection: "${collection_name}" not found in cache_collections_config in "nx-settings" or sync_direction is "redis_to_firebase", triggered firebase snapshot for collection: "${collection_name}"`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (subscription_collections.has(cache_name)) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
trigger_firebase_snapshot: false,
|
|
62
|
+
message: `⚠️ collection: "${collection_name}" already has a subscription, skipped redis snapshot for collection: "${collection_name}"`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
subscription_collections.add(cache_name);
|
|
66
|
+
return { success: true };
|
|
67
|
+
};
|
|
68
|
+
const parse_redis_snapshot = async (config, redis_update) => {
|
|
69
|
+
const { collection_name: config_collection_name, cache_name = config_collection_name, extra_parsers, parse_as, debug, on_add, on_remove, on_modify, } = config;
|
|
70
|
+
if (!redis_update) {
|
|
71
|
+
const init_data = await get_collection_data(config_collection_name);
|
|
72
|
+
default_parsers(parse_as, init_data, config);
|
|
73
|
+
if (debug?.on_first_time) {
|
|
74
|
+
logger.log(`${cache_name} => Redis snapshot on first time: `, debug.on_first_time === "documents" ? init_data : { length: init_data.length });
|
|
75
|
+
}
|
|
76
|
+
config.on_first_time?.(init_data, config);
|
|
77
|
+
extra_parsers?.forEach((parser) => {
|
|
78
|
+
if (debug?.extra_parsers?.on_first_time) {
|
|
79
|
+
logger.log(`${cache_name} => Redis snapshot extra parsers on first time: `, debug.extra_parsers.on_first_time === "documents" ? init_data : { length: init_data.length });
|
|
80
|
+
}
|
|
81
|
+
parser?.on_first_time?.(init_data, config);
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!subscription_collections.has(cache_name)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const { update_type, update } = redis_update;
|
|
89
|
+
switch (update_type) {
|
|
90
|
+
case "add":
|
|
91
|
+
if (debug?.on_add) {
|
|
92
|
+
logger.log(`${cache_name} => Redis snapshot on add: `, update);
|
|
93
|
+
}
|
|
94
|
+
default_parsers(parse_as, update, config);
|
|
95
|
+
on_add?.(update, config);
|
|
96
|
+
extra_parsers?.forEach((parser) => {
|
|
97
|
+
if (debug?.extra_parsers?.on_add) {
|
|
98
|
+
logger.log(`${cache_name} => Redis snapshot extra parsers on add: `, update);
|
|
99
|
+
}
|
|
100
|
+
parser?.on_add?.(update, config);
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
case "update":
|
|
104
|
+
if (debug?.on_modify) {
|
|
105
|
+
logger.log(`${cache_name} => Redis snapshot on modify: `, update);
|
|
106
|
+
}
|
|
107
|
+
default_parsers(parse_as, update, config);
|
|
108
|
+
on_modify?.(update, config);
|
|
109
|
+
extra_parsers?.forEach((parser) => {
|
|
110
|
+
if (debug?.extra_parsers?.on_modify) {
|
|
111
|
+
logger.log(`${cache_name} => Redis snapshot extra parsers on modify: `, update);
|
|
112
|
+
}
|
|
113
|
+
parser?.on_modify?.(update, config);
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
case "delete":
|
|
117
|
+
if (debug?.on_remove) {
|
|
118
|
+
logger.log(`${cache_name} => Redis snapshot on remove: `, update);
|
|
119
|
+
}
|
|
120
|
+
if (parse_as === "object") {
|
|
121
|
+
parse_delete_as_object(update, config);
|
|
122
|
+
}
|
|
123
|
+
if (parse_as === "array") {
|
|
124
|
+
parse_delete_as_array(update, config);
|
|
125
|
+
}
|
|
126
|
+
on_remove?.(update, config);
|
|
127
|
+
extra_parsers?.forEach((parser) => {
|
|
128
|
+
if (debug?.extra_parsers?.on_remove) {
|
|
129
|
+
logger.log(`${cache_name} => Redis snapshot extra parsers on remove: `, update);
|
|
130
|
+
}
|
|
131
|
+
parser?.on_remove?.(update, config);
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
default:
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const default_parsers = (parse_as, update, config) => {
|
|
139
|
+
if (parse_as === "object") {
|
|
140
|
+
parse_add_update_as_object(update, config);
|
|
141
|
+
}
|
|
142
|
+
if (parse_as === "array") {
|
|
143
|
+
parse_add_update_as_array(update, config);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const get_collection_data = async (collection_name) => {
|
|
147
|
+
const redis_commander = get_redis_commander();
|
|
148
|
+
const keys = await scan_redis_keys(get_collection_keys(collection_name), redis_commander);
|
|
149
|
+
let collection_data = [];
|
|
150
|
+
if (keys.length > 0) {
|
|
151
|
+
const values = await redis_commander.mget(keys);
|
|
152
|
+
collection_data = values.filter(Boolean).map((v) => JSON.parse(v).data);
|
|
153
|
+
}
|
|
154
|
+
return collection_data.map((data) => convert_object_timestamps(data));
|
|
155
|
+
};
|
|
156
|
+
const convert_object_timestamps = (data) => {
|
|
157
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
158
|
+
if (typeof value === "object" && value && value._seconds !== undefined) {
|
|
159
|
+
data[key] = new Timestamp(value._seconds, value._nanoseconds);
|
|
160
|
+
}
|
|
161
|
+
if (typeof value === "object" && value && value._seconds === undefined) {
|
|
162
|
+
convert_object_timestamps(value);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
return data;
|
|
166
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
import { logger } from "../managers";
|
|
4
|
+
import { init_env_variables, init_snapshots } from "./";
|
|
5
|
+
import { error_handler } from "../middlewares/error_handling";
|
|
6
|
+
import { request_logger, trim_body_middleware } from "../middlewares";
|
|
7
|
+
import { init_redis } from "./redis/initialize";
|
|
8
|
+
import axios from "axios";
|
|
9
|
+
import https from "https";
|
|
10
|
+
import http from "http";
|
|
11
|
+
import { rabbitmq } from "../managers/rabbitmq_manager";
|
|
12
|
+
export const start_server = async (main_router, project_name, version, options = {}) => {
|
|
13
|
+
let env_data = init_env_variables(["mode"]);
|
|
14
|
+
const { port = Number(env_data.port), log_requests, initialize_redis = true, enable_axios_keep_alive = true } = options;
|
|
15
|
+
const app = express();
|
|
16
|
+
app.use(cors());
|
|
17
|
+
app.use(express.json({ limit: "10mb" }));
|
|
18
|
+
app.use(express.urlencoded({ limit: "10mb", extended: true }));
|
|
19
|
+
app.use(trim_body_middleware());
|
|
20
|
+
app.use(request_logger(log_requests || {}));
|
|
21
|
+
main_router(app);
|
|
22
|
+
app.use(error_handler);
|
|
23
|
+
if (enable_axios_keep_alive) {
|
|
24
|
+
keep_axios_alive();
|
|
25
|
+
}
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
app.listen(port, async () => {
|
|
28
|
+
logger.log(`Server is running at http://localhost:${port}`);
|
|
29
|
+
logger.log("project status", { project_name, version, environment: env_data.mode });
|
|
30
|
+
if (initialize_redis) {
|
|
31
|
+
try {
|
|
32
|
+
logger.log("Initializing Redis...");
|
|
33
|
+
await init_redis();
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
logger.warn("Redis unavailable, continuing without it", { err });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
resolve(app);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
export const basic_init = async (main_router, project_name, version, options = {}) => {
|
|
44
|
+
const { init_snapshot_options, on_shutdown } = options;
|
|
45
|
+
try {
|
|
46
|
+
const app = await start_server(main_router, project_name, version, options);
|
|
47
|
+
await init_snapshots(init_snapshot_options);
|
|
48
|
+
const shutdown = async (signal) => {
|
|
49
|
+
logger.log(`Received ${signal}, shutting down...`);
|
|
50
|
+
try {
|
|
51
|
+
await rabbitmq.cancel_subscriptions();
|
|
52
|
+
await rabbitmq.close();
|
|
53
|
+
await on_shutdown?.();
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
logger.error("Shutdown error", err);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
process.exit(0);
|
|
60
|
+
};
|
|
61
|
+
process.once("SIGINT", () => void shutdown("SIGINT"));
|
|
62
|
+
process.once("SIGTERM", () => void shutdown("SIGTERM"));
|
|
63
|
+
return app;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
logger.error("Error from init function: ", error);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const keep_axios_alive = () => {
|
|
71
|
+
axios.defaults.httpsAgent = new https.Agent({
|
|
72
|
+
keepAlive: true,
|
|
73
|
+
maxSockets: 100,
|
|
74
|
+
maxFreeSockets: 20,
|
|
75
|
+
timeout: 60000,
|
|
76
|
+
});
|
|
77
|
+
axios.defaults.httpAgent = new http.Agent({
|
|
78
|
+
keepAlive: true,
|
|
79
|
+
maxSockets: 100,
|
|
80
|
+
maxFreeSockets: 20,
|
|
81
|
+
timeout: 60000,
|
|
82
|
+
});
|
|
83
|
+
};
|