aboutsummaryrefslogtreecommitdiffstats
path: root/lib/db.js
blob: 31464b194ba7eb37a6a7d8bbc24faa25c4fd4c9e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import config from "$config";
import { wait, sleep } from "$lib/utils";
import { createClient, defineScript } from "redis";
import { warn, l, err } from "$lib/logging";

let SCRIPT = `
local balanceKey = KEYS[1]
local creditKey = KEYS[2]
local amount = tonumber(ARGV[1])
local tip = tonumber(ARGV[2])
local fee = tonumber(ARGV[3])
local ourfee = tonumber(ARGV[4])

local balance = tonumber(redis.call('get', balanceKey) or '0')
local credit = tonumber(redis.call('get', creditKey) or '0')

local covered = math.min(credit, ourfee)
ourfee = ourfee - covered

if balance < amount + tip + fee + ourfee then
    return {err = 'Insufficient funds'}
end

redis.call('decrby', creditKey, tostring(math.floor(covered)))
redis.call('decrby', balanceKey, tostring(math.floor(amount + tip + fee + ourfee)))

return {ok = 'ok'}
`;

let debit = defineScript({
  NUMBER_OF_KEYS: 2,
  SCRIPT,
  transformArguments: (...args) => args.map((a) => a.toString())
});

export let db = createClient({
  url: config.db,
  scripts: { debit },
  socket: {
    reconnectStrategy: (retries) => Math.min(retries * 50, 5000)
  }
});

async function connectWithRetry() {
  try {
    await db.connect();
  } catch (err) {
    console.error("Failed to connect to Redis, retrying...", err);
    setTimeout(connectWithRetry, 5000); // Retry after 5 seconds
  }
}

connectWithRetry();

db.on("error", (e) => {
  if (e.message.startsWith("getaddr")) return;
  err("Redis error", e.message);
});

db.on("end", () => {
  warn("Redis connection ended");
});

export default db;

export let g = async (k) => {
  let v = await db.get(k);
  try {
    return JSON.parse(v);
  } catch (e) {
    return v;
  }
};
export let s = (k, v) => {
  if (k === "user:null" || k === "user:undefined") {
    warn("###### NULL USER #######");
    console.trace();
  }
  db.set(k, JSON.stringify(v));
};

let retries = {};
export let t = async (k, f) => {
  try {
    await db.watch(k);
    await f(await db.get(k), db);
  } catch (err) {
    if (!err.message.includes("watch")) throw err;

    let r = retries[k] || 0;
    retries[k] = r + 1;

    if (r < 10) {
      await sleep(100);
      await t(k, f);
    } else {
      delete retries[k];
      throw new Error("unable to obtain lock");
    }
  }

  delete retries[k];
};