From fe62cadd6fb4a7e49f7d59401fc74ae0411e32a7 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 26 Aug 2024 21:17:43 -0700 Subject: typescript --- routes/ecash.js | 127 ----------- routes/ecash.js.map | 1 + routes/ecash.ts | 127 +++++++++++ routes/email.js | 61 ------ routes/email.js.map | 1 + routes/email.ts | 61 ++++++ routes/info.js | 32 --- routes/info.js.map | 1 + routes/info.ts | 32 +++ routes/invoices.js | 38 ---- routes/invoices.js.map | 1 + routes/invoices.ts | 38 ++++ routes/items.js | 70 ------ routes/items.js.map | 1 + routes/items.ts | 70 ++++++ routes/lnurl.js | 135 ------------ routes/lnurl.js.map | 1 + routes/lnurl.ts | 135 ++++++++++++ routes/locations.js | 8 - routes/locations.js.map | 1 + routes/locations.ts | 8 + routes/nostr.js | 213 ------------------- routes/nostr.js.map | 1 + routes/nostr.ts | 213 +++++++++++++++++++ routes/payments.js | 478 ----------------------------------------- routes/payments.js.map | 1 + routes/payments.ts | 478 +++++++++++++++++++++++++++++++++++++++++ routes/rates.js | 11 - routes/rates.js.map | 1 + routes/rates.ts | 11 + routes/requests.js | 85 -------- routes/requests.js.map | 1 + routes/requests.ts | 85 ++++++++ routes/shopify.js | 41 ---- routes/shopify.js.map | 1 + routes/shopify.ts | 41 ++++ routes/users.js | 556 ------------------------------------------------ routes/users.js.map | 1 + routes/users.ts | 556 ++++++++++++++++++++++++++++++++++++++++++++++++ 39 files changed, 1868 insertions(+), 1855 deletions(-) delete mode 100644 routes/ecash.js create mode 100644 routes/ecash.js.map create mode 100644 routes/ecash.ts delete mode 100644 routes/email.js create mode 100644 routes/email.js.map create mode 100644 routes/email.ts delete mode 100644 routes/info.js create mode 100644 routes/info.js.map create mode 100644 routes/info.ts delete mode 100644 routes/invoices.js create mode 100644 routes/invoices.js.map create mode 100644 routes/invoices.ts delete mode 100644 routes/items.js create mode 100644 routes/items.js.map create mode 100644 routes/items.ts delete mode 100644 routes/lnurl.js create mode 100644 routes/lnurl.js.map create mode 100644 routes/lnurl.ts delete mode 100644 routes/locations.js create mode 100644 routes/locations.js.map create mode 100644 routes/locations.ts delete mode 100644 routes/nostr.js create mode 100644 routes/nostr.js.map create mode 100644 routes/nostr.ts delete mode 100644 routes/payments.js create mode 100644 routes/payments.js.map create mode 100644 routes/payments.ts delete mode 100644 routes/rates.js create mode 100644 routes/rates.js.map create mode 100644 routes/rates.ts delete mode 100644 routes/requests.js create mode 100644 routes/requests.js.map create mode 100644 routes/requests.ts delete mode 100644 routes/shopify.js create mode 100644 routes/shopify.js.map create mode 100644 routes/shopify.ts delete mode 100644 routes/users.js create mode 100644 routes/users.js.map create mode 100644 routes/users.ts (limited to 'routes') diff --git a/routes/ecash.js b/routes/ecash.js deleted file mode 100644 index 8c2684b1..00000000 --- a/routes/ecash.js +++ /dev/null @@ -1,127 +0,0 @@ -import { g, s, db } from "$lib/db"; -import { claim, mint, check } from "$lib/ecash"; -import { bail } from "$lib/utils"; -import { debit, credit, types } from "$lib/payments"; -import { v4 } from "uuid"; -import { l, err, warn } from "$lib/logging"; -import { emit } from "$lib/sockets"; -import store from "$lib/store"; - -let { ecash: type } = types; - -export default { - async save({ body: { token } }, res) { - try { - let id = v4(); - await s(`cash:${id}`, token); - res.send({ id }); - } catch (e) { - err(e.message); - bail(res, e.message); - } - }, - - async get({ params: { id } }, res) { - try { - let token = await g(`cash:${id}`); - let status = await check(token); - res.send({ token, status }); - } catch (e) { - err(e.message); - bail(res, e.message); - } - }, - - async claim({ body: { token }, user }, res) { - try { - let amount = await claim(token); - - let memo; - let hash = v4(); - let { currency, id: uid } = user; - let rates = await g("rates"); - await s(`invoice:${hash}`, { - currency, - id: hash, - hash, - rate: rates[currency], - uid, - received: 0, - }); - - await credit(hash, amount, memo, user.id, type); - - res.send({ ok: true }); - } catch (e) { - err(e.message); - bail(res, e.message); - } - }, - - async mint({ body: { amount }, user }, res) { - try { - let id = v4(); - let hash = v4(); - - let p = await debit({ hash, amount, user, type }); - let token = await mint(amount); - p.memo = token; - await s(`payment:${p.id}`, p); - s(`cash:${id}`, token); - - res.send({ id }); - } catch (e) { - err(e.message); - bail(res, e.message); - } - }, - - async melt({ body: { amount, bolt11: hash, preimage }, user }, res) { - try { - amount = Math.round(amount / 1000); - let ref = preimage; - let { lightning: type } = types; - if (user.username !== "mint") fail("unauthorized"); - let { id: uid, currency, username } = user; - let ourfee = await db.debit( - `balance:${uid}`, - `credit:${type}:${uid}`, - amount || 0, - 0, - 0, - 0, - ); - - let rates = await g("rates"); - let rate = rates[currency]; - - if (ourfee.err) fail(ourfee.err); - - let id = v4(); - let p = { - id, - amount: -amount, - hash, - ourfee, - uid, - confirmed: true, - rate, - currency, - type, - ref, - created: Date.now(), - }; - - await s(`payment:${hash}`, id); - await s(`payment:${id}`, p); - await db.lPush(`${uid}:payments`, id); - - l(user.username, "sent", type, amount); - emit(user.id, "payment", p); - - res.send(p); - } catch (e) { - bail(res, e.message); - } - }, -}; diff --git a/routes/ecash.js.map b/routes/ecash.js.map new file mode 100644 index 00000000..42dfc998 --- /dev/null +++ b/routes/ecash.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ecash.js","sourceRoot":"","sources":["ecash.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/routes/ecash.ts b/routes/ecash.ts new file mode 100644 index 00000000..8c2684b1 --- /dev/null +++ b/routes/ecash.ts @@ -0,0 +1,127 @@ +import { g, s, db } from "$lib/db"; +import { claim, mint, check } from "$lib/ecash"; +import { bail } from "$lib/utils"; +import { debit, credit, types } from "$lib/payments"; +import { v4 } from "uuid"; +import { l, err, warn } from "$lib/logging"; +import { emit } from "$lib/sockets"; +import store from "$lib/store"; + +let { ecash: type } = types; + +export default { + async save({ body: { token } }, res) { + try { + let id = v4(); + await s(`cash:${id}`, token); + res.send({ id }); + } catch (e) { + err(e.message); + bail(res, e.message); + } + }, + + async get({ params: { id } }, res) { + try { + let token = await g(`cash:${id}`); + let status = await check(token); + res.send({ token, status }); + } catch (e) { + err(e.message); + bail(res, e.message); + } + }, + + async claim({ body: { token }, user }, res) { + try { + let amount = await claim(token); + + let memo; + let hash = v4(); + let { currency, id: uid } = user; + let rates = await g("rates"); + await s(`invoice:${hash}`, { + currency, + id: hash, + hash, + rate: rates[currency], + uid, + received: 0, + }); + + await credit(hash, amount, memo, user.id, type); + + res.send({ ok: true }); + } catch (e) { + err(e.message); + bail(res, e.message); + } + }, + + async mint({ body: { amount }, user }, res) { + try { + let id = v4(); + let hash = v4(); + + let p = await debit({ hash, amount, user, type }); + let token = await mint(amount); + p.memo = token; + await s(`payment:${p.id}`, p); + s(`cash:${id}`, token); + + res.send({ id }); + } catch (e) { + err(e.message); + bail(res, e.message); + } + }, + + async melt({ body: { amount, bolt11: hash, preimage }, user }, res) { + try { + amount = Math.round(amount / 1000); + let ref = preimage; + let { lightning: type } = types; + if (user.username !== "mint") fail("unauthorized"); + let { id: uid, currency, username } = user; + let ourfee = await db.debit( + `balance:${uid}`, + `credit:${type}:${uid}`, + amount || 0, + 0, + 0, + 0, + ); + + let rates = await g("rates"); + let rate = rates[currency]; + + if (ourfee.err) fail(ourfee.err); + + let id = v4(); + let p = { + id, + amount: -amount, + hash, + ourfee, + uid, + confirmed: true, + rate, + currency, + type, + ref, + created: Date.now(), + }; + + await s(`payment:${hash}`, id); + await s(`payment:${id}`, p); + await db.lPush(`${uid}:payments`, id); + + l(user.username, "sent", type, amount); + emit(user.id, "payment", p); + + res.send(p); + } catch (e) { + bail(res, e.message); + } + }, +}; diff --git a/routes/email.js b/routes/email.js deleted file mode 100644 index 63c88ab0..00000000 --- a/routes/email.js +++ /dev/null @@ -1,61 +0,0 @@ -import { bail } from "$lib/utils"; -import got from "got"; -import config from "$config"; -import { l, warn } from "$lib/logging"; -import { SESClient } from "@aws-sdk/client-ses"; -import { SendEmailCommand } from "@aws-sdk/client-ses"; - -export default { - async send({ body }, res) { - try { - let { email, message, username, token: response } = body; - - let Charset = "UTF-8"; - - let { recaptcha: secret } = config; - let { success } = await got - .post("https://www.google.com/recaptcha/api/siteverify", { - form: { - secret, - response, - }, - }) - .json(); - - if (success || response === config.adminpass) { - delete body.token; - - warn("support request from", email); - let client = new SESClient({ region: "us-east-2" }); - await client.send( - new SendEmailCommand({ - Destination: { - CcAddresses: [], - ToAddresses: [config.support], - }, - Message: { - Body: { - Html: { Charset, Data: message.replace(/\n/g, "
") }, - Text: { Charset, Data: message }, - }, - Subject: { - Charset, - Data: - body.subject || - "Support Request" + (username ? " From " + username : ""), - }, - }, - ReplyToAddresses: [email], - Source: config.support, - }), - ); - - res.send({ ok: true }); - } else { - bail(res, "failed captcha"); - } - } catch (e) { - console.log(e); - } - }, -}; diff --git a/routes/email.js.map b/routes/email.js.map new file mode 100644 index 00000000..fb6f1acd --- /dev/null +++ b/routes/email.js.map @@ -0,0 +1 @@ +{"version":3,"file":"email.js","sourceRoot":"","sources":["email.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oCAAkC;AAClC,4CAAsB;AACtB,oDAA6B;AAC7B,wCAAuC;AACvC,kDAAgD;AAChD,kDAAuD;AAEvD,kBAAe;IACP,IAAI;4DAAC,EAAQ,EAAE,GAAG;;gBAAX,IAAI,UAAA;;;;;wBAEP,KAAK,GAAyC,IAAI,MAA7C,EAAE,OAAO,GAAgC,IAAI,QAApC,EAAE,QAAQ,GAAsB,IAAI,SAA1B,EAAS,QAAQ,GAAK,IAAI,MAAT,CAAU;wBAErD,OAAO,GAAG,OAAO,CAAC;wBAEL,MAAM,GAAK,iBAAM,UAAX,CAAY;wBACjB,qBAAM,aAAG;iCACxB,IAAI,CAAC,iDAAiD,EAAE;gCACvD,IAAI,EAAE;oCACJ,MAAM,QAAA;oCACN,QAAQ,UAAA;iCACT;6BACF,CAAC;iCACD,IAAI,EAAE,EAAA;;wBAPH,OAAO,GAAK,CAAA,SAOT,CAAA,QAPI;6BAST,CAAA,OAAO,IAAI,QAAQ,KAAK,iBAAM,CAAC,SAAS,CAAA,EAAxC,wBAAwC;wBAC1C,OAAO,IAAI,CAAC,KAAK,CAAC;wBAElB,IAAA,cAAI,EAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC;wBAChC,MAAM,GAAG,IAAI,sBAAS,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;wBACpD,qBAAM,MAAM,CAAC,IAAI,CACf,IAAI,6BAAgB,CAAC;gCACnB,WAAW,EAAE;oCACX,WAAW,EAAE,EAAE;oCACf,WAAW,EAAE,CAAC,iBAAM,CAAC,OAAO,CAAC;iCAC9B;gCACD,OAAO,EAAE;oCACP,IAAI,EAAE;wCACJ,IAAI,EAAE,EAAE,OAAO,SAAA,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE;wCACvD,IAAI,EAAE,EAAE,OAAO,SAAA,EAAE,IAAI,EAAE,OAAO,EAAE;qCACjC;oCACD,OAAO,EAAE;wCACP,OAAO,SAAA;wCACP,IAAI,EACF,IAAI,CAAC,OAAO;4CACZ,iBAAiB,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;qCAC5D;iCACF;gCACD,gBAAgB,EAAE,CAAC,KAAK,CAAC;gCACzB,MAAM,EAAE,iBAAM,CAAC,OAAO;6BACvB,CAAC,CACH,EAAA;;wBArBD,SAqBC,CAAC;wBAEF,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;;;wBAEvB,IAAA,YAAI,EAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC;;;;;wBAG9B,OAAO,CAAC,GAAG,CAAC,GAAC,CAAC,CAAC;;;;;;KAElB;CACF,CAAC"} \ No newline at end of file diff --git a/routes/email.ts b/routes/email.ts new file mode 100644 index 00000000..63c88ab0 --- /dev/null +++ b/routes/email.ts @@ -0,0 +1,61 @@ +import { bail } from "$lib/utils"; +import got from "got"; +import config from "$config"; +import { l, warn } from "$lib/logging"; +import { SESClient } from "@aws-sdk/client-ses"; +import { SendEmailCommand } from "@aws-sdk/client-ses"; + +export default { + async send({ body }, res) { + try { + let { email, message, username, token: response } = body; + + let Charset = "UTF-8"; + + let { recaptcha: secret } = config; + let { success } = await got + .post("https://www.google.com/recaptcha/api/siteverify", { + form: { + secret, + response, + }, + }) + .json(); + + if (success || response === config.adminpass) { + delete body.token; + + warn("support request from", email); + let client = new SESClient({ region: "us-east-2" }); + await client.send( + new SendEmailCommand({ + Destination: { + CcAddresses: [], + ToAddresses: [config.support], + }, + Message: { + Body: { + Html: { Charset, Data: message.replace(/\n/g, "
") }, + Text: { Charset, Data: message }, + }, + Subject: { + Charset, + Data: + body.subject || + "Support Request" + (username ? " From " + username : ""), + }, + }, + ReplyToAddresses: [email], + Source: config.support, + }), + ); + + res.send({ ok: true }); + } else { + bail(res, "failed captcha"); + } + } catch (e) { + console.log(e); + } + }, +}; diff --git a/routes/info.js b/routes/info.js deleted file mode 100644 index f2e262f6..00000000 --- a/routes/info.js +++ /dev/null @@ -1,32 +0,0 @@ -import { archive } from "$lib/db"; -import db from "$lib/db"; -import ln from "$lib/ln"; - -export default { - async balances(req, res) { - let lnchannel; - let lnwallet; - - let total = 0; - - for await (let k of db.scanIterator({ MATCH: "balance:*" })) { - total += parseInt(await db.get(k)); - } - - for await (let k of archive.scanIterator({ MATCH: "balance:*" })) { - total += parseInt(await archive.get(k)); - } - - const funds = await ln.listfunds(); - lnchannel = parseInt(funds.channels.reduce((a, b) => a + b.channel_sat, 0)); - lnwallet = parseInt(funds.outputs.reduce((a, b) => a + b.value, 0)); - - const info = { - lnchannel, - lnwallet, - total, - }; - - res.send(info); - }, -}; diff --git a/routes/info.js.map b/routes/info.js.map new file mode 100644 index 00000000..d230bd05 --- /dev/null +++ b/routes/info.js.map @@ -0,0 +1 @@ +{"version":3,"file":"info.js","sourceRoot":"","sources":["info.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8BAAkC;AAClC,+CAAyB;AACzB,+CAAyB;AAEzB,kBAAe;IACP,QAAQ,YAAC,GAAG,EAAE,GAAG;;;;;;;wBAIjB,KAAK,GAAG,CAAC,CAAC;;;;mCAEM,KAAA,cAAA,YAAE,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;;;;;wBAAvC,cAAuC;wBAAvC,WAAuC;wBAA5C,CAAC,KAAA,CAAA;wBACd,KAAA,KAAK,CAAA;wBAAI,KAAA,QAAQ,CAAA;wBAAC,qBAAM,YAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAA;;wBAAjC,KAAK,GAAL,KAAS,kBAAS,SAAe,EAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;mCAGjB,KAAA,cAAA,YAAO,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;;;;;wBAA5C,cAA4C;wBAA5C,WAA4C;wBAAjD,CAAC,KAAA,CAAA;wBACd,KAAA,KAAK,CAAA;wBAAI,KAAA,QAAQ,CAAA;wBAAC,qBAAM,YAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAA;;wBAAtC,KAAK,GAAL,KAAS,kBAAS,SAAoB,EAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;6BAG5B,qBAAM,YAAE,CAAC,SAAS,EAAE,EAAA;;wBAA5B,KAAK,GAAG,SAAoB;wBAClC,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAC,CAAC,EAAE,CAAC,IAAK,OAAA,CAAC,GAAG,CAAC,CAAC,WAAW,EAAjB,CAAiB,EAAE,CAAC,CAAC,CAAC,CAAC;wBAC5E,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,UAAC,CAAC,EAAE,CAAC,IAAK,OAAA,CAAC,GAAG,CAAC,CAAC,KAAK,EAAX,CAAW,EAAE,CAAC,CAAC,CAAC,CAAC;wBAE9D,IAAI,GAAG;4BACX,SAAS,WAAA;4BACT,QAAQ,UAAA;4BACR,KAAK,OAAA;yBACN,CAAC;wBAEF,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;;;;KAChB;CACF,CAAC"} \ No newline at end of file diff --git a/routes/info.ts b/routes/info.ts new file mode 100644 index 00000000..f2e262f6 --- /dev/null +++ b/routes/info.ts @@ -0,0 +1,32 @@ +import { archive } from "$lib/db"; +import db from "$lib/db"; +import ln from "$lib/ln"; + +export default { + async balances(req, res) { + let lnchannel; + let lnwallet; + + let total = 0; + + for await (let k of db.scanIterator({ MATCH: "balance:*" })) { + total += parseInt(await db.get(k)); + } + + for await (let k of archive.scanIterator({ MATCH: "balance:*" })) { + total += parseInt(await archive.get(k)); + } + + const funds = await ln.listfunds(); + lnchannel = parseInt(funds.channels.reduce((a, b) => a + b.channel_sat, 0)); + lnwallet = parseInt(funds.outputs.reduce((a, b) => a + b.value, 0)); + + const info = { + lnchannel, + lnwallet, + total, + }; + + res.send(info); + }, +}; diff --git a/routes/invoices.js b/routes/invoices.js deleted file mode 100644 index 5cc60e05..00000000 --- a/routes/invoices.js +++ /dev/null @@ -1,38 +0,0 @@ -import { g, s } from "$lib/db"; -import { generate } from "$lib/invoices"; -import { bail, fail, pick } from "$lib/utils"; -import whitelist from "$lib/whitelist"; -import got from "got"; -import config from "$config"; -import ln from "$lib/ln"; - -export default { - async get({ params: { id } }, res) { - let invoice = await g(`invoice:${id}`); - if (typeof invoice === "string") invoice = await g(`invoice:${invoice}`); - - if (invoice) { - delete invoice.secret; - invoice.user = pick(await g(`user:${invoice.uid}`), [ - "id", - "profile", - "banner", - "currency", - "username", - "pubkey", - ]); - - invoice.items ||= []; - } - if (invoice) res.send(invoice); - else res.code(500).send("invoice not found"); - }, - - async create({ body: { invoice, user }, user: sender }, res) { - try { - res.send(await generate({ invoice, user, sender })); - } catch (e) { - bail(res, e.message); - } - }, -}; diff --git a/routes/invoices.js.map b/routes/invoices.js.map new file mode 100644 index 00000000..183f297d --- /dev/null +++ b/routes/invoices.js.map @@ -0,0 +1 @@ +{"version":3,"file":"invoices.js","sourceRoot":"","sources":["invoices.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8BAA+B;AAC/B,0CAAyC;AACzC,oCAA8C;AAM9C,kBAAe;IACP,GAAG;4DAAC,EAAkB,EAAE,GAAG;;gBAAX,EAAE,eAAA;;;4BACR,qBAAM,IAAA,MAAC,EAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;;wBAAlC,OAAO,GAAG,SAAwB;6BAClC,CAAA,OAAO,OAAO,KAAK,QAAQ,CAAA,EAA3B,wBAA2B;wBAAY,qBAAM,IAAA,MAAC,EAAC,kBAAW,OAAO,CAAE,CAAC,EAAA;;wBAAvC,OAAO,GAAG,SAA6B,CAAC;;;6BAErE,OAAO,EAAP,wBAAO;wBACT,OAAO,OAAO,CAAC,MAAM,CAAC;wBACtB,KAAA,OAAO,CAAA;wBAAQ,KAAA,YAAI,CAAA;wBAAC,qBAAM,IAAA,MAAC,EAAC,eAAQ,OAAO,CAAC,GAAG,CAAE,CAAC,EAAA;;wBAAlD,GAAQ,IAAI,GAAG,kBAAK,SAA8B,EAAE;gCAClD,IAAI;gCACJ,SAAS;gCACT,QAAQ;gCACR,UAAU;gCACV,UAAU;gCACV,QAAQ;6BACT,EAAC,CAAC;wBAEH,OAAO,CAAC,KAAK,KAAb,OAAO,CAAC,KAAK,GAAK,EAAE,EAAC;;;wBAEvB,IAAI,OAAO;4BAAE,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;;4BAC1B,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;;;;;KAC9C;IAEK,MAAM;4DAAC,EAAyC,EAAE,GAAG;;gBAA5C,YAAuB,EAAf,OAAO,aAAA,EAAE,IAAI,UAAA,EAAU,MAAM,UAAA;;;;;wBAEhD,KAAA,CAAA,KAAA,GAAG,CAAA,CAAC,IAAI,CAAA;wBAAC,qBAAM,IAAA,mBAAQ,EAAC,EAAE,OAAO,SAAA,EAAE,IAAI,MAAA,EAAE,MAAM,QAAA,EAAE,CAAC,EAAA;;wBAAlD,cAAS,SAAyC,EAAC,CAAC;;;;wBAEpD,IAAA,YAAI,EAAC,GAAG,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;CACF,CAAC"} \ No newline at end of file diff --git a/routes/invoices.ts b/routes/invoices.ts new file mode 100644 index 00000000..5cc60e05 --- /dev/null +++ b/routes/invoices.ts @@ -0,0 +1,38 @@ +import { g, s } from "$lib/db"; +import { generate } from "$lib/invoices"; +import { bail, fail, pick } from "$lib/utils"; +import whitelist from "$lib/whitelist"; +import got from "got"; +import config from "$config"; +import ln from "$lib/ln"; + +export default { + async get({ params: { id } }, res) { + let invoice = await g(`invoice:${id}`); + if (typeof invoice === "string") invoice = await g(`invoice:${invoice}`); + + if (invoice) { + delete invoice.secret; + invoice.user = pick(await g(`user:${invoice.uid}`), [ + "id", + "profile", + "banner", + "currency", + "username", + "pubkey", + ]); + + invoice.items ||= []; + } + if (invoice) res.send(invoice); + else res.code(500).send("invoice not found"); + }, + + async create({ body: { invoice, user }, user: sender }, res) { + try { + res.send(await generate({ invoice, user, sender })); + } catch (e) { + bail(res, e.message); + } + }, +}; diff --git a/routes/items.js b/routes/items.js deleted file mode 100644 index 9b6314fa..00000000 --- a/routes/items.js +++ /dev/null @@ -1,70 +0,0 @@ -import { g, s, db } from "$lib/db"; -import { v4 } from "uuid"; -import { bail, fail } from "$lib/utils"; - -export default { - async list({ params: { id: uid } }, res) { - let items = []; - for (let id of await db.lRange(`${uid}:items`, 0, -1)) { - let item = await g(`item:${id}`); - if (item) items.push(item); - else await db.lRem(`${uid}:items`, 0, id); - } - - res.send(items); - }, - - async get({ params: { id } }, res) { - try { - let item = await g(`item:${id}`); - if (!item) fail("Item not found"); - res.send(item); - } catch (e) { - bail(res, e.message); - } - }, - - async create({ body: item, user: { id } }, res) { - try { - if (!item.id) { - item.id = v4(); - await db.lPush(`${id}:items`, item.id); - } - - if (!parseFloat(item.price)) fail("Invalid price"); - item.name = item.name.replace(/[^a-zA-Z0-9 ]/g, ""); - - await s(`item:${item.id}`, item); - - res.send(item); - } catch (e) { - bail(res, e.message); - } - }, - - async del({ body: { item }, user: { id } }, res) { - try { - let n = await db.lRem(`${id}:items`, 0, item.id); - if (n) db.del(`item:${item.id}`); - else fail("item not found"); - - res.send({}); - } catch (e) { - bail(res, e.message); - } - }, - - async sort({ body: { items }, user: { id } }, res) { - try { - await db.del(`${id}:items`); - - for (let item of items) { - await db.rPush(`${id}:items`, item.id); - } - - res.send({}); - } catch (e) { - bail(res, e.message); - } - }, -}; diff --git a/routes/items.js.map b/routes/items.js.map new file mode 100644 index 00000000..adebdcbc --- /dev/null +++ b/routes/items.js.map @@ -0,0 +1 @@ +{"version":3,"file":"items.js","sourceRoot":"","sources":["items.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/routes/items.ts b/routes/items.ts new file mode 100644 index 00000000..9b6314fa --- /dev/null +++ b/routes/items.ts @@ -0,0 +1,70 @@ +import { g, s, db } from "$lib/db"; +import { v4 } from "uuid"; +import { bail, fail } from "$lib/utils"; + +export default { + async list({ params: { id: uid } }, res) { + let items = []; + for (let id of await db.lRange(`${uid}:items`, 0, -1)) { + let item = await g(`item:${id}`); + if (item) items.push(item); + else await db.lRem(`${uid}:items`, 0, id); + } + + res.send(items); + }, + + async get({ params: { id } }, res) { + try { + let item = await g(`item:${id}`); + if (!item) fail("Item not found"); + res.send(item); + } catch (e) { + bail(res, e.message); + } + }, + + async create({ body: item, user: { id } }, res) { + try { + if (!item.id) { + item.id = v4(); + await db.lPush(`${id}:items`, item.id); + } + + if (!parseFloat(item.price)) fail("Invalid price"); + item.name = item.name.replace(/[^a-zA-Z0-9 ]/g, ""); + + await s(`item:${item.id}`, item); + + res.send(item); + } catch (e) { + bail(res, e.message); + } + }, + + async del({ body: { item }, user: { id } }, res) { + try { + let n = await db.lRem(`${id}:items`, 0, item.id); + if (n) db.del(`item:${item.id}`); + else fail("item not found"); + + res.send({}); + } catch (e) { + bail(res, e.message); + } + }, + + async sort({ body: { items }, user: { id } }, res) { + try { + await db.del(`${id}:items`); + + for (let item of items) { + await db.rPush(`${id}:items`, item.id); + } + + res.send({}); + } catch (e) { + bail(res, e.message); + } + }, +}; diff --git a/routes/lnurl.js b/routes/lnurl.js deleted file mode 100644 index 462127ac..00000000 --- a/routes/lnurl.js +++ /dev/null @@ -1,135 +0,0 @@ -import migrate from "$lib/migrate"; -import { g, s } from "$lib/db"; -import { l, err, warn } from "$lib/logging"; -import { bail, getUser, fail } from "$lib/utils"; -import { v4 } from "uuid"; -import got from "got"; -import { generate } from "$lib/invoices"; -import { bech32 } from "bech32"; -import { fields, pick } from "$lib/utils"; -import { types } from "$lib/payments"; -import crypto from "crypto"; -import { COINOS_PUBKEY } from "$lib/nostr"; - -import config from "$config"; -let { admin } = config; - -let { URL } = process.env; -let host = URL.split("/").at(-1); - -export default { - async encode({ query: { address } }, res) { - let [name, domain] = address.split("@"); - let url = `https://${domain}/.well-known/lnurlp/${name}`; - - try { - let r = await got(url).json(); - if (r.tag !== "payRequest") fail("not an ln address"); - } catch (e) { - let m = `failed to lookup lightning address ${address}`; - warn(m); - return bail(res, m); - } - - let enc = bech32.encode("lnurl", bech32.toWords(Buffer.from(url)), 20000); - res.send(enc); - }, - - async decode({ query: { text } }, res) { - try { - let url = Buffer.from( - bech32.fromWords(bech32.decode(text, 20000).words), - ).toString(); - - res.send(await got(url).json()); - } catch (e) { - bail(res, e.message); - } - }, - - async lnurlp( - { - params: { username }, - query: { minSendable = 1000, maxSendable = 100000000000 }, - }, - res, - ) { - try { - let { id: uid } = await getUser(username); - - let metadata = JSON.stringify([ - ["text/plain", `Paying ${username}@${host}`], - ["text/identifier", `${username}@${host}`], - ]); - - let id = v4(); - await s(`lnurl:${id}`, uid); - - res.send({ - allowsNostr: true, - minSendable, - maxSendable, - metadata, - nostrPubkey: COINOS_PUBKEY, - callback: `${URL}/api/lnurl/${id}`, - tag: "payRequest", - }); - } catch (e) { - warn("problem generating lnurlp request", username, e.message); - bail(res, e.message); - } - }, - - async lnurlw({ params: { k1, pr } }, res) {}, - - async lnurl({ params: { id }, query: { amount, nostr } }, res) { - try { - let uid = await g(`lnurl:${id}`); - let user = await getUser(uid); - if (!user) fail("user not found"); - let { username } = user; - username = username.replace(/\s/g, "").toLowerCase(); - - let memo = `Paying ${username}@${host}`; - let metadata = JSON.stringify([ - ["text/plain", memo], - ["text/identifier", `${username}@${host}`], - ]); - - if (nostr) { - try { - let event = JSON.parse(decodeURIComponent(nostr)); - // TODO: validate the event - await s(`zap:${id}`, event); - metadata = nostr; - } catch (e) { - err("problem handling zap", e.message); - } - } - - let { id: iid, text: pr } = await generate({ - invoice: { - amount: Math.round(amount / 1000), - memo: metadata, - type: types.lightning, - }, - user, - }); - - res.send({ pr, routes: [], verify: `${URL}/api/lnurl/verify/${iid}` }); - } catch (e) { - bail(res, e.message); - } - }, - - async verify({ params: { id } }, res) { - let inv = await g(`invoice:${id}`); - - if (!inv) return res.send({ status: "ERROR", reason: "Not found" }); - - let { received, amount, preimage } = inv; - let settled = received >= amount; - - res.send({ status: "OK", settled, preimage }); - }, -}; diff --git a/routes/lnurl.js.map b/routes/lnurl.js.map new file mode 100644 index 00000000..58ef53e3 --- /dev/null +++ b/routes/lnurl.js.map @@ -0,0 +1 @@ +{"version":3,"file":"lnurl.js","sourceRoot":"","sources":["lnurl.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/routes/lnurl.ts b/routes/lnurl.ts new file mode 100644 index 00000000..462127ac --- /dev/null +++ b/routes/lnurl.ts @@ -0,0 +1,135 @@ +import migrate from "$lib/migrate"; +import { g, s } from "$lib/db"; +import { l, err, warn } from "$lib/logging"; +import { bail, getUser, fail } from "$lib/utils"; +import { v4 } from "uuid"; +import got from "got"; +import { generate } from "$lib/invoices"; +import { bech32 } from "bech32"; +import { fields, pick } from "$lib/utils"; +import { types } from "$lib/payments"; +import crypto from "crypto"; +import { COINOS_PUBKEY } from "$lib/nostr"; + +import config from "$config"; +let { admin } = config; + +let { URL } = process.env; +let host = URL.split("/").at(-1); + +export default { + async encode({ query: { address } }, res) { + let [name, domain] = address.split("@"); + let url = `https://${domain}/.well-known/lnurlp/${name}`; + + try { + let r = await got(url).json(); + if (r.tag !== "payRequest") fail("not an ln address"); + } catch (e) { + let m = `failed to lookup lightning address ${address}`; + warn(m); + return bail(res, m); + } + + let enc = bech32.encode("lnurl", bech32.toWords(Buffer.from(url)), 20000); + res.send(enc); + }, + + async decode({ query: { text } }, res) { + try { + let url = Buffer.from( + bech32.fromWords(bech32.decode(text, 20000).words), + ).toString(); + + res.send(await got(url).json()); + } catch (e) { + bail(res, e.message); + } + }, + + async lnurlp( + { + params: { username }, + query: { minSendable = 1000, maxSendable = 100000000000 }, + }, + res, + ) { + try { + let { id: uid } = await getUser(username); + + let metadata = JSON.stringify([ + ["text/plain", `Paying ${username}@${host}`], + ["text/identifier", `${username}@${host}`], + ]); + + let id = v4(); + await s(`lnurl:${id}`, uid); + + res.send({ + allowsNostr: true, + minSendable, + maxSendable, + metadata, + nostrPubkey: COINOS_PUBKEY, + callback: `${URL}/api/lnurl/${id}`, + tag: "payRequest", + }); + } catch (e) { + warn("problem generating lnurlp request", username, e.message); + bail(res, e.message); + } + }, + + async lnurlw({ params: { k1, pr } }, res) {}, + + async lnurl({ params: { id }, query: { amount, nostr } }, res) { + try { + let uid = await g(`lnurl:${id}`); + let user = await getUser(uid); + if (!user) fail("user not found"); + let { username } = user; + username = username.replace(/\s/g, "").toLowerCase(); + + let memo = `Paying ${username}@${host}`; + let metadata = JSON.stringify([ + ["text/plain", memo], + ["text/identifier", `${username}@${host}`], + ]); + + if (nostr) { + try { + let event = JSON.parse(decodeURIComponent(nostr)); + // TODO: validate the event + await s(`zap:${id}`, event); + metadata = nostr; + } catch (e) { + err("problem handling zap", e.message); + } + } + + let { id: iid, text: pr } = await generate({ + invoice: { + amount: Math.round(amount / 1000), + memo: metadata, + type: types.lightning, + }, + user, + }); + + res.send({ pr, routes: [], verify: `${URL}/api/lnurl/verify/${iid}` }); + } catch (e) { + bail(res, e.message); + } + }, + + async verify({ params: { id } }, res) { + let inv = await g(`invoice:${id}`); + + if (!inv) return res.send({ status: "ERROR", reason: "Not found" }); + + let { received, amount, preimage } = inv; + let settled = received >= amount; + + res.send({ status: "OK", settled, preimage }); + }, +}; diff --git a/routes/locations.js b/routes/locations.js deleted file mode 100644 index e9a6843c..00000000 --- a/routes/locations.js +++ /dev/null @@ -1,8 +0,0 @@ -import { g } from "$lib/db"; - -export default { - async list(req, res) { - let locations = await g("locations"); - res.send({ locations }); - }, -}; diff --git a/routes/locations.js.map b/routes/locations.js.map new file mode 100644 index 00000000..d4eda525 --- /dev/null +++ b/routes/locations.js.map @@ -0,0 +1 @@ +{"version":3,"file":"locations.js","sourceRoot":"","sources":["locations.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8BAA4B;AAE5B,kBAAe;IACP,IAAI,YAAC,GAAG,EAAE,GAAG;;;;;4BACD,qBAAM,IAAA,MAAC,EAAC,WAAW,CAAC,EAAA;;wBAAhC,SAAS,GAAG,SAAoB;wBACpC,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,WAAA,EAAE,CAAC,CAAC;;;;;KACzB;CACF,CAAC"} \ No newline at end of file diff --git a/routes/locations.ts b/routes/locations.ts new file mode 100644 index 00000000..e9a6843c --- /dev/null +++ b/routes/locations.ts @@ -0,0 +1,8 @@ +import { g } from "$lib/db"; + +export default { + async list(req, res) { + let locations = await g("locations"); + res.send({ locations }); + }, +}; diff --git a/routes/nostr.js b/routes/nostr.js deleted file mode 100644 index fdfa2613..00000000 --- a/routes/nostr.js +++ /dev/null @@ -1,213 +0,0 @@ -import { bail, nada, getUser, uniq } from "$lib/utils"; -import config from "$config"; -import { g, s, db } from "$lib/db"; -import { pool, q } from "$lib/nostr"; - -export default { - async event({ params: { id } }, res) { - let event = await g(`ev:${id}`); - if (!event) return bail(res, "event not found"); - - let { pubkey } = event; - - event.user = (await g(`user:${pubkey}`)) || { - username: pubkey.substr(0, 6), - pubkey, - anon: true, - follows: [], - followers: [], - }; - - res.send(event); - }, - - async notes({ params: { pubkey } }, res) { - let uid = await g(`user:${pubkey}`); - let user = await g(`user:${uid}`); - - if (!user) - user = { - username: pubkey.substr(0, 6), - pubkey, - anon: true, - follows: [], - followers: [], - }; - - let params = { - kinds: [1], - authors: [pubkey], - }, - opts = { since: 0 }; - - q(`${pubkey}:notes`, params, opts).catch(nada); - - let ids = await db.sMembers(pubkey); - - let events = ids.length - ? (await db.mGet(ids.map((k) => "ev:" + k))).map(JSON.parse) - : []; - - res.send(events.map((e) => ({ ...e, user }))); - }, - - async messages({ params: { pubkey, since = 0 } }, res) { - let params = { - kinds: [4], - authors: [pubkey], - }; - - let opts = { since }; - - await q(`${pubkey}:messages`, params, opts).catch(nada); - - params = { - kinds: [4], - "#p": [pubkey], - }; - - await q(`${pubkey}:messages`, params, opts).catch(nada); - - let messages = await db.sMembers(`${pubkey}:messages`); - - messages = await Promise.all( - messages.map(async (id) => { - let m = await g(`ev:${id}`); - - let aid = await g(`user:${m.pubkey}`); - m.author = await g(`user:${aid}`); - - let rid = await g(`user:${m.tags[0][1]}`); - m.recipient = await g(`user:${rid}`); - - return m; - }), - ); - - res.send(messages); - }, - - async broadcast(req, res) { - let { event } = req.body; - pool.send(["EVENT", event]); - res.send(event); - }, - - async follows({ params: { pubkey }, query: { tagsonly } }, res) { - try { - let sub = `${pubkey}:follows`, - params = { - limit: 1, - kinds: [3], - authors: [pubkey], - }, - opts = { timeout: 60000, eager: 60000 }; - - q(sub, params, opts).catch(nada); - - let tags = (await g(`${pubkey}:follows`)) || []; - if (tagsonly) return res.send(tags); - - let follows = []; - for (let f of tags) { - let [_, pubkey] = f; - - q(`${pubkey}:profile:f1`, { - limit: 1, - kinds: [0], - authors: [pubkey], - }).catch(nada); - - let uid = await g(`user:${pubkey}`); - let user = await g(`user:${uid}`); - - if (!user) - user = { - username: pubkey.substr(0, 6), - pubkey, - anon: true, - }; - - follows.push(user); - } - - follows = uniq(follows, (e) => e.pubkey); - follows.sort( - (a, b) => a.username && a.username.localeCompare(b.username), - ); - - res.send(follows); - } catch (e) { - bail(res, e.message); - } - }, - - async followers({ params: { pubkey } }, res) { - try { - let pubkeys = [ - ...new Set([...(await db.sMembers(`${pubkey}:followers`))]), - ]; - - let followers = []; - - q( - `${pubkey}:followers`, - { kinds: [3], "#p": [pubkey] }, - { timeout: 60000, eager: 60000 }, - ).catch(nada); - - for (let pubkey of pubkeys) { - let uid = await g(`user:${pubkey}`); - let user = await g(`user:${uid}`); - if (!user || !user.updated) { - await q(`${pubkey}:profile:f2`, { - limit: 1, - kinds: [0], - authors: [pubkey], - }).catch(nada); - - uid = await g(`user:${pubkey}`); - user = await g(`user:${uid}`); - } - - if (!user) - user = { - username: pubkey.substr(0, 6), - pubkey, - anon: true, - }; - - followers.push(user); - } - - followers = uniq(followers, (e) => e.pubkey); - followers.sort( - (a, b) => a.username && a.username.localeCompare(b.username), - ); - - res.send(followers); - } catch (e) { - console.log(e); - res.code(500).send(e && e.message); - } - }, - - async identities({ query: { name } }, res) { - let names = {}; - if (name) { - names = { [name]: (await getUser(name)).pubkey }; - } else { - let records = await db.sMembers("nip5"); - for (let s of records) { - let [name, pubkey] = s.split(":"); - names[name] = pubkey; - } - } - - res.send({ names }); - }, - - async info(req, res) { - res.send({ pubkey: COINOS_PUBKEY }); - } -}; diff --git a/routes/nostr.js.map b/routes/nostr.js.map new file mode 100644 index 00000000..19e4d7d2 --- /dev/null +++ b/routes/nostr.js.map @@ -0,0 +1 @@ +{"version":3,"file":"nostr.js","sourceRoot":"","sources":["nostr.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/routes/nostr.ts b/routes/nostr.ts new file mode 100644 index 00000000..fdfa2613 --- /dev/null +++ b/routes/nostr.ts @@ -0,0 +1,213 @@ +import { bail, nada, getUser, uniq } from "$lib/utils"; +import config from "$config"; +import { g, s, db } from "$lib/db"; +import { pool, q } from "$lib/nostr"; + +export default { + async event({ params: { id } }, res) { + let event = await g(`ev:${id}`); + if (!event) return bail(res, "event not found"); + + let { pubkey } = event; + + event.user = (await g(`user:${pubkey}`)) || { + username: pubkey.substr(0, 6), + pubkey, + anon: true, + follows: [], + followers: [], + }; + + res.send(event); + }, + + async notes({ params: { pubkey } }, res) { + let uid = await g(`user:${pubkey}`); + let user = await g(`user:${uid}`); + + if (!user) + user = { + username: pubkey.substr(0, 6), + pubkey, + anon: true, + follows: [], + followers: [], + }; + + let params = { + kinds: [1], + authors: [pubkey], + }, + opts = { since: 0 }; + + q(`${pubkey}:notes`, params, opts).catch(nada); + + let ids = await db.sMembers(pubkey); + + let events = ids.length + ? (await db.mGet(ids.map((k) => "ev:" + k))).map(JSON.parse) + : []; + + res.send(events.map((e) => ({ ...e, user }))); + }, + + async messages({ params: { pubkey, since = 0 } }, res) { + let params = { + kinds: [4], + authors: [pubkey], + }; + + let opts = { since }; + + await q(`${pubkey}:messages`, params, opts).catch(nada); + + params = { + kinds: [4], + "#p": [pubkey], + }; + + await q(`${pubkey}:messages`, params, opts).catch(nada); + + let messages = await db.sMembers(`${pubkey}:messages`); + + messages = await Promise.all( + messages.map(async (id) => { + let m = await g(`ev:${id}`); + + let aid = await g(`user:${m.pubkey}`); + m.author = await g(`user:${aid}`); + + let rid = await g(`user:${m.tags[0][1]}`); + m.recipient = await g(`user:${rid}`); + + return m; + }), + ); + + res.send(messages); + }, + + async broadcast(req, res) { + let { event } = req.body; + pool.send(["EVENT", event]); + res.send(event); + }, + + async follows({ params: { pubkey }, query: { tagsonly } }, res) { + try { + let sub = `${pubkey}:follows`, + params = { + limit: 1, + kinds: [3], + authors: [pubkey], + }, + opts = { timeout: 60000, eager: 60000 }; + + q(sub, params, opts).catch(nada); + + let tags = (await g(`${pubkey}:follows`)) || []; + if (tagsonly) return res.send(tags); + + let follows = []; + for (let f of tags) { + let [_, pubkey] = f; + + q(`${pubkey}:profile:f1`, { + limit: 1, + kinds: [0], + authors: [pubkey], + }).catch(nada); + + let uid = await g(`user:${pubkey}`); + let user = await g(`user:${uid}`); + + if (!user) + user = { + username: pubkey.substr(0, 6), + pubkey, + anon: true, + }; + + follows.push(user); + } + + follows = uniq(follows, (e) => e.pubkey); + follows.sort( + (a, b) => a.username && a.username.localeCompare(b.username), + ); + + res.send(follows); + } catch (e) { + bail(res, e.message); + } + }, + + async followers({ params: { pubkey } }, res) { + try { + let pubkeys = [ + ...new Set([...(await db.sMembers(`${pubkey}:followers`))]), + ]; + + let followers = []; + + q( + `${pubkey}:followers`, + { kinds: [3], "#p": [pubkey] }, + { timeout: 60000, eager: 60000 }, + ).catch(nada); + + for (let pubkey of pubkeys) { + let uid = await g(`user:${pubkey}`); + let user = await g(`user:${uid}`); + if (!user || !user.updated) { + await q(`${pubkey}:profile:f2`, { + limit: 1, + kinds: [0], + authors: [pubkey], + }).catch(nada); + + uid = await g(`user:${pubkey}`); + user = await g(`user:${uid}`); + } + + if (!user) + user = { + username: pubkey.substr(0, 6), + pubkey, + anon: true, + }; + + followers.push(user); + } + + followers = uniq(followers, (e) => e.pubkey); + followers.sort( + (a, b) => a.username && a.username.localeCompare(b.username), + ); + + res.send(followers); + } catch (e) { + console.log(e); + res.code(500).send(e && e.message); + } + }, + + async identities({ query: { name } }, res) { + let names = {}; + if (name) { + names = { [name]: (await getUser(name)).pubkey }; + } else { + let records = await db.sMembers("nip5"); + for (let s of records) { + let [name, pubkey] = s.split(":"); + names[name] = pubkey; + } + } + + res.send({ names }); + }, + + async info(req, res) { + res.send({ pubkey: COINOS_PUBKEY }); + } +}; diff --git a/routes/payments.js b/routes/payments.js deleted file mode 100644 index 1ae87916..00000000 --- a/routes/payments.js +++ /dev/null @@ -1,478 +0,0 @@ -import config from "$config"; -import { emit } from "$lib/sockets"; -import { generate } from "$lib/invoices"; -import { v4 } from "uuid"; -import { db, g, s, t } from "$lib/db"; -import { l, err, warn } from "$lib/logging"; -import { bail, fail, getInvoice, btc, sats, SATS, getUser } from "$lib/utils"; -import { requirePin } from "$lib/auth"; -import { - completePayment, - decode, - debit, - credit, - types, - getNode, - build, - sendLightning, - sendOnchain, -} from "$lib/payments"; -import { mqtt1, mqtt2 } from "$lib/mqtt"; -import got from "got"; -import api from "$lib/api"; - -import lq from "$lib/liquid"; -import bc from "$lib/bitcoin"; -import ln from "$lib/ln"; - -export default { - async info({ body, user }, res) { - res.send(await ln.getinfo()); - }, - - async create({ body, user }, res) { - let { amount, hash, maxfee, fund, memo, payreq, rate, username } = body; - let balance = await g(`balance:${user.id}`); - - try { - if (await g("freeze")) fail("Problem sending payment"); - - if (typeof amount !== "undefined") { - amount = parseInt(amount); - if (amount < 0 || amount > SATS || isNaN(amount)) - fail("Invalid amount"); - } - - await requirePin({ body, user }); - - let p; - - if (payreq) { - p = await sendLightning({ user, pr: payreq, amount, maxfee, memo }); - } - - if (!p) { - if (hash) { - p = await debit({ hash, amount, memo, user }); - await credit(hash, amount, memo, user.id); - } else if (fund) { - p = await debit({ hash, amount, memo: fund, user, type: types.fund }); - await db.incrBy(`fund:${fund}`, amount); - await db.lPush(`fund:${fund}:payments`, p.id); - l("funded fund", fund); - } - } - - res.send(p); - } catch (e) { - warn(user.username, "payment failed", amount, balance, hash, payreq); - err(e.message); - bail(res, e.message); - } - }, - - async list({ user: { id }, query: { start, end, limit, offset } }, res) { - if (limit) limit = parseInt(limit); - offset = parseInt(offset) || 0; - - let payments = (await db.lRange(`${id}:payments`, 0, -1)) || []; - payments = ( - await Promise.all( - payments.map(async (id) => { - let p = await g(`payment:${id}`); - if (!p) { - warn("missing payment", id); - return p; - } - if (p.created < start || p.created > end) return; - if (p.type === types.internal) p.with = await g(`user:${p.ref}`); - return p; - }), - ) - ) - .filter((p) => p) - .sort((a, b) => b.created - a.created); - - let count = payments.length; - - let fn = (a, b) => ({ - ...a, - [b.currency]: { - tips: (a[b.currency] ? a[b.currency].tips : 0) + (b.tip || 0), - fiatTips: ( - parseFloat(a[b.currency] ? a[b.currency].fiatTips : 0) + - ((b.tip || 0) * b.rate) / SATS - ).toFixed(2), - sats: - (a[b.currency] ? a[b.currency].sats : 0) + - (b.amount || 0) + - (b.tip || 0) - - (b.fee || 0) - - (b.ourfee || 0), - fiat: ( - parseFloat(a[b.currency] ? a[b.currency].fiat : 0) + - (((b.amount || 0) + - ((b.amount > 0 ? b.tip : -b.tip) || 0) - - (b.fee || 0) - - (b.ourfee || 0)) * - b.rate) / - SATS - ).toFixed(2), - }, - }); - - let incoming = payments.filter((p) => p.amount > 0).reduce(fn, {}); - let outgoing = payments.filter((p) => p.amount < 0).reduce(fn, {}); - - if (limit) payments = payments.slice(offset, offset + limit); - - res.send({ payments, count, incoming, outgoing }); - }, - - async get({ params: { hash } }, res) { - let p = await g(`payment:${hash}`); - if (typeof p === "string") p = await g(`payment:${p}`); - if (p.type === types.internal) p.with = await g(`user:${p.ref}`); - res.send(p); - }, - - async parse({ body: { payreq }, user }, res) { - try { - let hour = 1000 * 60 * 60; - let nodes = await g("nodes"); - let { last } = nodes || {}; - - if (!last || last > Date.now() - hour) { - ({ nodes } = await ln.listnodes()); - nodes.last = Date.now(); - await s("nodes", nodes); - } - - let twoWeeksAgo = new Date(new Date().setDate(new Date().getDate() - 14)); - let decoded = await ln.decodepay(payreq); - let { amount_msat, payee } = decoded; - let node = nodes.find((n) => n.nodeid === payee); - let alias = node ? node.alias : payee.substr(0, 12); - - let amount = Math.round(amount_msat / 1000); - let ourfee = Math.round(amount * config.fee); - let credit = await g(`credit:lightning:${user.id}`); - let covered = Math.min(credit, ourfee) || 0; - ourfee -= covered; - - res.send({ alias, amount, ourfee }); - } catch (e) { - bail(res, e.message); - } - }, - - async fund({ params: { name } }, res) { - let amount = await g(`fund:${name}`); - if (typeof amount === "undefined" || amount === null) - return bail(res, "fund not found"); - let payments = (await db.lRange(`fund:${name}:payments`, 0, -1)) || []; - payments = await Promise.all(payments.map((hash) => g(`payment:${hash}`))); - - await Promise.all( - payments.map(async (p) => (p.user = await g(`user:${p.uid}`))), - ); - - payments = payments.filter((p) => p); - res.send({ amount, payments }); - }, - - async withdraw({ params: { name } }, res) { - let maxWithdrawable = await g(`fund:${name}`); - res.send({ - tag: "withdrawRequest", - callback: `${URL}/lnurlw/${name}`, - k1: name, - defaultDescription: `Withdraw from coinos fund ${name}`, - minWithdrawable: 0, - maxWithdrawable, - }); - }, - - async take({ body: { id, amount, invoice: iid }, user }, res) { - try { - amount = parseInt(amount); - if (amount < 0) fail("Invalid amount"); - - await t(`fund:${id}`, async (balance, db) => { - if (balance < amount) fail("Insufficient funds"); - await db.multi().decrBy(`fund:${id}`, amount).exec(); - }); - - if (!iid) { - iid = v4(); - let rates = await g("rates"); - let { currency } = user; - await s(`invoice:${iid}`, { - currency, - id: iid, - hash: iid, - rate: rates[currency], - uid: user.id, - received: 0, - }); - } - - let payment = await credit(iid, amount, id, id, types.fund); - await db.lPush(`fund:${id}:payments`, payment.id); - - res.send({ payment }); - } catch (e) { - warn("problem withdrawing from fund", e.message); - bail(res, e.message); - } - }, - - async confirm({ body: { txid, wallet, type } }, res) { - try { - let node = getNode(type); - - if (wallet === config.bitcoin.wallet || wallet === config.liquid.wallet) { - let { confirmations, details } = await node.getTransaction(txid); - for (let { address, amount, asset, category, vout } of details) { - if (!address) continue; - if (type === types.liquid && asset !== config.liquid.btc) continue; - if (category === "send") { - let p = await g(`payment:${txid}`); - if (typeof p === "string") p = await g(`payment:${p}`); - if (!p) continue; - - if (confirmations >= 1) p.confirmed = true; - await s(`payment:${p.id}`, p); - emit(p.uid, "payment", p); - continue; - } - - let p = await g(`payment:${txid}:${vout}`); - if (typeof p === "string") p = await g(`payment:${p}`); - - if (!p) { - await credit(address, sats(amount), "", `${txid}:${vout}`, type); - } else if (confirmations >= 1) { - let id = `payment:${txid}:${vout}`; - let p = await g(id); - if (typeof p === "string") p = await g(`payment:${p}`); - if (!p) return db.sAdd("missed", id); - if (p.confirmed) return; - - let invoice = await getInvoice(address); - let { id: iid } = invoice; - - p.confirmed = true; - invoice.received += parseInt(invoice.pending); - invoice.pending = 0; - - l("confirming", id, p.id, p.amount); - - let r = await db - .multi() - .set(`invoice:${iid}`, JSON.stringify(invoice)) - .set(`payment:${p.id}`, JSON.stringify(p)) - .decrBy(`pending:${p.uid}`, p.amount) - .incrBy(`balance:${p.uid}`, p.amount) - .exec(); - - emit(p.uid, "payment", p); - let user = await g(`user:${p.uid}`); - await completePayment(p, user); - } - } - } - res.send({}); - } catch (e) { - console.log(e); - warn(`problem processing ${txid}`); - bail(res, e.message); - } - }, - - async fee({ body, user }, res) { - try { - res.send(await build({ ...body, user })); - } catch (e) { - warn( - "problem estimating fee", - e.message, - user.username, - body.amount, - body.address, - ); - let msg = e.message; - if (msg.includes("500")) msg = ""; - bail(res, "Failed to prepare transaction " + msg); - } - }, - - async send({ body, user }, res) { - try { - await requirePin({ body, user }); - let { hash: txid } = await sendOnchain({ ...body, user }); - let pid = await g(`payment:${txid}`); - let p = await g(`payment:${pid}`); - - res.send({ txid }); - } catch (e) { - warn(user.username, "payment failed", e.message); - res.code(500).send(e.message); - } - }, - - async freeze({ body: { secret } }, res) { - try { - if (secret !== config.adminpass) fail("unauthorized"); - await s("freeze", true); - res.send("ok"); - } catch (e) { - console.log(e); - bail(res, e.message); - } - }, - - async print({ body: { id }, user }, res) { - try { - let p = await g(`payment:${id}`); - if (p.uid !== user.id) fail("unauthorized"); - emit(user.id, "payment", p); - - let { username } = user; - - mqtt1.publish( - username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, - ); - - mqtt2.publish( - username, - `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, - ); - - res.send({ ok: true }); - } catch (e) { - bail(res, e.message); - } - }, - - async lnaddress( - { params: { lnaddress, amount, maxfee = 5000 }, body, user }, - res, - ) { - try { - lnaddress = decodeURIComponent(lnaddress); - await requirePin({ body, user }); - - let [username, domain] = lnaddress.split("@"); - let { minSendable, maxSendable, callback, metadata } = await got( - `https://${domain}/.well-known/lnurlp/${username}`, - ).json(); - - let memo = metadata["text/plain"] || ""; - if (amount * 1000 < minSendable || amount * 1000 > maxSendable) - fail("amount out of range"); - - let r = await got(`${callback}?amount=${amount * 1000}`).json(); - if (r.reason) fail(r.rason); - let { pr } = r; - let p = await sendLightning({ user, pr, amount, maxfee, memo }); - - if (!p) { - p = await debit({ hash: pr, amount, memo, user }); - await credit(pr, amount, memo, user.id); - } - - res.send(p); - } catch (e) { - console.log(e); - bail(res, e.message); - } - }, - - async gateway({ body: { short_channel_id, webhook } }, res) { - await s(short_channel_id, webhook); - res.send({ ok: true }); - }, - - async replace({ body: { id }, user }, res) { - try { - let { id: uid } = user; - let p = await g(`payment:${id}`); - if (!p) fail("Payment not found"); - if (p.uid !== user.id) fail("unauthorized"); - - let { tx, type } = await decode(p.hex); - let node = getNode(type); - - let fees = await fetch(`${api[type]}/fees/recommended`).then((r) => - r.json(), - ); - - let outputs = []; - for (let { - scriptPubKey: { address }, - value, - } of tx.vout) { - if (address && !(await node.getAddressInfo(address)).ismine) - outputs.push({ [address]: value }); - } - - let raw = await node.createRawTransaction(tx.vin, outputs); - - let newTx = await node.fundRawTransaction(raw, { - fee_rate: fees.fastestFee + 50, - replaceable: true, - subtractFeeFromOutputs: [], - }); - - let diff = sats(newTx.fee) - p.fee; - if (diff < 0) fail("fee must increase"); - - // let ourfee = Math.round(diff * config.fee) || 0; - // - // ourfee = await db.debit( - // `balance:${uid}`, - // `credit:${type}:${uid}`, - // p.amount || 0, - // 0, - // diff, - // ourfee, - // ); - - if (config[type].walletpass) - await node.walletPassphrase(config[type].walletpass, 300); - p.hex = (await node.signRawTransactionWithWallet(newTx.hex)).hex; - let r = await node.testMempoolAccept([p.hex]); - if (!r[0].allowed) fail(`transaction rejected ${p.hex}`); - warn("bump", user.username, p.hex); - // p.hash = await node.sendRawTransaction(p.hex); - // p.fee = sats(newTx.fee); - // await s(`payment:${id}`, p); - // await s(`payment:${p.hash}`, id); - // emit(uid, "payment", p); - - res.send({ ok: true }); - } catch (e) { - err("failed to bump payment", id, e.message); - bail(res, e.message); - } - }, - - async internal({ body: { username, amount }, user: sender }, res) { - let recipient = await getUser(username); - let inv = await generate({ - invoice: { amount, type: "lightning" }, - user: recipient, - sender, - }); - - let { hash } = inv; - let memo; - - let p = await debit({ hash, amount, memo, user: sender }); - await credit(hash, amount, memo, recipient.id); - - res.send(p); - }, -}; diff --git a/routes/payments.js.map b/routes/payments.js.map new file mode 100644 index 00000000..67fa0d05 --- /dev/null +++ b/routes/payments.js.map @@ -0,0 +1 @@ +{"version":3,"file":"payments.js","sourceRoot":"","sources":["payments.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/routes/payments.ts b/routes/payments.ts new file mode 100644 index 00000000..1ae87916 --- /dev/null +++ b/routes/payments.ts @@ -0,0 +1,478 @@ +import config from "$config"; +import { emit } from "$lib/sockets"; +import { generate } from "$lib/invoices"; +import { v4 } from "uuid"; +import { db, g, s, t } from "$lib/db"; +import { l, err, warn } from "$lib/logging"; +import { bail, fail, getInvoice, btc, sats, SATS, getUser } from "$lib/utils"; +import { requirePin } from "$lib/auth"; +import { + completePayment, + decode, + debit, + credit, + types, + getNode, + build, + sendLightning, + sendOnchain, +} from "$lib/payments"; +import { mqtt1, mqtt2 } from "$lib/mqtt"; +import got from "got"; +import api from "$lib/api"; + +import lq from "$lib/liquid"; +import bc from "$lib/bitcoin"; +import ln from "$lib/ln"; + +export default { + async info({ body, user }, res) { + res.send(await ln.getinfo()); + }, + + async create({ body, user }, res) { + let { amount, hash, maxfee, fund, memo, payreq, rate, username } = body; + let balance = await g(`balance:${user.id}`); + + try { + if (await g("freeze")) fail("Problem sending payment"); + + if (typeof amount !== "undefined") { + amount = parseInt(amount); + if (amount < 0 || amount > SATS || isNaN(amount)) + fail("Invalid amount"); + } + + await requirePin({ body, user }); + + let p; + + if (payreq) { + p = await sendLightning({ user, pr: payreq, amount, maxfee, memo }); + } + + if (!p) { + if (hash) { + p = await debit({ hash, amount, memo, user }); + await credit(hash, amount, memo, user.id); + } else if (fund) { + p = await debit({ hash, amount, memo: fund, user, type: types.fund }); + await db.incrBy(`fund:${fund}`, amount); + await db.lPush(`fund:${fund}:payments`, p.id); + l("funded fund", fund); + } + } + + res.send(p); + } catch (e) { + warn(user.username, "payment failed", amount, balance, hash, payreq); + err(e.message); + bail(res, e.message); + } + }, + + async list({ user: { id }, query: { start, end, limit, offset } }, res) { + if (limit) limit = parseInt(limit); + offset = parseInt(offset) || 0; + + let payments = (await db.lRange(`${id}:payments`, 0, -1)) || []; + payments = ( + await Promise.all( + payments.map(async (id) => { + let p = await g(`payment:${id}`); + if (!p) { + warn("missing payment", id); + return p; + } + if (p.created < start || p.created > end) return; + if (p.type === types.internal) p.with = await g(`user:${p.ref}`); + return p; + }), + ) + ) + .filter((p) => p) + .sort((a, b) => b.created - a.created); + + let count = payments.length; + + let fn = (a, b) => ({ + ...a, + [b.currency]: { + tips: (a[b.currency] ? a[b.currency].tips : 0) + (b.tip || 0), + fiatTips: ( + parseFloat(a[b.currency] ? a[b.currency].fiatTips : 0) + + ((b.tip || 0) * b.rate) / SATS + ).toFixed(2), + sats: + (a[b.currency] ? a[b.currency].sats : 0) + + (b.amount || 0) + + (b.tip || 0) - + (b.fee || 0) - + (b.ourfee || 0), + fiat: ( + parseFloat(a[b.currency] ? a[b.currency].fiat : 0) + + (((b.amount || 0) + + ((b.amount > 0 ? b.tip : -b.tip) || 0) - + (b.fee || 0) - + (b.ourfee || 0)) * + b.rate) / + SATS + ).toFixed(2), + }, + }); + + let incoming = payments.filter((p) => p.amount > 0).reduce(fn, {}); + let outgoing = payments.filter((p) => p.amount < 0).reduce(fn, {}); + + if (limit) payments = payments.slice(offset, offset + limit); + + res.send({ payments, count, incoming, outgoing }); + }, + + async get({ params: { hash } }, res) { + let p = await g(`payment:${hash}`); + if (typeof p === "string") p = await g(`payment:${p}`); + if (p.type === types.internal) p.with = await g(`user:${p.ref}`); + res.send(p); + }, + + async parse({ body: { payreq }, user }, res) { + try { + let hour = 1000 * 60 * 60; + let nodes = await g("nodes"); + let { last } = nodes || {}; + + if (!last || last > Date.now() - hour) { + ({ nodes } = await ln.listnodes()); + nodes.last = Date.now(); + await s("nodes", nodes); + } + + let twoWeeksAgo = new Date(new Date().setDate(new Date().getDate() - 14)); + let decoded = await ln.decodepay(payreq); + let { amount_msat, payee } = decoded; + let node = nodes.find((n) => n.nodeid === payee); + let alias = node ? node.alias : payee.substr(0, 12); + + let amount = Math.round(amount_msat / 1000); + let ourfee = Math.round(amount * config.fee); + let credit = await g(`credit:lightning:${user.id}`); + let covered = Math.min(credit, ourfee) || 0; + ourfee -= covered; + + res.send({ alias, amount, ourfee }); + } catch (e) { + bail(res, e.message); + } + }, + + async fund({ params: { name } }, res) { + let amount = await g(`fund:${name}`); + if (typeof amount === "undefined" || amount === null) + return bail(res, "fund not found"); + let payments = (await db.lRange(`fund:${name}:payments`, 0, -1)) || []; + payments = await Promise.all(payments.map((hash) => g(`payment:${hash}`))); + + await Promise.all( + payments.map(async (p) => (p.user = await g(`user:${p.uid}`))), + ); + + payments = payments.filter((p) => p); + res.send({ amount, payments }); + }, + + async withdraw({ params: { name } }, res) { + let maxWithdrawable = await g(`fund:${name}`); + res.send({ + tag: "withdrawRequest", + callback: `${URL}/lnurlw/${name}`, + k1: name, + defaultDescription: `Withdraw from coinos fund ${name}`, + minWithdrawable: 0, + maxWithdrawable, + }); + }, + + async take({ body: { id, amount, invoice: iid }, user }, res) { + try { + amount = parseInt(amount); + if (amount < 0) fail("Invalid amount"); + + await t(`fund:${id}`, async (balance, db) => { + if (balance < amount) fail("Insufficient funds"); + await db.multi().decrBy(`fund:${id}`, amount).exec(); + }); + + if (!iid) { + iid = v4(); + let rates = await g("rates"); + let { currency } = user; + await s(`invoice:${iid}`, { + currency, + id: iid, + hash: iid, + rate: rates[currency], + uid: user.id, + received: 0, + }); + } + + let payment = await credit(iid, amount, id, id, types.fund); + await db.lPush(`fund:${id}:payments`, payment.id); + + res.send({ payment }); + } catch (e) { + warn("problem withdrawing from fund", e.message); + bail(res, e.message); + } + }, + + async confirm({ body: { txid, wallet, type } }, res) { + try { + let node = getNode(type); + + if (wallet === config.bitcoin.wallet || wallet === config.liquid.wallet) { + let { confirmations, details } = await node.getTransaction(txid); + for (let { address, amount, asset, category, vout } of details) { + if (!address) continue; + if (type === types.liquid && asset !== config.liquid.btc) continue; + if (category === "send") { + let p = await g(`payment:${txid}`); + if (typeof p === "string") p = await g(`payment:${p}`); + if (!p) continue; + + if (confirmations >= 1) p.confirmed = true; + await s(`payment:${p.id}`, p); + emit(p.uid, "payment", p); + continue; + } + + let p = await g(`payment:${txid}:${vout}`); + if (typeof p === "string") p = await g(`payment:${p}`); + + if (!p) { + await credit(address, sats(amount), "", `${txid}:${vout}`, type); + } else if (confirmations >= 1) { + let id = `payment:${txid}:${vout}`; + let p = await g(id); + if (typeof p === "string") p = await g(`payment:${p}`); + if (!p) return db.sAdd("missed", id); + if (p.confirmed) return; + + let invoice = await getInvoice(address); + let { id: iid } = invoice; + + p.confirmed = true; + invoice.received += parseInt(invoice.pending); + invoice.pending = 0; + + l("confirming", id, p.id, p.amount); + + let r = await db + .multi() + .set(`invoice:${iid}`, JSON.stringify(invoice)) + .set(`payment:${p.id}`, JSON.stringify(p)) + .decrBy(`pending:${p.uid}`, p.amount) + .incrBy(`balance:${p.uid}`, p.amount) + .exec(); + + emit(p.uid, "payment", p); + let user = await g(`user:${p.uid}`); + await completePayment(p, user); + } + } + } + res.send({}); + } catch (e) { + console.log(e); + warn(`problem processing ${txid}`); + bail(res, e.message); + } + }, + + async fee({ body, user }, res) { + try { + res.send(await build({ ...body, user })); + } catch (e) { + warn( + "problem estimating fee", + e.message, + user.username, + body.amount, + body.address, + ); + let msg = e.message; + if (msg.includes("500")) msg = ""; + bail(res, "Failed to prepare transaction " + msg); + } + }, + + async send({ body, user }, res) { + try { + await requirePin({ body, user }); + let { hash: txid } = await sendOnchain({ ...body, user }); + let pid = await g(`payment:${txid}`); + let p = await g(`payment:${pid}`); + + res.send({ txid }); + } catch (e) { + warn(user.username, "payment failed", e.message); + res.code(500).send(e.message); + } + }, + + async freeze({ body: { secret } }, res) { + try { + if (secret !== config.adminpass) fail("unauthorized"); + await s("freeze", true); + res.send("ok"); + } catch (e) { + console.log(e); + bail(res, e.message); + } + }, + + async print({ body: { id }, user }, res) { + try { + let p = await g(`payment:${id}`); + if (p.uid !== user.id) fail("unauthorized"); + emit(user.id, "payment", p); + + let { username } = user; + + mqtt1.publish( + username, + `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, + ); + + mqtt2.publish( + username, + `pay:${p.amount}:${p.tip}:${p.rate}:${p.created}:${p.id}`, + ); + + res.send({ ok: true }); + } catch (e) { + bail(res, e.message); + } + }, + + async lnaddress( + { params: { lnaddress, amount, maxfee = 5000 }, body, user }, + res, + ) { + try { + lnaddress = decodeURIComponent(lnaddress); + await requirePin({ body, user }); + + let [username, domain] = lnaddress.split("@"); + let { minSendable, maxSendable, callback, metadata } = await got( + `https://${domain}/.well-known/lnurlp/${username}`, + ).json(); + + let memo = metadata["text/plain"] || ""; + if (amount * 1000 < minSendable || amount * 1000 > maxSendable) + fail("amount out of range"); + + let r = await got(`${callback}?amount=${amount * 1000}`).json(); + if (r.reason) fail(r.rason); + let { pr } = r; + let p = await sendLightning({ user, pr, amount, maxfee, memo }); + + if (!p) { + p = await debit({ hash: pr, amount, memo, user }); + await credit(pr, amount, memo, user.id); + } + + res.send(p); + } catch (e) { + console.log(e); + bail(res, e.message); + } + }, + + async gateway({ body: { short_channel_id, webhook } }, res) { + await s(short_channel_id, webhook); + res.send({ ok: true }); + }, + + async replace({ body: { id }, user }, res) { + try { + let { id: uid } = user; + let p = await g(`payment:${id}`); + if (!p) fail("Payment not found"); + if (p.uid !== user.id) fail("unauthorized"); + + let { tx, type } = await decode(p.hex); + let node = getNode(type); + + let fees = await fetch(`${api[type]}/fees/recommended`).then((r) => + r.json(), + ); + + let outputs = []; + for (let { + scriptPubKey: { address }, + value, + } of tx.vout) { + if (address && !(await node.getAddressInfo(address)).ismine) + outputs.push({ [address]: value }); + } + + let raw = await node.createRawTransaction(tx.vin, outputs); + + let newTx = await node.fundRawTransaction(raw, { + fee_rate: fees.fastestFee + 50, + replaceable: true, + subtractFeeFromOutputs: [], + }); + + let diff = sats(newTx.fee) - p.fee; + if (diff < 0) fail("fee must increase"); + + // let ourfee = Math.round(diff * config.fee) || 0; + // + // ourfee = await db.debit( + // `balance:${uid}`, + // `credit:${type}:${uid}`, + // p.amount || 0, + // 0, + // diff, + // ourfee, + // ); + + if (config[type].walletpass) + await node.walletPassphrase(config[type].walletpass, 300); + p.hex = (await node.signRawTransactionWithWallet(newTx.hex)).hex; + let r = await node.testMempoolAccept([p.hex]); + if (!r[0].allowed) fail(`transaction rejected ${p.hex}`); + warn("bump", user.username, p.hex); + // p.hash = await node.sendRawTransaction(p.hex); + // p.fee = sats(newTx.fee); + // await s(`payment:${id}`, p); + // await s(`payment:${p.hash}`, id); + // emit(uid, "payment", p); + + res.send({ ok: true }); + } catch (e) { + err("failed to bump payment", id, e.message); + bail(res, e.message); + } + }, + + async internal({ body: { username, amount }, user: sender }, res) { + let recipient = await getUser(username); + let inv = await generate({ + invoice: { amount, type: "lightning" }, + user: recipient, + sender, + }); + + let { hash } = inv; + let memo; + + let p = await debit({ hash, amount, memo, user: sender }); + await credit(hash, amount, memo, recipient.id); + + res.send(p); + }, +}; diff --git a/routes/rates.js b/routes/rates.js deleted file mode 100644 index 828e5b2c..00000000 --- a/routes/rates.js +++ /dev/null @@ -1,11 +0,0 @@ -import { g } from "$lib/db"; - -export default { - async last(req, res) { - res.send(await g('rate')); - }, - - async index(req, res) { - res.send(await g('rates')); - }, -}; diff --git a/routes/rates.js.map b/routes/rates.js.map new file mode 100644 index 00000000..1df70b10 --- /dev/null +++ b/routes/rates.js.map @@ -0,0 +1 @@ +{"version":3,"file":"rates.js","sourceRoot":"","sources":["rates.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8BAA4B;AAE5B,kBAAe;IACP,IAAI,YAAC,GAAG,EAAE,GAAG;;;;;;wBACjB,KAAA,CAAA,KAAA,GAAG,CAAA,CAAC,IAAI,CAAA;wBAAC,qBAAM,IAAA,MAAC,EAAC,MAAM,CAAC,EAAA;;wBAAxB,cAAS,SAAe,EAAC,CAAC;;;;;KAC3B;IAEK,KAAK,YAAC,GAAG,EAAE,GAAG;;;;;;wBAClB,KAAA,CAAA,KAAA,GAAG,CAAA,CAAC,IAAI,CAAA;wBAAC,qBAAM,IAAA,MAAC,EAAC,OAAO,CAAC,EAAA;;wBAAzB,cAAS,SAAgB,EAAC,CAAC;;;;;KAC5B;CACF,CAAC"} \ No newline at end of file diff --git a/routes/rates.ts b/routes/rates.ts new file mode 100644 index 00000000..828e5b2c --- /dev/null +++ b/routes/rates.ts @@ -0,0 +1,11 @@ +import { g } from "$lib/db"; + +export default { + async last(req, res) { + res.send(await g('rate')); + }, + + async index(req, res) { + res.send(await g('rates')); + }, +}; diff --git a/routes/requests.js b/routes/requests.js deleted file mode 100644 index a0d55739..00000000 --- a/routes/requests.js +++ /dev/null @@ -1,85 +0,0 @@ -import { fail } from "$lib/utils"; -import { emit } from "$lib/sockets"; -import { g, s, db } from "$lib/db"; -import { v4 } from "uuid"; - -export default { - async get({ params: { id } }, res) { - let request = await g(`request:${id}`); - if (!request) fail("request not found"); - request.recipient = await g(`user:${request.recipient_id}`); - request.requester = await g(`user:${request.requester_id}`); - request.invoice = await g(`invoice:${request.invoice_id}`); - res.send({ request }); - }, - - async list({ user: { id } }, res) { - try { - const day = new Date(new Date().setDate(new Date().getDate() - 1)); - - let requests = await db.lRange(`${id}:requests`, 0, -1); - - requests = await Promise.all(requests.map((id) => g(`request:${id}`))); - - requests = await Promise.all( - requests.map(async (r) => { - r.requester = await g(`user:${r.requester_id}`); - r.recipient = await g(`user:${r.recipient_id}`); - return r; - }), - ); - - let sent = requests.filter((r) => r.requester_id === id); - - let received = requests - .filter((r) => r.recipient_id === id) - .filter((r) => !r.invoice_id); - - let invoices = []; - for (let r of sent) { - if (!r.invoice_id) continue; - - let invoice = await g(`invoice:${r.invoice_id}`); - if (!invoice) continue; - - invoice.request = r; - if (invoice.amount < invoice.received) invoices.push(invoice); - } - - res.send({ invoices, sent, received }); - } catch (e) { - console.log(e); - res.code(500).send(e.message); - } - }, - - async create( - { - body: { recipient, ...params }, - user: { id: requester_id, username, profile }, - }, - res, - ) { - let recipient_id = await g(`user:${recipient}`); - - let id = v4(); - let request = { - id, - recipient_id, - requester_id, - ...params, - }; - - await s(`request:${id}`, request); - await db.lPush(`${requester_id}:requests`, id); - await db.lPush(`${recipient_id}:requests`, id); - - emit(recipient, "request", request); - res.send(request); - }, - - async destroy({ body: { request_id }, user: { id } }, res) { - await db.lrem(`user:${id}:requests`, request_id); - res.send(); - }, -}; diff --git a/routes/requests.js.map b/routes/requests.js.map new file mode 100644 index 00000000..448a576e --- /dev/null +++ b/routes/requests.js.map @@ -0,0 +1 @@ +{"version":3,"file":"requests.js","sourceRoot":"","sources":["requests.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/routes/requests.ts b/routes/requests.ts new file mode 100644 index 00000000..a0d55739 --- /dev/null +++ b/routes/requests.ts @@ -0,0 +1,85 @@ +import { fail } from "$lib/utils"; +import { emit } from "$lib/sockets"; +import { g, s, db } from "$lib/db"; +import { v4 } from "uuid"; + +export default { + async get({ params: { id } }, res) { + let request = await g(`request:${id}`); + if (!request) fail("request not found"); + request.recipient = await g(`user:${request.recipient_id}`); + request.requester = await g(`user:${request.requester_id}`); + request.invoice = await g(`invoice:${request.invoice_id}`); + res.send({ request }); + }, + + async list({ user: { id } }, res) { + try { + const day = new Date(new Date().setDate(new Date().getDate() - 1)); + + let requests = await db.lRange(`${id}:requests`, 0, -1); + + requests = await Promise.all(requests.map((id) => g(`request:${id}`))); + + requests = await Promise.all( + requests.map(async (r) => { + r.requester = await g(`user:${r.requester_id}`); + r.recipient = await g(`user:${r.recipient_id}`); + return r; + }), + ); + + let sent = requests.filter((r) => r.requester_id === id); + + let received = requests + .filter((r) => r.recipient_id === id) + .filter((r) => !r.invoice_id); + + let invoices = []; + for (let r of sent) { + if (!r.invoice_id) continue; + + let invoice = await g(`invoice:${r.invoice_id}`); + if (!invoice) continue; + + invoice.request = r; + if (invoice.amount < invoice.received) invoices.push(invoice); + } + + res.send({ invoices, sent, received }); + } catch (e) { + console.log(e); + res.code(500).send(e.message); + } + }, + + async create( + { + body: { recipient, ...params }, + user: { id: requester_id, username, profile }, + }, + res, + ) { + let recipient_id = await g(`user:${recipient}`); + + let id = v4(); + let request = { + id, + recipient_id, + requester_id, + ...params, + }; + + await s(`request:${id}`, request); + await db.lPush(`${requester_id}:requests`, id); + await db.lPush(`${recipient_id}:requests`, id); + + emit(recipient, "request", request); + res.send(request); + }, + + async destroy({ body: { request_id }, user: { id } }, res) { + await db.lrem(`user:${id}:requests`, request_id); + res.send(); + }, +}; diff --git a/routes/shopify.js b/routes/shopify.js deleted file mode 100644 index bcb60ea3..00000000 --- a/routes/shopify.js +++ /dev/null @@ -1,41 +0,0 @@ -import got from "got"; -import { g } from "$lib/db"; -import { err } from "$lib/logging"; - -let query = `mutation orderMarkAsPaid($input: OrderMarkAsPaidInput!) { - orderMarkAsPaid(input: $input) { - order { id } - userErrors { - field - message - } - } -}`; - -export default async ({ body: { hash }, params: { id } }, res) => { - let p = await g(`payment:${hash}`); - if (typeof p === "string") p = await g(`payment:${p}`); - let user = await g(`user:${p.uid}`); - - try { - let r = await got - .post( - `https://${user.shopifyStore}.myshopify.com/admin/api/2023-07/graphql.json`, - { - headers: { - "Content-Type": "application/json", - "X-Shopify-Access-Token": user.shopifyToken, - }, - json: { - query, - variables: { input: { id: `gid://shopify/Order/${id}` } }, - }, - }, - ) - .json(); - - res.send(r); - } catch (e) { - err("problem marking shopify order as paid", e.message); - } -}; diff --git a/routes/shopify.js.map b/routes/shopify.js.map new file mode 100644 index 00000000..af2690f8 --- /dev/null +++ b/routes/shopify.js.map @@ -0,0 +1 @@ +{"version":3,"file":"shopify.js","sourceRoot":"","sources":["shopify.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4CAAsB;AACtB,8BAA4B;AAC5B,wCAAmC;AAEnC,IAAI,KAAK,GAAG,kLAQV,CAAC;AAEH,mBAAe,+EAAO,EAAkC,EAAE,GAAG;;QAA7B,IAAI,eAAA,EAAc,EAAE,eAAA;;;oBAC1C,qBAAM,IAAA,MAAC,EAAC,kBAAW,IAAI,CAAE,CAAC,EAAA;;gBAA9B,CAAC,GAAG,SAA0B;qBAC9B,CAAA,OAAO,CAAC,KAAK,QAAQ,CAAA,EAArB,wBAAqB;gBAAM,qBAAM,IAAA,MAAC,EAAC,kBAAW,CAAC,CAAE,CAAC,EAAA;;gBAA3B,CAAC,GAAG,SAAuB,CAAC;;oBAC5C,qBAAM,IAAA,MAAC,EAAC,eAAQ,CAAC,CAAC,GAAG,CAAE,CAAC,EAAA;;gBAA/B,IAAI,GAAG,SAAwB;;;;gBAGzB,qBAAM,aAAG;yBACd,IAAI,CACH,kBAAW,IAAI,CAAC,YAAY,kDAA+C,EAC3E;wBACE,OAAO,EAAE;4BACP,cAAc,EAAE,kBAAkB;4BAClC,wBAAwB,EAAE,IAAI,CAAC,YAAY;yBAC5C;wBACD,IAAI,EAAE;4BACJ,KAAK,OAAA;4BACL,SAAS,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,8BAAuB,EAAE,CAAE,EAAE,EAAE;yBAC1D;qBACF,CACF;yBACA,IAAI,EAAE,EAAA;;gBAdL,CAAC,GAAG,SAcC;gBAET,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;;;;gBAEZ,IAAA,aAAG,EAAC,uCAAuC,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;KAE3D,EAAC"} \ No newline at end of file diff --git a/routes/shopify.ts b/routes/shopify.ts new file mode 100644 index 00000000..bcb60ea3 --- /dev/null +++ b/routes/shopify.ts @@ -0,0 +1,41 @@ +import got from "got"; +import { g } from "$lib/db"; +import { err } from "$lib/logging"; + +let query = `mutation orderMarkAsPaid($input: OrderMarkAsPaidInput!) { + orderMarkAsPaid(input: $input) { + order { id } + userErrors { + field + message + } + } +}`; + +export default async ({ body: { hash }, params: { id } }, res) => { + let p = await g(`payment:${hash}`); + if (typeof p === "string") p = await g(`payment:${p}`); + let user = await g(`user:${p.uid}`); + + try { + let r = await got + .post( + `https://${user.shopifyStore}.myshopify.com/admin/api/2023-07/graphql.json`, + { + headers: { + "Content-Type": "application/json", + "X-Shopify-Access-Token": user.shopifyToken, + }, + json: { + query, + variables: { input: { id: `gid://shopify/Order/${id}` } }, + }, + }, + ) + .json(); + + res.send(r); + } catch (e) { + err("problem marking shopify order as paid", e.message); + } +}; diff --git a/routes/users.js b/routes/users.js deleted file mode 100644 index 17bea006..00000000 --- a/routes/users.js +++ /dev/null @@ -1,556 +0,0 @@ -import { g, s, db } from "$lib/db"; -import config from "$config"; -import store from "$lib/store"; -import { - fields, - nada, - pick, - uniq, - wait, - bail, - fail, - getUser, -} from "$lib/utils"; -import jwt from "jsonwebtoken"; -import { authenticator } from "otplib"; -import whitelist from "$lib/whitelist"; -import { l, err, warn } from "$lib/logging"; -import { emit } from "$lib/sockets"; -import register from "$lib/register"; -import { requirePin } from "$lib/auth"; -import { v4 } from "uuid"; -import { parseISO } from "date-fns"; -import { types } from "$lib/payments"; -import { bech32 } from "bech32"; -import { mail, templates } from "$lib/mail"; - -import got from "got"; -import upload from "$lib/upload"; - -const { encode, decode, fromWords, toWords } = bech32; - -export default { - upload, - - async me({ user }, res) { - try { - user.balance = await g(`balance:${user.id}`); - user.prompt = !!user.prompt; - if (user.pubkey) - user.npub = encode("npub", toWords(Buffer.from(user.pubkey, "hex"))); - - res.send(pick(user, whitelist)); - } catch (e) { - console.log("problem fetching user", e); - res.code(500).send(e.message); - } - }, - - async list({ user }, res) { - if (!user.admin) fail("unauthorized"); - - let users = []; - - for await (let k of db.scanIterator({ MATCH: "balance:*" })) { - let uid = k.split(":")[1]; - let user = await getUser(uid); - - if (!user) { - await db.del(`balance:${uid}`); - continue; - } - - user.balance = await g(k); - - let payments = await db.lRange(`${uid}:payments`, 0, -1); - - let total = 0; - for (let pid of payments) { - let p = await g(`payment:${pid}`); - if (!p) continue; - total += p.amount; - if (p.amount < 0) - total -= (p.fee || 0) + (p.ourfee || 0) + (p.tip || 0); - else total += p.tip || 0; - } - - user.expected = total; - users.push(user); - } - - res.send(users); - }, - - async get({ params: { key } }, res) { - key = key.toLowerCase().replace(/\s/g, ""); - try { - if (key.startsWith("npub")) { - try { - key = Buffer.from(fromWords(decode(key).words)).toString("hex"); - } catch (e) {} - } - - let user = await getUser(key); - - if (!user && key.length === 64) { - user = { - currency: "USD", - username: key, - display: key.substr(0, 6), - pubkey: key, - anon: true, - }; - } - - if (!user) return res.code(500).send("User not found"); - - let whitelist = [ - "address", - "anon", - "banner", - "banner", - "currency", - "display", - "id", - "hidepay", - "memoPrompt", - "npub", - "profile", - "prompt", - "pubkey", - "username", - ]; - - if (user.pubkey) - user.npub = encode("npub", toWords(Buffer.from(user.pubkey, "hex"))); - user.prompt = !!user.prompt; - - res.send(pick(user, whitelist)); - } catch (e) { - err("problem getting user", e.message); - res.code(500).send(e.message); - } - }, - - async create({ body, headers }, res) { - try { - const ip = headers["cf-connecting-ip"]; - if (!body.user) fail("no user object provided"); - let { user } = body; - let fields = [ - "profile", - "cipher", - "pubkey", - "password", - "username", - "salt", - ]; - - user = await register(pick(user, fields), ip, false); - l("registered new user", user.username); - res.send(pick(user, whitelist)); - } catch (e) { - res.code(500).send(e.message); - } - }, - - async disable2fa({ user, body: { token } }, res) { - let { id, twofa, username, otpsecret } = user; - if (twofa && !authenticator.check(token, otpsecret)) { - return res.code(401).send("2fa required"); - } - - user.twofa = false; - await s(`user:${id}`, user); - emit(username, "user", user); - emit(username, "otpsecret", user.otpsecret); - l("disabled 2fa", username); - res.send({}); - }, - - async enable2fa({ user, body: { token } }, res) { - let { id, otpsecret, username } = user; - const isValid = authenticator.check(token, otpsecret); - if (isValid) { - user.twofa = true; - await s(`user:${id}`, user); - emit(username, "user", user); - } else { - return res.code(500).send("Invalid token"); - } - - l("enabled 2fa", username); - res.send({}); - }, - - async update({ user, body }, res) { - try { - l("updating user", user.username); - - let { - profile, - banner, - confirm, - password, - pin, - newpin, - pubkey, - username, - shopifyToken, - shopifyStore, - } = body; - - if (user.pin && !(pin === user.pin)) fail("Pin required"); - if (typeof newpin !== "undefined" && newpin.length === 6) - user.pin = newpin; - if (user.pin === "delete") delete user.pin; - - let exists; - if (username) exists = await getUser(username); - - let token, oldname; - if (user.username.toLowerCase() !== username.toLowerCase() && exists) { - err("username taken", username, user.username, exists.username); - fail("Username taken"); - } else if (username) { - if (user.username.toLowerCase() !== username.toLowerCase()) - l("changing username", user.username, username); - - await db.del(`user:${user.username}`); - oldname = user.username; - user.username = username; - } - - if (pubkey) exists = await getUser(pubkey); - if (exists && ![oldname, username].includes(exists.username)) { - warn("key in use", pubkey, exists.username); - if (exists.anon) await db.del(`user:${pubkey}`); - else fail("Key in use by another account"); - } - - let attributes = [ - "address", - "autowithdraw", - "banner", - "cipher", - "currencies", - "currency", - "destination", - "display", - "email", - "language", - "fiat", - "locktime", - "memoPrompt", - "nip5", - "notify", - "nsec", - "prompt", - "profile", - "pubkey", - "reserve", - "salt", - "seed", - "threshold", - "tokens", - "twofa", - "shopifyToken", - "shopifyStore", - ]; - - for (let a of attributes) { - if (typeof body[a] !== "undefined") user[a] = body[a]; - } - - if (password && password === confirm) { - user.password = await Bun.password.hash(password, { - algorithm: "bcrypt", - cost: 1, - }); - } - - user.haspin = !!user.pin; - await s(`user:${user.pubkey}`, user.id); - await s( - `user:${user.username.toLowerCase().replace(/\s/g, "")}`, - user.id, - ); - - await s(`user:${user.id}`, user); - if (user.nip5) await db.sAdd("nip5", `${user.username}:${user.pubkey}`); - - emit(user.id, "user", user); - res.send({ user, token }); - } catch (e) { - warn("failed to update", user.username, e.message); - bail(res, e.message); - } - }, - - async login(req, res) { - try { - let { username, password, token: twofa } = req.body; - - if (username !== "coinos" && username !== "funk") - l("logging in", username, req.headers["cf-connecting-ip"]); - - username = username.toLowerCase().replace(/\s/g, ""); - let user = await getUser(username); - - if ( - !user || - (user.password && - !( - (config.adminpass && password === config.adminpass) || - (await Bun.password.verify(password, user.password)) - )) - ) { - warn("invalid username or password attempt", username); - return res.code(401).send({}); - } - - if ( - !(config.adminpass && password === config.adminpass) && - user.twofa && - (typeof twofa === "undefined" || - !authenticator.check(twofa, user.otpsecret)) - ) { - return res.code(401).send("2fa required"); - } - - if (username !== "coinos" && username !== "funk") - l("logged in", username); - - let payload = { id: user.id }; - let token = jwt.sign(payload, config.jwt); - res.cookie("token", token, { expires: new Date(Date.now() + 432000000) }); - user = pick(user, whitelist); - res.send({ user, token }); - } catch (e) { - err("login error", e.message, req.socket.remoteAddress); - res.code(401).send({}); - } - }, - - async subscribe({ body, user }, res) { - let { subscriptions } = user; - let { subscription } = body; - if (!subscriptions) subscriptions = []; - if ( - !subscriptions.find( - (s) => JSON.stringify(s) === JSON.stringify(subscription), - ) - ) - subscriptions.push(subscription); - user.subscriptions = subscriptions; - l("subscribing", user.username); - await user.save(); - res.sendStatus(201); - }, - - async password({ body: { password }, user }, res) { - if (!user.password) return res.send(true); - - try { - if (!password) fail("password not provided"); - res.send(await Bun.password.verify(password, user.password)); - } catch (e) { - bail(res, e.message); - } - }, - - async pin({ body: { pin }, user }, res) { - res.send(!user.pin || user.pin === pin); - }, - - async otpsecret(req, res) { - try { - await requirePin(req); - let { otpsecret, username } = req.user; - res.send({ secret: otpsecret, username }); - } catch (e) { - res.code(500).send(e.message); - } - }, - - async contacts({ user: { id } }, res) { - let lastlen = (await g(`${id}:lastlen`)) || 0; - let len = await db.lLen(`${id}:payments`); - let payments = (await db.lRange(`${id}:payments`, 0, len - lastlen)) || []; - await db.set(`${id}:lastlen`, len); - - let contacts = (await g(`${id}:contacts`)) || []; - - for (let { ref } of ( - await Promise.all( - payments.reverse().map(async (id) => await g(`payment:${id}`)), - ) - ).filter((p) => p && p.type === types.internal && p.ref)) { - let i = contacts.findIndex((c) => c && c.id === ref); - if (~i) contacts.splice(i, 1); - let u = await g(`user:${ref}`); - if (typeof u === "string") u = await g(`user:${ref}`); - if (u) contacts.unshift(pick(u, ["id", "profile", "username"])); - } - - await s(`${id}:contacts`, contacts); - - res.send(contacts); - }, - - async del({ params: { username }, headers: { authorization } }, res) { - username = username.toLowerCase(); - if (!(authorization && authorization.includes(config.admin))) - return res.code(401).send("unauthorized"); - - let { id, pubkey } = await g( - `user:${await g(`user:${username.replace(/\s/g, "").toLowerCase()}`)}`, - ); - let invoices = await db.lRange(`${id}:invoices`, 0, -1); - let payments = await db.lRange(`${id}:payments`, 0, -1); - - for (let { id } of invoices) db.del(`invoice:${id}`); - for (let { id } of payments) db.del(`payment:${id}`); - db.del(`user:${username.toLowerCase()}`); - db.del(`user:${id}`); - db.del(`user:${pubkey}`); - - res.send({}); - }, - - async reset({ body: { code, username, password }, user: u }, res) { - let admin = u && u.admin; - let id, user; - - if (admin) { - id = await g(`user:${username.toLowerCase().replace(/\s/g, "")}`); - user = await g(`user:${id}`); - } else { - id = await g(`reset:${code}`); - user = await g(`user:${id}`); - } - - if (!user) fail("user not found"); - - try { - user.pin = null; - user.pubkey = null; - user.cipher = null; - user.salt = null; - user.password = await Bun.password.hash(password, { - algorithm: "bcrypt", - cost: 1, - }); - - await s(`user:${id}`, user); - await db.del(`reset:${code}`); - - res.send(pick(user, whitelist)); - } catch (e) { - bail(res, e.message); - } - }, - - async printerlogin({ body: { username, topic } }, res) { - if (username === topic) res.send({ ok: true }); - else bail(res, "unauthorized"); - }, - - async acl({ body: { username, topic } }, res) { - if (username === topic) res.send({ ok: true }); - else bail(res, "unauthorized"); - }, - - async superuser({ body: { username } }, res) { - if (username === config.mqtt2.username) res.send({ ok: true }); - else bail(res, "unauthorized"); - }, - - async request({ body: { id, email } }, res) { - try { - let user = await g(`user:${id}`); - - if (!user) fail("user not found"); - if (await g(`email:${email.toLowerCase()}`)) fail("Email already in use"); - - let { username } = user; - - if (email !== user.email) { - user.verified = false; - await s(`user:${id}`, user); - user.email = email; - } - - let code = v4(); - await s(`verify:${code}`, { id, email }); - let link = `${process.env.URL}/${username}/verify/${code}`; - let subject = "Email Verification"; - - await mail(user, subject, templates.verifyEmail, { - username, - link, - }); - - res.send({ ok: true }); - } catch (e) { - bail(res, e.message); - } - }, - - async verify({ params: { code } }, res) { - try { - let { id, email } = await g(`verify:${code}`); - if (!id) fail(res, "verification failed"); - let user = await g(`user:${id}`); - user.email = email; - user.verified = true; - await s(`user:${id}`, user); - await s(`email:${email.toLowerCase()}`, id); - - res.send(pick(user, whitelist)); - } catch (e) { - bail(res, e.message); - } - }, - - async forgot({ body: { email } }, res) { - try { - let uid = await g(`email:${email.toLowerCase()}`); - let user = await g(`user:${uid}`); - - if (user) { - let code = v4(); - let link = `${process.env.URL}/reset/${code}`; - await s(`reset:${code}`, uid); - - await mail(user, "Password reset", templates.passwordReset, { - ...user, - link, - }); - } - - res.send({}); - } catch (e) { - bail(res, e.message); - } - }, - - async items({ params: { id } }, res) { - let item = await g(`item:${id}`); - return item; - }, - - async hidepay({ body: { username } }, res) { - let u = await getUser(username); - u.hidepay = true; - await s(`user:${u.id}`, u); - res.send({}); - }, - - async unlimit({ body: { username } }, res) { - let u = await getUser(username); - u.unlimited = true; - await s(`user:${u.id}`, u); - res.send({}); - }, -}; diff --git a/routes/users.js.map b/routes/users.js.map new file mode 100644 index 00000000..75a073fb --- /dev/null +++ b/routes/users.js.map @@ -0,0 +1 @@ +{"version":3,"file":"users.js","sourceRoot":"","sources":["users.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8BAAmC;AACnC,oDAA6B;AAE7B,oCASoB;AACpB,8DAA+B;AAC/B,iCAAuC;AACvC,6DAAuC;AACvC,wCAA4C;AAC5C,wCAAoC;AACpC,2DAAqC;AACrC,kCAAuC;AACvC,6BAA0B;AAE1B,0CAAsC;AACtC,iCAAgC;AAChC,kCAA4C;AAG5C,uDAAiC;AAEzB,IAAA,MAAM,GAAiC,eAAM,OAAvC,EAAE,MAAM,GAAyB,eAAM,OAA/B,EAAE,SAAS,GAAc,eAAM,UAApB,EAAE,OAAO,GAAK,eAAM,QAAX,CAAY;AAEtD,kBAAe;IACb,MAAM,kBAAA;IAEA,EAAE;4DAAC,EAAQ,EAAE,GAAG;;gBAAX,IAAI,UAAA;;;;;wBAEX,KAAA,IAAI,CAAA;wBAAW,qBAAM,IAAA,MAAC,EAAC,kBAAW,IAAI,CAAC,EAAE,CAAE,CAAC,EAAA;;wBAA5C,GAAK,OAAO,GAAG,SAA6B,CAAC;wBAC7C,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;wBAC5B,IAAI,IAAI,CAAC,MAAM;4BACb,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;wBAEvE,GAAG,CAAC,IAAI,CAAC,IAAA,YAAI,EAAC,IAAI,EAAE,mBAAS,CAAC,CAAC,CAAC;;;;wBAEhC,OAAO,CAAC,GAAG,CAAC,uBAAuB,EAAE,GAAC,CAAC,CAAC;wBACxC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAEjC;IAEK,IAAI;4DAAC,EAAQ,EAAE,GAAG;;;gBAAX,IAAI,UAAA;;;;wBACf,IAAI,CAAC,IAAI,CAAC,KAAK;4BAAE,IAAA,YAAI,EAAC,cAAc,CAAC,CAAC;wBAElC,KAAK,GAAG,EAAE,CAAC;;;;mCAEK,KAAA,cAAA,OAAE,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;;;;;wBAAvC,cAAuC;wBAAvC,WAAuC;wBAA5C,CAAC,KAAA,CAAA;wBACV,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;wBACf,qBAAM,IAAA,eAAO,EAAC,GAAG,CAAC,EAAA;;wBAAzB,SAAO,SAAkB;6BAEzB,CAAC,MAAI,EAAL,wBAAK;wBACP,qBAAM,OAAE,CAAC,GAAG,CAAC,kBAAW,GAAG,CAAE,CAAC,EAAA;;wBAA9B,SAA8B,CAAC;wBAC/B,yBAAS;;wBAGX,KAAA,MAAI,CAAA;wBAAW,qBAAM,IAAA,MAAC,EAAC,CAAC,CAAC,EAAA;;wBAAzB,GAAK,OAAO,GAAG,SAAU,CAAC;wBAEX,qBAAM,OAAE,CAAC,MAAM,CAAC,UAAG,GAAG,cAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAA;;wBAApD,QAAQ,GAAG,SAAyC;wBAEpD,KAAK,GAAG,CAAC,CAAC;8BACU,EAAR,qBAAQ;;;6BAAR,CAAA,sBAAQ,CAAA;wBAAf,GAAG;wBACF,qBAAM,IAAA,MAAC,EAAC,kBAAW,GAAG,CAAE,CAAC,EAAA;;wBAA7B,CAAC,GAAG,SAAyB;wBACjC,IAAI,CAAC,CAAC;4BAAE,yBAAS;wBACjB,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;wBAClB,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;4BACd,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;;4BACpD,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;;;wBANX,IAAQ,CAAA;;;wBASxB,MAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;wBACtB,KAAK,CAAC,IAAI,CAAC,MAAI,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;wBAGnB,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;;;;KACjB;IAEK,GAAG;4DAAC,EAAmB,EAAE,GAAG;;gBAAZ,GAAG,gBAAA;;;;wBACvB,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;;;;wBAEzC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;4BAC3B,IAAI,CAAC;gCACH,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;4BAClE,CAAC;4BAAC,OAAO,CAAC,EAAE,CAAC,CAAA,CAAC;wBAChB,CAAC;wBAEU,qBAAM,IAAA,eAAO,EAAC,GAAG,CAAC,EAAA;;wBAAzB,IAAI,GAAG,SAAkB;wBAE7B,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;4BAC/B,IAAI,GAAG;gCACL,QAAQ,EAAE,KAAK;gCACf,QAAQ,EAAE,GAAG;gCACb,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;gCACzB,MAAM,EAAE,GAAG;gCACX,IAAI,EAAE,IAAI;6BACX,CAAC;wBACJ,CAAC;wBAED,IAAI,CAAC,IAAI;4BAAE,sBAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAC;wBAEnD,cAAY;4BACd,SAAS;4BACT,MAAM;4BACN,QAAQ;4BACR,QAAQ;4BACR,UAAU;4BACV,SAAS;4BACT,IAAI;4BACJ,SAAS;4BACT,YAAY;4BACZ,MAAM;4BACN,SAAS;4BACT,QAAQ;4BACR,QAAQ;4BACR,UAAU;yBACX,CAAC;wBAEF,IAAI,IAAI,CAAC,MAAM;4BACb,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;wBACvE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;wBAE5B,GAAG,CAAC,IAAI,CAAC,IAAA,YAAI,EAAC,IAAI,EAAE,WAAS,CAAC,CAAC,CAAC;;;;wBAEhC,IAAA,aAAG,EAAC,sBAAsB,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;wBACvC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAEjC;IAEK,MAAM;4DAAC,EAAiB,EAAE,GAAG;;gBAApB,IAAI,UAAA,EAAE,OAAO,aAAA;;;;;wBAElB,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;wBACvC,IAAI,CAAC,IAAI,CAAC,IAAI;4BAAE,IAAA,YAAI,EAAC,yBAAyB,CAAC,CAAC;wBAC1C,IAAI,GAAK,IAAI,KAAT,CAAU;wBAChB,WAAS;4BACX,SAAS;4BACT,QAAQ;4BACR,QAAQ;4BACR,UAAU;4BACV,UAAU;4BACV,MAAM;yBACP,CAAC;wBAEK,qBAAM,IAAA,kBAAQ,EAAC,IAAA,YAAI,EAAC,IAAI,EAAE,QAAM,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAA;;wBAApD,IAAI,GAAG,SAA6C,CAAC;wBACrD,IAAA,WAAC,EAAC,qBAAqB,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;wBACxC,GAAG,CAAC,IAAI,CAAC,IAAA,YAAI,EAAC,IAAI,EAAE,mBAAS,CAAC,CAAC,CAAC;;;;wBAEhC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAEjC;IAEK,UAAU;4DAAC,EAAyB,EAAE,GAAG;;gBAA5B,IAAI,UAAA,EAAU,KAAK,gBAAA;;;;wBAC9B,EAAE,GAAiC,IAAI,GAArC,EAAE,KAAK,GAA0B,IAAI,MAA9B,EAAE,QAAQ,GAAgB,IAAI,SAApB,EAAE,SAAS,GAAK,IAAI,UAAT,CAAU;wBAC9C,IAAI,KAAK,IAAI,CAAC,sBAAa,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;4BACpD,sBAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,EAAC;wBAC5C,CAAC;wBAED,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;wBACnB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;wBAA3B,SAA2B,CAAC;wBAC5B,IAAA,cAAI,EAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;wBAC7B,IAAA,cAAI,EAAC,QAAQ,EAAE,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;wBAC5C,IAAA,WAAC,EAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;wBAC5B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;;KACd;IAEK,SAAS;4DAAC,EAAyB,EAAE,GAAG;;gBAA5B,IAAI,UAAA,EAAU,KAAK,gBAAA;;;;wBAC7B,EAAE,GAA0B,IAAI,GAA9B,EAAE,SAAS,GAAe,IAAI,UAAnB,EAAE,QAAQ,GAAK,IAAI,SAAT,CAAU;wBACjC,OAAO,GAAG,sBAAa,CAAC,KAAK,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;6BAClD,OAAO,EAAP,wBAAO;wBACT,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;wBAClB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;wBAA3B,SAA2B,CAAC;wBAC5B,IAAA,cAAI,EAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;;4BAE7B,sBAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,EAAC;;wBAG7C,IAAA,WAAC,EAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;wBAC3B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;;KACd;IAEK,MAAM;4DAAC,EAAc,EAAE,GAAG;;gBAAjB,IAAI,UAAA,EAAE,IAAI,UAAA;;;;;wBAErB,IAAA,WAAC,EAAC,eAAe,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;wBAGhC,OAAO,GAUL,IAAI,QAVC,EACP,MAAM,GASJ,IAAI,OATA,EACN,YAQE,IAAI,QARC,EACP,QAAQ,GAON,IAAI,SAPE,EACR,GAAG,GAMD,IAAI,IANH,EACH,MAAM,GAKJ,IAAI,OALA,EACN,MAAM,GAIJ,IAAI,OAJA,EACN,QAAQ,GAGN,IAAI,SAHE,EACR,YAAY,GAEV,IAAI,aAFM,EACZ,YAAY,GACV,IAAI,aADM,CACL;wBAET,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,CAAC;4BAAE,IAAA,YAAI,EAAC,cAAc,CAAC,CAAC;wBAC1D,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;4BACtD,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC;wBACpB,IAAI,IAAI,CAAC,GAAG,KAAK,QAAQ;4BAAE,OAAO,IAAI,CAAC,GAAG,CAAC;wBAEvC,MAAM,SAAA,CAAC;6BACP,QAAQ,EAAR,wBAAQ;wBAAW,qBAAM,IAAA,eAAO,EAAC,QAAQ,CAAC,EAAA;;wBAAhC,MAAM,GAAG,SAAuB,CAAC;;;wBAE3C,KAAK,SAAA,EAAE,OAAO,SAAA,CAAC;6BACf,CAAA,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC,WAAW,EAAE,IAAI,MAAM,CAAA,EAAhE,wBAAgE;wBAClE,IAAA,aAAG,EAAC,gBAAgB,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;wBAChE,IAAA,YAAI,EAAC,gBAAgB,CAAC,CAAC;;;6BACd,QAAQ,EAAR,wBAAQ;wBACjB,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,QAAQ,CAAC,WAAW,EAAE;4BACxD,IAAA,WAAC,EAAC,mBAAmB,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;wBAElD,qBAAM,OAAE,CAAC,GAAG,CAAC,eAAQ,IAAI,CAAC,QAAQ,CAAE,CAAC,EAAA;;wBAArC,SAAqC,CAAC;wBACtC,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;wBACxB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;;;6BAGvB,MAAM,EAAN,wBAAM;wBAAW,qBAAM,IAAA,eAAO,EAAC,MAAM,CAAC,EAAA;;wBAA9B,MAAM,GAAG,SAAqB,CAAC;;;6BACvC,CAAA,MAAM,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA,EAAxD,yBAAwD;wBAC1D,IAAA,cAAI,EAAC,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;6BACxC,MAAM,CAAC,IAAI,EAAX,wBAAW;wBAAE,qBAAM,OAAE,CAAC,GAAG,CAAC,eAAQ,MAAM,CAAE,CAAC,EAAA;;wBAA9B,SAA8B,CAAC;;;wBAC3C,IAAA,YAAI,EAAC,+BAA+B,CAAC,CAAC;;;wBAGzC,UAAU,GAAG;4BACf,SAAS;4BACT,cAAc;4BACd,QAAQ;4BACR,QAAQ;4BACR,YAAY;4BACZ,UAAU;4BACV,aAAa;4BACb,SAAS;4BACT,OAAO;4BACP,UAAU;4BACV,MAAM;4BACN,UAAU;4BACV,YAAY;4BACZ,MAAM;4BACN,QAAQ;4BACR,MAAM;4BACN,QAAQ;4BACR,SAAS;4BACT,QAAQ;4BACR,SAAS;4BACT,MAAM;4BACN,MAAM;4BACN,WAAW;4BACX,QAAQ;4BACR,OAAO;4BACP,cAAc;4BACd,cAAc;yBACf,CAAC;wBAEF,WAAwB,EAAV,yBAAU,EAAV,wBAAU,EAAV,IAAU,EAAE,CAAC;4BAAlB,CAAC;4BACR,IAAI,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,WAAW;gCAAE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;wBACxD,CAAC;6BAEG,CAAA,QAAQ,IAAI,QAAQ,KAAK,SAAO,CAAA,EAAhC,yBAAgC;wBAClC,KAAA,IAAI,CAAA;wBAAY,qBAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE;gCAChD,SAAS,EAAE,QAAQ;gCACnB,IAAI,EAAE,CAAC;6BACR,CAAC,EAAA;;wBAHF,GAAK,QAAQ,GAAG,SAGd,CAAC;;;wBAGL,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;wBACzB,qBAAM,IAAA,MAAC,EAAC,eAAQ,IAAI,CAAC,MAAM,CAAE,EAAE,IAAI,CAAC,EAAE,CAAC,EAAA;;wBAAvC,SAAuC,CAAC;wBACxC,qBAAM,IAAA,MAAC,EACL,eAAQ,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAE,EACxD,IAAI,CAAC,EAAE,CACR,EAAA;;wBAHD,SAGC,CAAC;wBAEF,qBAAM,IAAA,MAAC,EAAC,eAAQ,IAAI,CAAC,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;wBAAhC,SAAgC,CAAC;6BAC7B,IAAI,CAAC,IAAI,EAAT,yBAAS;wBAAE,qBAAM,OAAE,CAAC,IAAI,CAAC,MAAM,EAAE,UAAG,IAAI,CAAC,QAAQ,cAAI,IAAI,CAAC,MAAM,CAAE,CAAC,EAAA;;wBAAxD,SAAwD,CAAC;;;wBAExE,IAAA,cAAI,EAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;wBAC5B,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,MAAA,EAAE,KAAK,OAAA,EAAE,CAAC,CAAC;;;;wBAE1B,IAAA,cAAI,EAAC,kBAAkB,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;wBACnD,IAAA,YAAI,EAAC,GAAG,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,KAAK,YAAC,GAAG,EAAE,GAAG;;;;;;;wBAEZ,KAAuC,GAAG,CAAC,IAAI,EAA7C,QAAQ,cAAA,EAAE,QAAQ,cAAA,EAAS,KAAK,WAAA,CAAc;wBAEpD,IAAI,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,MAAM;4BAC9C,IAAA,WAAC,EAAC,YAAY,EAAE,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC;wBAE7D,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;wBAC1C,qBAAM,IAAA,eAAO,EAAC,QAAQ,CAAC,EAAA;;wBAA9B,IAAI,GAAG,SAAuB;wBAGhC,KAAA,CAAC,IAAI,CAAA;gCAAL,wBAAK;wBACJ,KAAA,IAAI,CAAC,QAAQ,CAAA;iCAAb,wBAAa;wBAEV,KAAA,CAAC,iBAAM,CAAC,SAAS,IAAI,QAAQ,KAAK,iBAAM,CAAC,SAAS,CAAC,CAAA;gCAAnD,wBAAmD;wBAClD,qBAAM,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAA;;wBAAnD,KAAA,CAAC,SAAkD,CAAC,CAAA;;;wBAFtD,KAAA,CAAC,IAGA,CAAA;;;wBAJH,KAAA,IAII,CAAA;;;wBANN,QAOE,CAAC;4BACD,IAAA,cAAI,EAAC,sCAAsC,EAAE,QAAQ,CAAC,CAAC;4BACvD,sBAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAC;wBAChC,CAAC;wBAED,IACE,CAAC,CAAC,iBAAM,CAAC,SAAS,IAAI,QAAQ,KAAK,iBAAM,CAAC,SAAS,CAAC;4BACpD,IAAI,CAAC,KAAK;4BACV,CAAC,OAAO,KAAK,KAAK,WAAW;gCAC3B,CAAC,sBAAa,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,EAC9C,CAAC;4BACD,sBAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,EAAC;wBAC5C,CAAC;wBAED,IAAI,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,MAAM;4BAC9C,IAAA,WAAC,EAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;wBAEvB,OAAO,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;wBAC1B,KAAK,GAAG,sBAAG,CAAC,IAAI,CAAC,OAAO,EAAE,iBAAM,CAAC,GAAG,CAAC,CAAC;wBAC1C,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;wBAC1E,IAAI,GAAG,IAAA,YAAI,EAAC,IAAI,EAAE,mBAAS,CAAC,CAAC;wBAC7B,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,MAAA,EAAE,KAAK,OAAA,EAAE,CAAC,CAAC;;;;wBAE1B,IAAA,aAAG,EAAC,aAAa,EAAE,GAAC,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;wBACxD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;;;KAE1B;IAEK,SAAS;4DAAC,EAAc,EAAE,GAAG;;gBAAjB,IAAI,UAAA,EAAE,IAAI,UAAA;;;;wBACpB,aAAa,GAAK,IAAI,cAAT,CAAU;wBACvB,YAAY,GAAK,IAAI,aAAT,CAAU;wBAC5B,IAAI,CAAC,aAAa;4BAAE,aAAa,GAAG,EAAE,CAAC;wBACvC,IACE,CAAC,aAAa,CAAC,IAAI,CACjB,UAAC,CAAC,IAAK,OAAA,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,EAAlD,CAAkD,CAC1D;4BAED,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;wBACnC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;wBACnC,IAAA,WAAC,EAAC,aAAa,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;wBAChC,qBAAM,IAAI,CAAC,IAAI,EAAE,EAAA;;wBAAjB,SAAiB,CAAC;wBAClB,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;;;;;KACrB;IAEK,QAAQ;4DAAC,EAA4B,EAAE,GAAG;;gBAAvB,QAAQ,mBAAA,EAAI,IAAI,UAAA;;;;wBACvC,IAAI,CAAC,IAAI,CAAC,QAAQ;4BAAE,sBAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAC;;;;wBAGxC,IAAI,CAAC,QAAQ;4BAAE,IAAA,YAAI,EAAC,uBAAuB,CAAC,CAAC;wBAC7C,KAAA,CAAA,KAAA,GAAG,CAAA,CAAC,IAAI,CAAA;wBAAC,qBAAM,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAA;;wBAA3D,cAAS,SAAkD,EAAC,CAAC;;;;wBAE7D,IAAA,YAAI,EAAC,GAAG,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,GAAG;4DAAC,EAAuB,EAAE,GAAG;gBAAlB,GAAG,cAAA,EAAI,IAAI,UAAA;;gBAC7B,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;;;;KACzC;IAEK,SAAS,YAAC,GAAG,EAAE,GAAG;;;;;;;wBAEpB,qBAAM,IAAA,iBAAU,EAAC,GAAG,CAAC,EAAA;;wBAArB,SAAqB,CAAC;wBAClB,KAA0B,GAAG,CAAC,IAAI,EAAhC,SAAS,eAAA,EAAE,QAAQ,cAAA,CAAc;wBACvC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,UAAA,EAAE,CAAC,CAAC;;;;wBAE1C,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAEjC;IAEK,QAAQ;4DAAC,EAAgB,EAAE,GAAG;;;gBAAX,EAAE,aAAA;;;4BACV,qBAAM,IAAA,MAAC,EAAC,UAAG,EAAE,aAAU,CAAC,EAAA;;wBAAnC,OAAO,GAAG,CAAC,SAAwB,CAAC,IAAI,CAAC;wBACnC,qBAAM,OAAE,CAAC,IAAI,CAAC,UAAG,EAAE,cAAW,CAAC,EAAA;;wBAArC,GAAG,GAAG,SAA+B;wBACzB,qBAAM,OAAE,CAAC,MAAM,CAAC,UAAG,EAAE,cAAW,EAAE,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,EAAA;;wBAA/D,QAAQ,GAAG,CAAC,SAAmD,CAAC,IAAI,EAAE;wBAC1E,qBAAM,OAAE,CAAC,GAAG,CAAC,UAAG,EAAE,aAAU,EAAE,GAAG,CAAC,EAAA;;wBAAlC,SAAkC,CAAC;wBAEnB,qBAAM,IAAA,MAAC,EAAC,UAAG,EAAE,cAAW,CAAC,EAAA;;wBAArC,QAAQ,GAAG,CAAC,SAAyB,CAAC,IAAI,EAAE;4CAErC,GAAG;;;;;wCAKR,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,GAAG,EAAjB,CAAiB,CAAC,CAAC;wCACrD,IAAI,CAAC,CAAC;4CAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;wCACtB,qBAAM,IAAA,MAAC,EAAC,eAAQ,GAAG,CAAE,CAAC,EAAA;;wCAA1B,CAAC,GAAG,SAAsB;6CAC1B,CAAA,OAAO,CAAC,KAAK,QAAQ,CAAA,EAArB,wBAAqB;wCAAM,qBAAM,IAAA,MAAC,EAAC,eAAQ,GAAG,CAAE,CAAC,EAAA;;wCAA1B,CAAC,GAAG,SAAsB,CAAC;;;wCACtD,IAAI,CAAC;4CAAE,QAAQ,CAAC,OAAO,CAAC,IAAA,YAAI,EAAC,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;;;;;8BALV;wBAHtD,qBAAM,OAAO,CAAC,GAAG,CACf,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,UAAO,EAAE;;4CAAK,qBAAM,IAAA,MAAC,EAAC,kBAAW,EAAE,CAAE,CAAC,EAAA;4CAAxB,sBAAA,SAAwB,EAAA;;qCAAA,CAAC,CAC/D,EAAA;;wBAHiB,KAAA,CAClB,SAEC,CACF,CAAC,MAAM,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,gBAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,EAAvC,CAAuC,CAAC;;;6BAJpC,CAAA,cAIoC,CAAA;wBAJ7C,GAAG,aAAA;sDAAH,GAAG;;;;;wBAAM,IAIoC,CAAA;;6BAQxD,qBAAM,IAAA,MAAC,EAAC,UAAG,EAAE,cAAW,EAAE,QAAQ,CAAC,EAAA;;wBAAnC,SAAmC,CAAC;wBAEpC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;;;;;KACpB;IAEK,GAAG;4DAAC,EAAoD,EAAE,GAAG;;gBAA7C,QAAQ,qBAAA,EAAe,aAAa,2BAAA;;;;wBACxD,QAAQ,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;wBAClC,IAAI,CAAC,CAAC,aAAa,IAAI,aAAa,CAAC,QAAQ,CAAC,iBAAM,CAAC,KAAK,CAAC,CAAC;4BAC1D,sBAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,EAAC;wBAEjB,KAAA,MAAC,CAAA;;wBAClB,qBAAM,IAAA,MAAC,EAAC,eAAQ,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAE,CAAC,EAAA;4BADjD,qBAAM,kBACzB,mBAAQ,SAA4D,EAAE,EACvE,EAAA;;wBAFG,KAAiB,SAEpB,EAFK,EAAE,QAAA,EAAE,MAAM,YAAA;wBAGD,qBAAM,OAAE,CAAC,MAAM,CAAC,UAAG,EAAE,cAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAA;;wBAAnD,QAAQ,GAAG,SAAwC;wBACxC,qBAAM,OAAE,CAAC,MAAM,CAAC,UAAG,EAAE,cAAW,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAA;;wBAAnD,QAAQ,GAAG,SAAwC;wBAEvD,WAA2B,EAAR,qBAAQ,EAAR,sBAAQ,EAAR,IAAQ;4BAAhB,wBAAE;4BAAgB,OAAE,CAAC,GAAG,CAAC,kBAAW,IAAE,CAAE,CAAC,CAAC;yBAAA;wBACrD,WAA2B,EAAR,qBAAQ,EAAR,sBAAQ,EAAR,IAAQ;4BAAhB,wBAAE;4BAAgB,OAAE,CAAC,GAAG,CAAC,kBAAW,IAAE,CAAE,CAAC,CAAC;yBAAA;wBACrD,OAAE,CAAC,GAAG,CAAC,eAAQ,QAAQ,CAAC,WAAW,EAAE,CAAE,CAAC,CAAC;wBACzC,OAAE,CAAC,GAAG,CAAC,eAAQ,EAAE,CAAE,CAAC,CAAC;wBACrB,OAAE,CAAC,GAAG,CAAC,eAAQ,MAAM,CAAE,CAAC,CAAC;wBAEzB,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;;KACd;IAEK,KAAK;4DAAC,EAA+C,EAAE,GAAG;;gBAAlD,YAAkC,EAA1B,IAAI,UAAA,EAAE,QAAQ,cAAA,EAAE,QAAQ,cAAA,EAAU,CAAC,UAAA;;;;wBACnD,KAAK,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;6BAGrB,KAAK,EAAL,wBAAK;wBACF,qBAAM,IAAA,MAAC,EAAC,eAAQ,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAE,CAAC,EAAA;;wBAAjE,EAAE,GAAG,SAA4D,CAAC;wBAC3D,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;wBAA5B,IAAI,GAAG,SAAqB,CAAC;;4BAExB,qBAAM,IAAA,MAAC,EAAC,gBAAS,IAAI,CAAE,CAAC,EAAA;;wBAA7B,EAAE,GAAG,SAAwB,CAAC;wBACvB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;wBAA5B,IAAI,GAAG,SAAqB,CAAC;;;wBAG/B,IAAI,CAAC,IAAI;4BAAE,IAAA,YAAI,EAAC,gBAAgB,CAAC,CAAC;;;;wBAGhC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;wBAChB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;wBACnB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;wBACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;wBACjB,KAAA,IAAI,CAAA;wBAAY,qBAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE;gCAChD,SAAS,EAAE,QAAQ;gCACnB,IAAI,EAAE,CAAC;6BACR,CAAC,EAAA;;wBAHF,GAAK,QAAQ,GAAG,SAGd,CAAC;wBAEH,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;wBAA3B,SAA2B,CAAC;wBAC5B,qBAAM,OAAE,CAAC,GAAG,CAAC,gBAAS,IAAI,CAAE,CAAC,EAAA;;wBAA7B,SAA6B,CAAC;wBAE9B,GAAG,CAAC,IAAI,CAAC,IAAA,YAAI,EAAC,IAAI,EAAE,mBAAS,CAAC,CAAC,CAAC;;;;wBAEhC,IAAA,YAAI,EAAC,GAAG,EAAE,GAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,YAAY;4DAAC,EAA6B,EAAE,GAAG;gBAAhC,YAAyB,EAAjB,QAAQ,cAAA,EAAE,KAAK,WAAA;;gBAC1C,IAAI,QAAQ,KAAK,KAAK;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;;oBAC1C,IAAA,YAAI,EAAC,GAAG,EAAE,cAAc,CAAC,CAAC;;;;KAChC;IAEK,GAAG;4DAAC,EAA6B,EAAE,GAAG;gBAAhC,YAAyB,EAAjB,QAAQ,cAAA,EAAE,KAAK,WAAA;;gBACjC,IAAI,QAAQ,KAAK,KAAK;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;;oBAC1C,IAAA,YAAI,EAAC,GAAG,EAAE,cAAc,CAAC,CAAC;;;;KAChC;IAEK,SAAS;4DAAC,EAAsB,EAAE,GAAG;gBAAjB,QAAQ,mBAAA;;gBAChC,IAAI,QAAQ,KAAK,iBAAM,CAAC,KAAK,CAAC,QAAQ;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;;oBAC1D,IAAA,YAAI,EAAC,GAAG,EAAE,cAAc,CAAC,CAAC;;;;KAChC;IAEK,OAAO;4DAAC,EAAuB,EAAE,GAAG;;gBAA1B,YAAmB,EAAX,EAAE,QAAA,EAAE,KAAK,WAAA;;;;;wBAElB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;wBAA5B,IAAI,GAAG,SAAqB;wBAEhC,IAAI,CAAC,IAAI;4BAAE,IAAA,YAAI,EAAC,gBAAgB,CAAC,CAAC;wBAC9B,qBAAM,IAAA,MAAC,EAAC,gBAAS,KAAK,CAAC,WAAW,EAAE,CAAE,CAAC,EAAA;;wBAA3C,IAAI,SAAuC;4BAAE,IAAA,YAAI,EAAC,sBAAsB,CAAC,CAAC;wBAEpE,QAAQ,GAAK,IAAI,SAAT,CAAU;6BAEpB,CAAA,KAAK,KAAK,IAAI,CAAC,KAAK,CAAA,EAApB,wBAAoB;wBACtB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;wBACtB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;wBAA3B,SAA2B,CAAC;wBAC5B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;;;wBAGjB,IAAI,GAAG,IAAA,SAAE,GAAE,CAAC;wBAChB,qBAAM,IAAA,MAAC,EAAC,iBAAU,IAAI,CAAE,EAAE,EAAE,EAAE,IAAA,EAAE,KAAK,OAAA,EAAE,CAAC,EAAA;;wBAAxC,SAAwC,CAAC;wBACrC,IAAI,GAAG,UAAG,OAAO,CAAC,GAAG,CAAC,GAAG,cAAI,QAAQ,qBAAW,IAAI,CAAE,CAAC;wBACvD,OAAO,GAAG,oBAAoB,CAAC;wBAEnC,qBAAM,IAAA,WAAI,EAAC,IAAI,EAAE,OAAO,EAAE,gBAAS,CAAC,WAAW,EAAE;gCAC/C,QAAQ,UAAA;gCACR,IAAI,MAAA;6BACL,CAAC,EAAA;;wBAHF,SAGE,CAAC;wBAEH,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;;;;wBAEvB,IAAA,YAAI,EAAC,GAAG,EAAE,IAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,MAAM;4DAAC,EAAoB,EAAE,GAAG;;gBAAb,IAAI,iBAAA;;;;;wBAEL,qBAAM,IAAA,MAAC,EAAC,iBAAU,IAAI,CAAE,CAAC,EAAA;;wBAAzC,KAAgB,SAAyB,EAAvC,EAAE,QAAA,EAAE,KAAK,WAAA;wBACf,IAAI,CAAC,EAAE;4BAAE,IAAA,YAAI,EAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC;wBAC/B,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;wBAA5B,IAAI,GAAG,SAAqB;wBAChC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;wBACnB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;wBACrB,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,EAAE,IAAI,CAAC,EAAA;;wBAA3B,SAA2B,CAAC;wBAC5B,qBAAM,IAAA,MAAC,EAAC,gBAAS,KAAK,CAAC,WAAW,EAAE,CAAE,EAAE,EAAE,CAAC,EAAA;;wBAA3C,SAA2C,CAAC;wBAE5C,GAAG,CAAC,IAAI,CAAC,IAAA,YAAI,EAAC,IAAI,EAAE,mBAAS,CAAC,CAAC,CAAC;;;;wBAEhC,IAAA,YAAI,EAAC,GAAG,EAAE,IAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,MAAM;4DAAC,EAAmB,EAAE,GAAG;;gBAAd,KAAK,gBAAA;;;;;wBAEd,qBAAM,IAAA,MAAC,EAAC,gBAAS,KAAK,CAAC,WAAW,EAAE,CAAE,CAAC,EAAA;;wBAA7C,GAAG,GAAG,SAAuC;wBACtC,qBAAM,IAAA,MAAC,EAAC,eAAQ,GAAG,CAAE,CAAC,EAAA;;wBAA7B,IAAI,GAAG,SAAsB;6BAE7B,IAAI,EAAJ,wBAAI;wBACF,IAAI,GAAG,IAAA,SAAE,GAAE,CAAC;wBACZ,IAAI,GAAG,UAAG,OAAO,CAAC,GAAG,CAAC,GAAG,oBAAU,IAAI,CAAE,CAAC;wBAC9C,qBAAM,IAAA,MAAC,EAAC,gBAAS,IAAI,CAAE,EAAE,GAAG,CAAC,EAAA;;wBAA7B,SAA6B,CAAC;wBAE9B,qBAAM,IAAA,WAAI,EAAC,IAAI,EAAE,gBAAgB,EAAE,gBAAS,CAAC,aAAa,wBACrD,IAAI,KACP,IAAI,MAAA,IACJ,EAAA;;wBAHF,SAGE,CAAC;;;wBAGL,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;wBAEb,IAAA,YAAI,EAAC,GAAG,EAAE,IAAC,CAAC,OAAO,CAAC,CAAC;;;;;;KAExB;IAEK,KAAK;4DAAC,EAAkB,EAAE,GAAG;;gBAAX,EAAE,eAAA;;;4BACb,qBAAM,IAAA,MAAC,EAAC,eAAQ,EAAE,CAAE,CAAC,EAAA;;wBAA5B,IAAI,GAAG,SAAqB;wBAChC,sBAAO,IAAI,EAAC;;;;KACb;IAEK,OAAO;4DAAC,EAAsB,EAAE,GAAG;;gBAAjB,QAAQ,mBAAA;;;4BACtB,qBAAM,IAAA,eAAO,EAAC,QAAQ,CAAC,EAAA;;wBAA3B,CAAC,GAAG,SAAuB;wBAC/B,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC;wBACjB,qBAAM,IAAA,MAAC,EAAC,eAAQ,CAAC,CAAC,EAAE,CAAE,EAAE,CAAC,CAAC,EAAA;;wBAA1B,SAA0B,CAAC;wBAC3B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;;KACd;IAEK,OAAO;4DAAC,EAAsB,EAAE,GAAG;;gBAAjB,QAAQ,mBAAA;;;4BACtB,qBAAM,IAAA,eAAO,EAAC,QAAQ,CAAC,EAAA;;wBAA3B,CAAC,GAAG,SAAuB;wBAC/B,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC;wBACnB,qBAAM,IAAA,MAAC,EAAC,eAAQ,CAAC,CAAC,EAAE,CAAE,EAAE,CAAC,CAAC,EAAA;;wBAA1B,SAA0B,CAAC;wBAC3B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;;KACd;CACF,CAAC"} \ No newline at end of file diff --git a/routes/users.ts b/routes/users.ts new file mode 100644 index 00000000..17bea006 --- /dev/null +++ b/routes/users.ts @@ -0,0 +1,556 @@ +import { g, s, db } from "$lib/db"; +import config from "$config"; +import store from "$lib/store"; +import { + fields, + nada, + pick, + uniq, + wait, + bail, + fail, + getUser, +} from "$lib/utils"; +import jwt from "jsonwebtoken"; +import { authenticator } from "otplib"; +import whitelist from "$lib/whitelist"; +import { l, err, warn } from "$lib/logging"; +import { emit } from "$lib/sockets"; +import register from "$lib/register"; +import { requirePin } from "$lib/auth"; +import { v4 } from "uuid"; +import { parseISO } from "date-fns"; +import { types } from "$lib/payments"; +import { bech32 } from "bech32"; +import { mail, templates } from "$lib/mail"; + +import got from "got"; +import upload from "$lib/upload"; + +const { encode, decode, fromWords, toWords } = bech32; + +export default { + upload, + + async me({ user }, res) { + try { + user.balance = await g(`balance:${user.id}`); + user.prompt = !!user.prompt; + if (user.pubkey) + user.npub = encode("npub", toWords(Buffer.from(user.pubkey, "hex"))); + + res.send(pick(user, whitelist)); + } catch (e) { + console.log("problem fetching user", e); + res.code(500).send(e.message); + } + }, + + async list({ user }, res) { + if (!user.admin) fail("unauthorized"); + + let users = []; + + for await (let k of db.scanIterator({ MATCH: "balance:*" })) { + let uid = k.split(":")[1]; + let user = await getUser(uid); + + if (!user) { + await db.del(`balance:${uid}`); + continue; + } + + user.balance = await g(k); + + let payments = await db.lRange(`${uid}:payments`, 0, -1); + + let total = 0; + for (let pid of payments) { + let p = await g(`payment:${pid}`); + if (!p) continue; + total += p.amount; + if (p.amount < 0) + total -= (p.fee || 0) + (p.ourfee || 0) + (p.tip || 0); + else total += p.tip || 0; + } + + user.expected = total; + users.push(user); + } + + res.send(users); + }, + + async get({ params: { key } }, res) { + key = key.toLowerCase().replace(/\s/g, ""); + try { + if (key.startsWith("npub")) { + try { + key = Buffer.from(fromWords(decode(key).words)).toString("hex"); + } catch (e) {} + } + + let user = await getUser(key); + + if (!user && key.length === 64) { + user = { + currency: "USD", + username: key, + display: key.substr(0, 6), + pubkey: key, + anon: true, + }; + } + + if (!user) return res.code(500).send("User not found"); + + let whitelist = [ + "address", + "anon", + "banner", + "banner", + "currency", + "display", + "id", + "hidepay", + "memoPrompt", + "npub", + "profile", + "prompt", + "pubkey", + "username", + ]; + + if (user.pubkey) + user.npub = encode("npub", toWords(Buffer.from(user.pubkey, "hex"))); + user.prompt = !!user.prompt; + + res.send(pick(user, whitelist)); + } catch (e) { + err("problem getting user", e.message); + res.code(500).send(e.message); + } + }, + + async create({ body, headers }, res) { + try { + const ip = headers["cf-connecting-ip"]; + if (!body.user) fail("no user object provided"); + let { user } = body; + let fields = [ + "profile", + "cipher", + "pubkey", + "password", + "username", + "salt", + ]; + + user = await register(pick(user, fields), ip, false); + l("registered new user", user.username); + res.send(pick(user, whitelist)); + } catch (e) { + res.code(500).send(e.message); + } + }, + + async disable2fa({ user, body: { token } }, res) { + let { id, twofa, username, otpsecret } = user; + if (twofa && !authenticator.check(token, otpsecret)) { + return res.code(401).send("2fa required"); + } + + user.twofa = false; + await s(`user:${id}`, user); + emit(username, "user", user); + emit(username, "otpsecret", user.otpsecret); + l("disabled 2fa", username); + res.send({}); + }, + + async enable2fa({ user, body: { token } }, res) { + let { id, otpsecret, username } = user; + const isValid = authenticator.check(token, otpsecret); + if (isValid) { + user.twofa = true; + await s(`user:${id}`, user); + emit(username, "user", user); + } else { + return res.code(500).send("Invalid token"); + } + + l("enabled 2fa", username); + res.send({}); + }, + + async update({ user, body }, res) { + try { + l("updating user", user.username); + + let { + profile, + banner, + confirm, + password, + pin, + newpin, + pubkey, + username, + shopifyToken, + shopifyStore, + } = body; + + if (user.pin && !(pin === user.pin)) fail("Pin required"); + if (typeof newpin !== "undefined" && newpin.length === 6) + user.pin = newpin; + if (user.pin === "delete") delete user.pin; + + let exists; + if (username) exists = await getUser(username); + + let token, oldname; + if (user.username.toLowerCase() !== username.toLowerCase() && exists) { + err("username taken", username, user.username, exists.username); + fail("Username taken"); + } else if (username) { + if (user.username.toLowerCase() !== username.toLowerCase()) + l("changing username", user.username, username); + + await db.del(`user:${user.username}`); + oldname = user.username; + user.username = username; + } + + if (pubkey) exists = await getUser(pubkey); + if (exists && ![oldname, username].includes(exists.username)) { + warn("key in use", pubkey, exists.username); + if (exists.anon) await db.del(`user:${pubkey}`); + else fail("Key in use by another account"); + } + + let attributes = [ + "address", + "autowithdraw", + "banner", + "cipher", + "currencies", + "currency", + "destination", + "display", + "email", + "language", + "fiat", + "locktime", + "memoPrompt", + "nip5", + "notify", + "nsec", + "prompt", + "profile", + "pubkey", + "reserve", + "salt", + "seed", + "threshold", + "tokens", + "twofa", + "shopifyToken", + "shopifyStore", + ]; + + for (let a of attributes) { + if (typeof body[a] !== "undefined") user[a] = body[a]; + } + + if (password && password === confirm) { + user.password = await Bun.password.hash(password, { + algorithm: "bcrypt", + cost: 1, + }); + } + + user.haspin = !!user.pin; + await s(`user:${user.pubkey}`, user.id); + await s( + `user:${user.username.toLowerCase().replace(/\s/g, "")}`, + user.id, + ); + + await s(`user:${user.id}`, user); + if (user.nip5) await db.sAdd("nip5", `${user.username}:${user.pubkey}`); + + emit(user.id, "user", user); + res.send({ user, token }); + } catch (e) { + warn("failed to update", user.username, e.message); + bail(res, e.message); + } + }, + + async login(req, res) { + try { + let { username, password, token: twofa } = req.body; + + if (username !== "coinos" && username !== "funk") + l("logging in", username, req.headers["cf-connecting-ip"]); + + username = username.toLowerCase().replace(/\s/g, ""); + let user = await getUser(username); + + if ( + !user || + (user.password && + !( + (config.adminpass && password === config.adminpass) || + (await Bun.password.verify(password, user.password)) + )) + ) { + warn("invalid username or password attempt", username); + return res.code(401).send({}); + } + + if ( + !(config.adminpass && password === config.adminpass) && + user.twofa && + (typeof twofa === "undefined" || + !authenticator.check(twofa, user.otpsecret)) + ) { + return res.code(401).send("2fa required"); + } + + if (username !== "coinos" && username !== "funk") + l("logged in", username); + + let payload = { id: user.id }; + let token = jwt.sign(payload, config.jwt); + res.cookie("token", token, { expires: new Date(Date.now() + 432000000) }); + user = pick(user, whitelist); + res.send({ user, token }); + } catch (e) { + err("login error", e.message, req.socket.remoteAddress); + res.code(401).send({}); + } + }, + + async subscribe({ body, user }, res) { + let { subscriptions } = user; + let { subscription } = body; + if (!subscriptions) subscriptions = []; + if ( + !subscriptions.find( + (s) => JSON.stringify(s) === JSON.stringify(subscription), + ) + ) + subscriptions.push(subscription); + user.subscriptions = subscriptions; + l("subscribing", user.username); + await user.save(); + res.sendStatus(201); + }, + + async password({ body: { password }, user }, res) { + if (!user.password) return res.send(true); + + try { + if (!password) fail("password not provided"); + res.send(await Bun.password.verify(password, user.password)); + } catch (e) { + bail(res, e.message); + } + }, + + async pin({ body: { pin }, user }, res) { + res.send(!user.pin || user.pin === pin); + }, + + async otpsecret(req, res) { + try { + await requirePin(req); + let { otpsecret, username } = req.user; + res.send({ secret: otpsecret, username }); + } catch (e) { + res.code(500).send(e.message); + } + }, + + async contacts({ user: { id } }, res) { + let lastlen = (await g(`${id}:lastlen`)) || 0; + let len = await db.lLen(`${id}:payments`); + let payments = (await db.lRange(`${id}:payments`, 0, len - lastlen)) || []; + await db.set(`${id}:lastlen`, len); + + let contacts = (await g(`${id}:contacts`)) || []; + + for (let { ref } of ( + await Promise.all( + payments.reverse().map(async (id) => await g(`payment:${id}`)), + ) + ).filter((p) => p && p.type === types.internal && p.ref)) { + let i = contacts.findIndex((c) => c && c.id === ref); + if (~i) contacts.splice(i, 1); + let u = await g(`user:${ref}`); + if (typeof u === "string") u = await g(`user:${ref}`); + if (u) contacts.unshift(pick(u, ["id", "profile", "username"])); + } + + await s(`${id}:contacts`, contacts); + + res.send(contacts); + }, + + async del({ params: { username }, headers: { authorization } }, res) { + username = username.toLowerCase(); + if (!(authorization && authorization.includes(config.admin))) + return res.code(401).send("unauthorized"); + + let { id, pubkey } = await g( + `user:${await g(`user:${username.replace(/\s/g, "").toLowerCase()}`)}`, + ); + let invoices = await db.lRange(`${id}:invoices`, 0, -1); + let payments = await db.lRange(`${id}:payments`, 0, -1); + + for (let { id } of invoices) db.del(`invoice:${id}`); + for (let { id } of payments) db.del(`payment:${id}`); + db.del(`user:${username.toLowerCase()}`); + db.del(`user:${id}`); + db.del(`user:${pubkey}`); + + res.send({}); + }, + + async reset({ body: { code, username, password }, user: u }, res) { + let admin = u && u.admin; + let id, user; + + if (admin) { + id = await g(`user:${username.toLowerCase().replace(/\s/g, "")}`); + user = await g(`user:${id}`); + } else { + id = await g(`reset:${code}`); + user = await g(`user:${id}`); + } + + if (!user) fail("user not found"); + + try { + user.pin = null; + user.pubkey = null; + user.cipher = null; + user.salt = null; + user.password = await Bun.password.hash(password, { + algorithm: "bcrypt", + cost: 1, + }); + + await s(`user:${id}`, user); + await db.del(`reset:${code}`); + + res.send(pick(user, whitelist)); + } catch (e) { + bail(res, e.message); + } + }, + + async printerlogin({ body: { username, topic } }, res) { + if (username === topic) res.send({ ok: true }); + else bail(res, "unauthorized"); + }, + + async acl({ body: { username, topic } }, res) { + if (username === topic) res.send({ ok: true }); + else bail(res, "unauthorized"); + }, + + async superuser({ body: { username } }, res) { + if (username === config.mqtt2.username) res.send({ ok: true }); + else bail(res, "unauthorized"); + }, + + async request({ body: { id, email } }, res) { + try { + let user = await g(`user:${id}`); + + if (!user) fail("user not found"); + if (await g(`email:${email.toLowerCase()}`)) fail("Email already in use"); + + let { username } = user; + + if (email !== user.email) { + user.verified = false; + await s(`user:${id}`, user); + user.email = email; + } + + let code = v4(); + await s(`verify:${code}`, { id, email }); + let link = `${process.env.URL}/${username}/verify/${code}`; + let subject = "Email Verification"; + + await mail(user, subject, templates.verifyEmail, { + username, + link, + }); + + res.send({ ok: true }); + } catch (e) { + bail(res, e.message); + } + }, + + async verify({ params: { code } }, res) { + try { + let { id, email } = await g(`verify:${code}`); + if (!id) fail(res, "verification failed"); + let user = await g(`user:${id}`); + user.email = email; + user.verified = true; + await s(`user:${id}`, user); + await s(`email:${email.toLowerCase()}`, id); + + res.send(pick(user, whitelist)); + } catch (e) { + bail(res, e.message); + } + }, + + async forgot({ body: { email } }, res) { + try { + let uid = await g(`email:${email.toLowerCase()}`); + let user = await g(`user:${uid}`); + + if (user) { + let code = v4(); + let link = `${process.env.URL}/reset/${code}`; + await s(`reset:${code}`, uid); + + await mail(user, "Password reset", templates.passwordReset, { + ...user, + link, + }); + } + + res.send({}); + } catch (e) { + bail(res, e.message); + } + }, + + async items({ params: { id } }, res) { + let item = await g(`item:${id}`); + return item; + }, + + async hidepay({ body: { username } }, res) { + let u = await getUser(username); + u.hidepay = true; + await s(`user:${u.id}`, u); + res.send({}); + }, + + async unlimit({ body: { username } }, res) { + let u = await getUser(username); + u.unlimited = true; + await s(`user:${u.id}`, u); + res.send({}); + }, +}; -- cgit v1.2.3