@go-mailer/jarvis 10.9.3 → 10.9.4
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/lib/throttlers/wallet/index.js +112 -66
- package/package.json +1 -1
|
@@ -1,113 +1,159 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const path = require(
|
|
3
|
-
|
|
4
|
-
const WalletThrottler = async ({
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const WalletThrottler = async ({
|
|
5
|
+
redisClient = null,
|
|
6
|
+
RMQBroker,
|
|
7
|
+
wallet_charge_queue,
|
|
8
|
+
wallet_refund_queue,
|
|
9
|
+
}) => {
|
|
5
10
|
if (!redisClient) {
|
|
6
|
-
throw new Error(
|
|
11
|
+
throw new Error("Redis client is required for WalletThrottler");
|
|
7
12
|
}
|
|
8
13
|
|
|
9
|
-
const lua_script = fs.readFileSync(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const lua_script = fs.readFileSync(
|
|
15
|
+
path.join(__dirname, "throttler.lua"),
|
|
16
|
+
"utf-8",
|
|
17
|
+
);
|
|
18
|
+
const deduct_sha = await redisClient.scriptLoad(lua_script);
|
|
19
|
+
|
|
20
|
+
const deductCredits = async ({
|
|
21
|
+
tenant_id,
|
|
22
|
+
resource_id,
|
|
23
|
+
resource_type,
|
|
24
|
+
wallet_type,
|
|
25
|
+
amount,
|
|
26
|
+
min_allowed = 0,
|
|
27
|
+
}) => {
|
|
28
|
+
const balance_key = `wallet:tenant:${tenant_id}:wallet:${wallet_type}`;
|
|
29
|
+
const usage_key = `wallet:tenant:${tenant_id}:wallet:${wallet_type}:${resource_type}:${resource_id}:usage`;
|
|
30
|
+
|
|
31
|
+
let result;
|
|
17
32
|
try {
|
|
18
33
|
result = await redisClient.evalSha(deduct_sha, {
|
|
19
34
|
keys: [balance_key, usage_key],
|
|
20
|
-
arguments: [amount.toString(), min_allowed.toString()]
|
|
21
|
-
})
|
|
35
|
+
arguments: [amount.toString(), min_allowed.toString()],
|
|
36
|
+
});
|
|
22
37
|
} catch (err) {
|
|
23
38
|
result = await redisClient.eval(lua_script, {
|
|
24
39
|
keys: [balance_key, usage_key],
|
|
25
|
-
arguments: [amount.toString(), min_allowed.toString()]
|
|
26
|
-
})
|
|
40
|
+
arguments: [amount.toString(), min_allowed.toString()],
|
|
41
|
+
});
|
|
27
42
|
}
|
|
28
43
|
|
|
29
|
-
const [success, status, newBalance] = result
|
|
44
|
+
const [success, status, newBalance] = result;
|
|
30
45
|
if (success === 1) {
|
|
31
|
-
return { success: true, newBalance: parseInt(newBalance, 10) }
|
|
46
|
+
return { success: true, newBalance: parseInt(newBalance, 10) };
|
|
32
47
|
}
|
|
33
48
|
|
|
34
|
-
if (status ===
|
|
35
|
-
return { success: false, reason:
|
|
49
|
+
if (status === "no_balance") {
|
|
50
|
+
return { success: false, reason: "no_wallet" };
|
|
36
51
|
}
|
|
37
52
|
|
|
38
|
-
return {
|
|
39
|
-
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
reason: "insufficient",
|
|
56
|
+
currentBalance: parseInt(newBalance, 10),
|
|
57
|
+
};
|
|
58
|
+
};
|
|
40
59
|
|
|
41
60
|
const deductUsedCreditsFromDB = async () => {
|
|
42
61
|
if (!RMQBroker) {
|
|
43
|
-
throw new Error(
|
|
62
|
+
throw new Error("RMQBroker is required for WalletThrottler");
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
if (!wallet_charge_queue) {
|
|
47
|
-
throw new Error(
|
|
66
|
+
throw new Error("wallet_charge_queue is required for WalletThrottler");
|
|
48
67
|
}
|
|
49
68
|
|
|
50
|
-
const used_keys = await redisClient.keys(
|
|
69
|
+
const used_keys = await redisClient.keys(
|
|
70
|
+
"wallet:tenant:*:wallet:*:*:*:usage",
|
|
71
|
+
);
|
|
51
72
|
for (const usage_key of used_keys) {
|
|
52
|
-
const usage = await redisClient.get(usage_key)
|
|
53
|
-
const [, , tenant_id, , wallet_type, resource_type, resource_id] =
|
|
54
|
-
|
|
73
|
+
const usage = await redisClient.get(usage_key);
|
|
74
|
+
const [, , tenant_id, , wallet_type, resource_type, resource_id] =
|
|
75
|
+
usage_key.split(":");
|
|
76
|
+
if (usage && parseInt(usage, 10) === 0) continue;
|
|
55
77
|
|
|
56
|
-
await redisClient.multi().set(usage_key, 0).exec()
|
|
78
|
+
await redisClient.multi().set(usage_key, 0).exec();
|
|
57
79
|
RMQBroker.publish(
|
|
58
80
|
wallet_charge_queue,
|
|
59
|
-
JSON.stringify({
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
81
|
+
JSON.stringify({
|
|
82
|
+
tenant_id,
|
|
83
|
+
resource_id,
|
|
84
|
+
resource_type,
|
|
85
|
+
wallet_type,
|
|
86
|
+
cost: parseInt(usage, 10),
|
|
87
|
+
timestamp: Date.now(),
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
console.log(`Synced ${usage} used credits for ${tenant_id}`);
|
|
63
92
|
}
|
|
64
|
-
}
|
|
93
|
+
};
|
|
65
94
|
|
|
66
95
|
const loadCache = async ({ tenant_id, wallet_type, balance }) => {
|
|
67
|
-
const balance_key = `wallet:tenant:${tenant_id}:wallet:${wallet_type}
|
|
96
|
+
const balance_key = `wallet:tenant:${tenant_id}:wallet:${wallet_type}`;
|
|
68
97
|
|
|
69
98
|
// Load balance from DB if missing
|
|
70
|
-
const redis_balance = await redisClient.get(balance_key)
|
|
99
|
+
const redis_balance = await redisClient.get(balance_key);
|
|
71
100
|
if (redis_balance === null) {
|
|
72
|
-
await redisClient.set(balance_key, balance)
|
|
101
|
+
await redisClient.set(balance_key, balance);
|
|
73
102
|
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const refundCredits = async ({
|
|
77
|
-
|
|
78
|
-
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const refundCredits = async ({
|
|
106
|
+
tenant_id,
|
|
107
|
+
wallet_type,
|
|
108
|
+
amount,
|
|
109
|
+
resource_id,
|
|
110
|
+
resource_type,
|
|
111
|
+
syncWithDb = true,
|
|
112
|
+
}) => {
|
|
113
|
+
const balance_key = `wallet:tenant:${tenant_id}:wallet:${wallet_type}`;
|
|
114
|
+
const usage_key = `wallet:tenant:${tenant_id}:wallet:${wallet_type}:${resource_type}:${resource_id}:usage`;
|
|
79
115
|
|
|
80
116
|
// Atomic refund in Redis
|
|
81
117
|
await redisClient
|
|
82
118
|
.multi()
|
|
83
119
|
.incrBy(balance_key, amount)
|
|
84
120
|
.decrBy(usage_key, amount) // reduce tracked usage
|
|
85
|
-
.exec()
|
|
121
|
+
.exec();
|
|
86
122
|
|
|
87
123
|
// Async DB refund
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
124
|
+
if (syncWithDb) {
|
|
125
|
+
RMQBroker.publish(
|
|
126
|
+
wallet_refund_queue,
|
|
127
|
+
JSON.stringify({
|
|
128
|
+
tenant_id,
|
|
129
|
+
amount: 0,
|
|
130
|
+
units: amount,
|
|
131
|
+
resource_id,
|
|
132
|
+
resource_type,
|
|
133
|
+
type: wallet_type,
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
console.log(
|
|
138
|
+
`Refunded ${amount} credits for tenant ${tenant_id} on wallet ${wallet_type}`,
|
|
139
|
+
);
|
|
140
|
+
};
|
|
101
141
|
|
|
102
142
|
const topUpCredits = async ({ tenant_id, wallet_type, amount = 0 }) => {
|
|
103
|
-
const balance_key = `wallet:tenant:${tenant_id}:wallet:${wallet_type}
|
|
143
|
+
const balance_key = `wallet:tenant:${tenant_id}:wallet:${wallet_type}`;
|
|
104
144
|
|
|
105
145
|
// Increment Redis (instant across all instances)
|
|
106
|
-
await redisClient.incrBy(balance_key, amount)
|
|
107
|
-
console.log(`Top-up ${amount} for ${tenant_id} on wallet ${wallet_type}`)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
146
|
+
await redisClient.incrBy(balance_key, amount);
|
|
147
|
+
console.log(`Top-up ${amount} for ${tenant_id} on wallet ${wallet_type}`);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
deductCredits,
|
|
152
|
+
deductUsedCreditsFromDB,
|
|
153
|
+
loadCache,
|
|
154
|
+
refundCredits,
|
|
155
|
+
topUpCredits,
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
module.exports = { WalletThrottler };
|