From bec36602b55893ffb635ddad73b5a17e14408ebe Mon Sep 17 00:00:00 2001 From: Danny Morabito Date: Tue, 3 Dec 2024 18:33:15 +0100 Subject: [PATCH] Code cleanup/rewrite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ’ฅ Breaking Changes ๐Ÿ’ฅ - Ditched Bun for Deno ๐Ÿฆ•: Migrated from Bun to Deno due to recurring memory leaks and crashes on our test server. - SQL Simplification ๐Ÿ“ˆ: Removed Prisma and now using Libsql alone - Hono Takes the Stage: Switched from Elysia to Hono, a cleaner and more compatible framework that plays nice with Deno. ๐Ÿงน Code Cleanup ๐Ÿ’ช Removed unnecessary clutter and streamlined the codebase for better readability and maintainability. --- bun.lockb | Bin 56363 -> 0 bytes deno.json | 19 + deno.lock | 733 ++++++++++++++++++ package.json | 34 - .../20241125122247_init/migration.sql | 29 - .../migration.sql | 10 - prisma/migrations/migration_lock.toml | 3 - prisma/schema.prisma | 28 - src/httpServer.ts | 420 +++++----- src/index.ts | 52 +- src/models.ts | 113 +++ src/smtpServer.ts | 183 +++-- src/utils/index.ts | 30 +- src/utils/logs.ts | 12 +- 14 files changed, 1235 insertions(+), 431 deletions(-) delete mode 100755 bun.lockb create mode 100644 deno.json create mode 100644 deno.lock delete mode 100644 package.json delete mode 100644 prisma/migrations/20241125122247_init/migration.sql delete mode 100644 prisma/migrations/20241126193747_remove_mail_queue/migration.sql delete mode 100644 prisma/migrations/migration_lock.toml delete mode 100644 prisma/schema.prisma create mode 100644 src/models.ts diff --git a/bun.lockb b/bun.lockb deleted file mode 100755 index af68f95ea97d559766114681b03bd81e5bc482df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56363 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 XIPPVGlkiI48xf6|6I&4b|NH-6>6l%c diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..a70a678 --- /dev/null +++ b/deno.json @@ -0,0 +1,19 @@ +{ + "nodeModulesDir": "auto", + "tasks": { + "dev": "deno run --allow-all --watch src/index.ts" + }, + "imports": { + "@cashu/cashu-ts": "npm:@cashu/cashu-ts@^2.0.0", + "@libsql/client": "npm:@libsql/client", + "@nostr-dev-kit/ndk": "npm:@nostr-dev-kit/ndk@^2.10.7", + "@std/bytes": "jsr:@std/bytes@^1.0.4", + "@std/dotenv": "jsr:@std/dotenv@^0.225.2", + "@std/encoding": "jsr:@std/encoding@^1.0.5", + "nostr-tools": "jsr:@nostr/tools@^2.10.4", + "@std/assert": "jsr:@std/assert@1", + "@arx/utils": "https://git.arx-ccn.com/Arx/ts-utils/raw/commit/c1d309ba097ada64cd072ec1e3e97edaaf8773b6/src/index.ts", + "smtp-server": "npm:smtp-server@^3.13.6", + "winston": "npm:winston@^3.17.0" + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..4154e3b --- /dev/null +++ b/deno.lock @@ -0,0 +1,733 @@ +{ + "version": "4", + "specifiers": { + "jsr:@nostr/tools@^2.10.4": "2.10.4", + "jsr:@std/assert@1": "1.0.8", + "jsr:@std/bytes@^1.0.4": "1.0.4", + "jsr:@std/dotenv@~0.225.2": "0.225.2", + "jsr:@std/encoding@^1.0.5": "1.0.5", + "jsr:@std/internal@^1.0.5": "1.0.5", + "npm:@cashu/cashu-ts@2": "2.0.0", + "npm:@libsql/client@*": "0.14.0", + "npm:@noble/ciphers@~0.5.1": "0.5.3", + "npm:@noble/curves@1.2.0": "1.2.0", + "npm:@noble/hashes@1.3.1": "1.3.1", + "npm:@nostr-dev-kit/ndk@^2.10.7": "2.10.7", + "npm:@scure/base@1.1.1": "1.1.1", + "npm:@scure/bip32@1.3.1": "1.3.1", + "npm:@scure/bip39@1.2.1": "1.2.1", + "npm:nostr-wasm@0.1.0": "0.1.0", + "npm:smtp-server@^3.13.6": "3.13.6", + "npm:winston@^3.17.0": "3.17.0" + }, + "jsr": { + "@nostr/tools@2.10.4": { + "integrity": "7fda015c96b4f674727843aecb990e2af1989e4724588415ccf6f69066abfd4f", + "dependencies": [ + "npm:@noble/ciphers", + "npm:@noble/curves", + "npm:@noble/hashes", + "npm:@scure/base", + "npm:@scure/bip32", + "npm:@scure/bip39", + "npm:nostr-wasm" + ] + }, + "@std/assert@1.0.8": { + "integrity": "ebe0bd7eb488ee39686f77003992f389a06c3da1bbd8022184804852b2fa641b", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/bytes@1.0.4": { + "integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc" + }, + "@std/dotenv@0.225.2": { + "integrity": "e2025dce4de6c7bca21dece8baddd4262b09d5187217e231b033e088e0c4dd23" + }, + "@std/encoding@1.0.5": { + "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04" + }, + "@std/internal@1.0.5": { + "integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba" + } + }, + "npm": { + "@cashu/cashu-ts@2.0.0": { + "integrity": "sha512-neVWZGviQGFf2RlpVpEerf8zQZDR4HvzmDj58gsae1gOQxzaZoU9BdAyRjVpcvz/dPTYQKZii9mUTAgR+fof2w==", + "dependencies": [ + "@cashu/crypto", + "@noble/curves@1.7.0", + "@noble/hashes@1.6.1", + "@scure/bip32@1.6.0", + "buffer" + ] + }, + "@cashu/crypto@0.3.4": { + "integrity": "sha512-mfv1Pj4iL1PXzUj9NKIJbmncCLMqYfnEDqh/OPxAX0nNBt6BOnVJJLjLWFlQeYxlnEfWABSNkrqPje1t5zcyhA==", + "dependencies": [ + "@noble/curves@1.7.0", + "@noble/hashes@1.6.1", + "@scure/bip32@1.6.0", + "@scure/bip39@1.5.0", + "buffer" + ] + }, + "@colors/colors@1.6.0": { + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==" + }, + "@dabh/diagnostics@2.0.3": { + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": [ + "colorspace", + "enabled", + "kuler" + ] + }, + "@libsql/client@0.14.0": { + "integrity": "sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q==", + "dependencies": [ + "@libsql/core", + "@libsql/hrana-client", + "js-base64", + "libsql", + "promise-limit" + ] + }, + "@libsql/core@0.14.0": { + "integrity": "sha512-nhbuXf7GP3PSZgdCY2Ecj8vz187ptHlZQ0VRc751oB2C1W8jQUXKKklvt7t1LJiUTQBVJuadF628eUk+3cRi4Q==", + "dependencies": [ + "js-base64" + ] + }, + "@libsql/darwin-arm64@0.4.7": { + "integrity": "sha512-yOL742IfWUlUevnI5PdnIT4fryY3LYTdLm56bnY0wXBw7dhFcnjuA7jrH3oSVz2mjZTHujxoITgAE7V6Z+eAbg==" + }, + "@libsql/darwin-x64@0.4.7": { + "integrity": "sha512-ezc7V75+eoyyH07BO9tIyJdqXXcRfZMbKcLCeF8+qWK5nP8wWuMcfOVywecsXGRbT99zc5eNra4NEx6z5PkSsA==" + }, + "@libsql/hrana-client@0.7.0": { + "integrity": "sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==", + "dependencies": [ + "@libsql/isomorphic-fetch", + "@libsql/isomorphic-ws", + "js-base64", + "node-fetch" + ] + }, + "@libsql/isomorphic-fetch@0.3.1": { + "integrity": "sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==" + }, + "@libsql/isomorphic-ws@0.1.5": { + "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", + "dependencies": [ + "@types/ws", + "ws" + ] + }, + "@libsql/linux-arm64-gnu@0.4.7": { + "integrity": "sha512-WlX2VYB5diM4kFfNaYcyhw5y+UJAI3xcMkEUJZPtRDEIu85SsSFrQ+gvoKfcVh76B//ztSeEX2wl9yrjF7BBCA==" + }, + "@libsql/linux-arm64-musl@0.4.7": { + "integrity": "sha512-6kK9xAArVRlTCpWeqnNMCoXW1pe7WITI378n4NpvU5EJ0Ok3aNTIC2nRPRjhro90QcnmLL1jPcrVwO4WD1U0xw==" + }, + "@libsql/linux-x64-gnu@0.4.7": { + "integrity": "sha512-CMnNRCmlWQqqzlTw6NeaZXzLWI8bydaXDke63JTUCvu8R+fj/ENsLrVBtPDlxQ0wGsYdXGlrUCH8Qi9gJep0yQ==" + }, + "@libsql/linux-x64-musl@0.4.7": { + "integrity": "sha512-nI6tpS1t6WzGAt1Kx1n1HsvtBbZ+jHn0m7ogNNT6pQHZQj7AFFTIMeDQw/i/Nt5H38np1GVRNsFe99eSIMs9XA==" + }, + "@libsql/win32-x64-msvc@0.4.7": { + "integrity": "sha512-7pJzOWzPm6oJUxml+PCDRzYQ4A1hTMHAciTAHfFK4fkbDZX33nWPVG7Y3vqdKtslcwAzwmrNDc6sXy2nwWnbiw==" + }, + "@neon-rs/load@0.0.4": { + "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==" + }, + "@noble/ciphers@0.5.3": { + "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==" + }, + "@noble/curves@1.1.0": { + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "dependencies": [ + "@noble/hashes@1.3.1" + ] + }, + "@noble/curves@1.2.0": { + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": [ + "@noble/hashes@1.3.2" + ] + }, + "@noble/curves@1.7.0": { + "integrity": "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==", + "dependencies": [ + "@noble/hashes@1.6.0" + ] + }, + "@noble/hashes@1.3.1": { + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==" + }, + "@noble/hashes@1.3.2": { + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==" + }, + "@noble/hashes@1.6.0": { + "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==" + }, + "@noble/hashes@1.6.1": { + "integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==" + }, + "@noble/secp256k1@2.1.0": { + "integrity": "sha512-XLEQQNdablO0XZOIniFQimiXsZDNwaYgL96dZwC54Q30imSbAOFf3NKtepc+cXyuZf5Q1HCgbqgZ2UFFuHVcEw==" + }, + "@nostr-dev-kit/ndk@2.10.7": { + "integrity": "sha512-cylva8jsaAGMijxAI32CnJWlzvwD4sWyl86/+RMS6xpZn4MIgeVUfBFc/pYkcfZzDP3v1Z9mIPsuiICRyvu9yQ==", + "dependencies": [ + "@noble/curves@1.7.0", + "@noble/hashes@1.6.1", + "@noble/secp256k1", + "@scure/base@1.2.1", + "debug@4.3.7", + "light-bolt11-decoder", + "nostr-tools", + "tseep", + "typescript-lru-cache", + "utf8-buffer", + "websocket-polyfill" + ] + }, + "@scure/base@1.1.1": { + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==" + }, + "@scure/base@1.2.1": { + "integrity": "sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==" + }, + "@scure/bip32@1.3.1": { + "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "dependencies": [ + "@noble/curves@1.1.0", + "@noble/hashes@1.3.2", + "@scure/base@1.1.1" + ] + }, + "@scure/bip32@1.6.0": { + "integrity": "sha512-82q1QfklrUUdXJzjuRU7iG7D7XiFx5PHYVS0+oeNKhyDLT7WPqs6pBcM2W5ZdwOwKCwoE1Vy1se+DHjcXwCYnA==", + "dependencies": [ + "@noble/curves@1.7.0", + "@noble/hashes@1.6.1", + "@scure/base@1.2.1" + ] + }, + "@scure/bip39@1.2.1": { + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "dependencies": [ + "@noble/hashes@1.3.2", + "@scure/base@1.1.1" + ] + }, + "@scure/bip39@1.5.0": { + "integrity": "sha512-Dop+ASYhnrwm9+HA/HwXg7j2ZqM6yk2fyLWb5znexjctFY3+E+eU8cIWI0Pql0Qx4hPZCijlGq4OL71g+Uz30A==", + "dependencies": [ + "@noble/hashes@1.6.1", + "@scure/base@1.2.1" + ] + }, + "@types/node@22.5.4": { + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dependencies": [ + "undici-types" + ] + }, + "@types/triple-beam@1.3.5": { + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, + "@types/ws@8.5.13": { + "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", + "dependencies": [ + "@types/node" + ] + }, + "async@3.2.6": { + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "base32.js@0.1.0": { + "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==" + }, + "base64-js@1.5.1": { + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "buffer@6.0.3": { + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dependencies": [ + "base64-js", + "ieee754" + ] + }, + "bufferutil@4.0.8": { + "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "dependencies": [ + "node-gyp-build" + ] + }, + "color-convert@1.9.3": { + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": [ + "color-name" + ] + }, + "color-name@1.1.3": { + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "color-string@1.9.1": { + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": [ + "color-name", + "simple-swizzle" + ] + }, + "color@3.2.1": { + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": [ + "color-convert", + "color-string" + ] + }, + "colorspace@1.1.4": { + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": [ + "color", + "text-hex" + ] + }, + "d@1.0.2": { + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dependencies": [ + "es5-ext", + "type" + ] + }, + "data-uri-to-buffer@4.0.1": { + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" + }, + "debug@2.6.9": { + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": [ + "ms@2.0.0" + ] + }, + "debug@4.3.7": { + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": [ + "ms@2.1.3" + ] + }, + "detect-libc@2.0.2": { + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==" + }, + "enabled@2.0.0": { + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "es5-ext@0.10.64": { + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "dependencies": [ + "es6-iterator", + "es6-symbol", + "esniff", + "next-tick" + ] + }, + "es6-iterator@2.0.3": { + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": [ + "d", + "es5-ext", + "es6-symbol" + ] + }, + "es6-symbol@3.1.4": { + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dependencies": [ + "d", + "ext" + ] + }, + "esniff@2.0.1": { + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": [ + "d", + "es5-ext", + "event-emitter", + "type" + ] + }, + "event-emitter@0.3.5": { + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": [ + "d", + "es5-ext" + ] + }, + "ext@1.7.0": { + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": [ + "type" + ] + }, + "fecha@4.2.3": { + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "fetch-blob@3.2.0": { + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dependencies": [ + "node-domexception", + "web-streams-polyfill" + ] + }, + "fn.name@1.1.0": { + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "formdata-polyfill@4.0.10": { + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": [ + "fetch-blob" + ] + }, + "ieee754@1.2.1": { + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "inherits@2.0.4": { + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipv6-normalize@1.0.1": { + "integrity": "sha512-Bm6H79i01DjgGTCWjUuCjJ6QDo1HB96PT/xCYuyJUP9WFbVDrLSbG4EZCvOCun2rNswZb0c3e4Jt/ws795esHA==" + }, + "is-arrayish@0.3.2": { + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "is-stream@2.0.1": { + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "is-typedarray@1.0.0": { + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "js-base64@3.7.7": { + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" + }, + "kuler@2.0.0": { + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "libsql@0.4.7": { + "integrity": "sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==", + "dependencies": [ + "@libsql/darwin-arm64", + "@libsql/darwin-x64", + "@libsql/linux-arm64-gnu", + "@libsql/linux-arm64-musl", + "@libsql/linux-x64-gnu", + "@libsql/linux-x64-musl", + "@libsql/win32-x64-msvc", + "@neon-rs/load", + "detect-libc" + ] + }, + "light-bolt11-decoder@3.2.0": { + "integrity": "sha512-3QEofgiBOP4Ehs9BI+RkZdXZNtSys0nsJ6fyGeSiAGCBsMwHGUDS/JQlY/sTnWs91A2Nh0S9XXfA8Sy9g6QpuQ==", + "dependencies": [ + "@scure/base@1.1.1" + ] + }, + "logform@2.7.0": { + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "dependencies": [ + "@colors/colors", + "@types/triple-beam", + "fecha", + "ms@2.1.3", + "safe-stable-stringify", + "triple-beam" + ] + }, + "ms@2.0.0": { + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "ms@2.1.3": { + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "next-tick@1.1.0": { + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node-domexception@1.0.0": { + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, + "node-fetch@3.3.2": { + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": [ + "data-uri-to-buffer", + "fetch-blob", + "formdata-polyfill" + ] + }, + "node-gyp-build@4.8.4": { + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==" + }, + "nodemailer@6.9.15": { + "integrity": "sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==" + }, + "nostr-tools@2.10.4": { + "integrity": "sha512-biU7sk+jxHgVASfobg2T5ttxOGGSt69wEVBC51sHHOEaKAAdzHBLV/I2l9Rf61UzClhliZwNouYhqIso4a3HYg==", + "dependencies": [ + "@noble/ciphers", + "@noble/curves@1.2.0", + "@noble/hashes@1.3.1", + "@scure/base@1.1.1", + "@scure/bip32@1.3.1", + "@scure/bip39@1.2.1", + "nostr-wasm" + ] + }, + "nostr-wasm@0.1.0": { + "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==" + }, + "one-time@1.0.0": { + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": [ + "fn.name" + ] + }, + "promise-limit@2.7.0": { + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==" + }, + "punycode.js@2.3.1": { + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==" + }, + "readable-stream@3.6.2": { + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": [ + "inherits", + "string_decoder", + "util-deprecate" + ] + }, + "safe-buffer@5.2.1": { + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-stable-stringify@2.5.0": { + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" + }, + "simple-swizzle@0.2.2": { + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": [ + "is-arrayish" + ] + }, + "smtp-server@3.13.6": { + "integrity": "sha512-dqbSPKn3PCq3Gp5hxBM99u7PET7cQSAWrauhtArJbc+zrf5xNEOjm9+Ob3lySySrRoIEvNE0dz+w2H/xWFJNRw==", + "dependencies": [ + "base32.js", + "ipv6-normalize", + "nodemailer", + "punycode.js" + ] + }, + "stack-trace@0.0.10": { + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==" + }, + "string_decoder@1.3.0": { + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": [ + "safe-buffer" + ] + }, + "text-hex@1.0.0": { + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "triple-beam@1.4.1": { + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==" + }, + "tseep@1.3.1": { + "integrity": "sha512-ZPtfk1tQnZVyr7BPtbJ93qaAh2lZuIOpTMjhrYa4XctT8xe7t4SAW9LIxrySDuYMsfNNayE51E/WNGrNVgVicQ==" + }, + "tstl@2.5.16": { + "integrity": "sha512-+O2ybLVLKcBwKm4HymCEwZIT0PpwS3gCYnxfSDEjJEKADvIFruaQjd3m7CAKNU1c7N3X3WjVz87re7TA2A5FUw==" + }, + "type@2.7.3": { + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==" + }, + "typedarray-to-buffer@3.1.5": { + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": [ + "is-typedarray" + ] + }, + "typescript-lru-cache@2.0.0": { + "integrity": "sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA==" + }, + "undici-types@6.19.8": { + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "utf-8-validate@5.0.10": { + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "dependencies": [ + "node-gyp-build" + ] + }, + "utf8-buffer@1.0.0": { + "integrity": "sha512-ueuhzvWnp5JU5CiGSY4WdKbiN/PO2AZ/lpeLiz2l38qwdLy/cW40XobgyuIWucNyum0B33bVB0owjFCeGBSLqg==" + }, + "util-deprecate@1.0.2": { + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "web-streams-polyfill@3.3.3": { + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" + }, + "websocket-polyfill@0.0.3": { + "integrity": "sha512-pF3kR8Uaoau78MpUmFfzbIRxXj9PeQrCuPepGE6JIsfsJ/o/iXr07Q2iQNzKSSblQJ0FiGWlS64N4pVSm+O3Dg==", + "dependencies": [ + "tstl", + "websocket" + ] + }, + "websocket@1.0.35": { + "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==", + "dependencies": [ + "bufferutil", + "debug@2.6.9", + "es5-ext", + "typedarray-to-buffer", + "utf-8-validate", + "yaeti" + ] + }, + "winston-transport@4.9.0": { + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "dependencies": [ + "logform", + "readable-stream", + "triple-beam" + ] + }, + "winston@3.17.0": { + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "dependencies": [ + "@colors/colors", + "@dabh/diagnostics", + "async", + "is-stream", + "logform", + "one-time", + "readable-stream", + "safe-stable-stringify", + "stack-trace", + "triple-beam", + "winston-transport" + ] + }, + "ws@8.18.0": { + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==" + }, + "yaeti@0.0.6": { + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==" + } + }, + "remote": { + "https://deno.land/x/hono@v4.3.11/adapter/deno/serve-static.ts": "db226d30f08f1a8bb77653ead42a911357b2f8710d653e43c01eccebb424b295", + "https://deno.land/x/hono@v4.3.11/client/client.ts": "dcda3887257fa3164db7b32c56665c6e757f0ef047a14f3f9599ef41725c1525", + "https://deno.land/x/hono@v4.3.11/client/index.ts": "30def535310a37bede261f1b23d11a9758983b8e9d60a6c56309cee5f6746ab2", + "https://deno.land/x/hono@v4.3.11/client/utils.ts": "8be84b49c5c7952666875a8e901fde3044c85c853ea6ba3a7e2c0468478459c0", + "https://deno.land/x/hono@v4.3.11/compose.ts": "37d6e33b7db80e4c43a0629b12fd3a1e3406e7d9e62a4bfad4b30426ea7ae4f1", + "https://deno.land/x/hono@v4.3.11/context.ts": "facfd749d823a645039571d66d9d228f5ae6836818b65d3b6c4c6891adfe071e", + "https://deno.land/x/hono@v4.3.11/helper/adapter/index.ts": "ff7e11eb1ca1fbd74ca3c46cd1d24014582f91491ef6d3846d66ed1cede18ec4", + "https://deno.land/x/hono@v4.3.11/helper/cookie/index.ts": "689c84eae410f0444a4598f136a4f859b9122ec6f790dff74412d34405883db8", + "https://deno.land/x/hono@v4.3.11/helper/html/index.ts": "48a0ddc576c10452db6c3cab03dd4ee6986ab61ebdc667335b40a81fa0487f69", + "https://deno.land/x/hono@v4.3.11/hono-base.ts": "fd7e9c1bba1e13119e95158270011784da3a7c3014c149ba0700e700f840ae0d", + "https://deno.land/x/hono@v4.3.11/hono.ts": "23edd0140bf0bd5a68c14ae96e5856a5cec6b844277e853b91025e91ea74f416", + "https://deno.land/x/hono@v4.3.11/http-exception.ts": "f5dd375e61aa4b764eb9b99dd45a7160f8317fd36d3f79ae22585b9a5e8ad7c5", + "https://deno.land/x/hono@v4.3.11/jsx/base.ts": "33f1c302c8f72ae948abd9c3ef85f4b3be6525251a13b95fd18fe2910b7d4a0d", + "https://deno.land/x/hono@v4.3.11/jsx/children.ts": "26ead0f151faba5307883614b5b064299558f06798c695c432f32acbb1127d56", + "https://deno.land/x/hono@v4.3.11/jsx/components.ts": "f79ab215f59388f01a69e2d6ec0b841fd3b42ba38e0ee7c93a525cdf06e159f9", + "https://deno.land/x/hono@v4.3.11/jsx/constants.ts": "984e0797194be1fbc935cb688c8d0a60c112b21bc59301be5354c02232f18820", + "https://deno.land/x/hono@v4.3.11/jsx/context.ts": "2b7a86e6b35da171fab27aa05f09748bb3eba64b26c037ea1da655c07e8f6bc1", + "https://deno.land/x/hono@v4.3.11/jsx/dom/components.ts": "733da654edb3d4c178a4479649fac2c64e79069e37e848add0c3a49f90e7f2d7", + "https://deno.land/x/hono@v4.3.11/jsx/dom/context.ts": "06209d14553398750c69252cc826082018cefa277f5c82cbe58d7261c8a2d81e", + "https://deno.land/x/hono@v4.3.11/jsx/dom/jsx-dev-runtime.ts": "ba87562d14b77dd5f2a3cc30d41b1eb5edb0800e5f4a7337b5b87b2e66f8a099", + "https://deno.land/x/hono@v4.3.11/jsx/dom/jsx-runtime.ts": "6a50a65306771a9000030f494d92a5fdeeb055112e0126234b2fd9179de1d4f5", + "https://deno.land/x/hono@v4.3.11/jsx/dom/render.ts": "7db816d40de58c60e1cbdab64ac3f170b1e30696ed61ad449bbb823f60b46146", + "https://deno.land/x/hono@v4.3.11/jsx/dom/utils.ts": "5d3e8c14996902db9c1223041fb21480fa0e921a4ccdc59f8c7571c08b7810f2", + "https://deno.land/x/hono@v4.3.11/jsx/hooks/index.ts": "b7e0f0a754f31a1e1fbe0ac636b38b031603eb0ae195c32a30769a11d79fb871", + "https://deno.land/x/hono@v4.3.11/jsx/index.ts": "fe3e582c2a4e24e5f8b6027925bddccaae0283747d8f0161eb6f5a34616edd11", + "https://deno.land/x/hono@v4.3.11/jsx/streaming.ts": "5e5dde9a546041353b9a3860fc9020471f762813f10e1290009ab6bd40e7bdcf", + "https://deno.land/x/hono@v4.3.11/jsx/types.ts": "51c2bdbb373860e2570ad403546a7fdbbb1cf00a47ce7ed10b2aece922031ac4", + "https://deno.land/x/hono@v4.3.11/jsx/utils.ts": "4b8299d402ba5395472c552d1fe3297ee60112bfc32e0ef86cfe8e40086f7d54", + "https://deno.land/x/hono@v4.3.11/middleware.ts": "2e7c6062e36b0e5f84b44a62e7b0e1cef33a9827c19937c648be4b63e1b7d7c6", + "https://deno.land/x/hono@v4.3.11/middleware/basic-auth/index.ts": "2c8cb563f3b89df1a7a2232be37377c3df6194af38613dc0a823c6595816fc66", + "https://deno.land/x/hono@v4.3.11/middleware/bearer-auth/index.ts": "b3b7469bc0eb9543c6c47f3ff67de879210dd73063307a61536042ff30e8720e", + "https://deno.land/x/hono@v4.3.11/middleware/body-limit/index.ts": "3fefeaf7e6e576aa1b33f2694072d2eaab692842acd29cb360d98e20eebfe5aa", + "https://deno.land/x/hono@v4.3.11/middleware/cache/index.ts": "5e6273e5c9ea73ef387b25923ab23274c220b29d7c981b62ac0be26d6a1aa3d8", + "https://deno.land/x/hono@v4.3.11/middleware/compress/index.ts": "98c403a5fe7e9c5f5d776350b422b0a125fb34696851b8b14f825b9b7b06f2ac", + "https://deno.land/x/hono@v4.3.11/middleware/cors/index.ts": "976eb9ce8cefc214b403a2939503a13177cec76223274609a07ca554e0dc623b", + "https://deno.land/x/hono@v4.3.11/middleware/csrf/index.ts": "077bb0ce299d79d0d232cb9e462aaa4eaa901164f1310f74a7630f7e6cfe74e8", + "https://deno.land/x/hono@v4.3.11/middleware/etag/index.ts": "95e0270ea349cf00537ee6e58985a4cc7dba44091ca8e2dc42b6d8b2f01bcfe7", + "https://deno.land/x/hono@v4.3.11/middleware/jsx-renderer/index.ts": "229322c66ebc7f426cd2d71f282438025b4ee7ce8cb8e97e87c7efbc94530c19", + "https://deno.land/x/hono@v4.3.11/middleware/jwt/index.ts": "fce4e2db52b4816bfe6bb3a468bd596ab4705527bee1edf679bc28ca53b28ba3", + "https://deno.land/x/hono@v4.3.11/middleware/logger/index.ts": "52a2e968890ada2c11ce89a7a783692c5767b8ed7fb23ccf6b559d255d13ccbc", + "https://deno.land/x/hono@v4.3.11/middleware/method-override/index.ts": "bc13bdcf70c777b72b1300a5cca1b51a8bd126e0d922b991d89e96fe7c694b5b", + "https://deno.land/x/hono@v4.3.11/middleware/powered-by/index.ts": "6faba0cf042278d60b317b690640bb0b58747690cf280fa09024424c5174e66d", + "https://deno.land/x/hono@v4.3.11/middleware/pretty-json/index.ts": "2216ce4c9910be009fecac63367c3626b46137d4cf7cb9a82913e501104b4a88", + "https://deno.land/x/hono@v4.3.11/middleware/secure-headers/index.ts": "f2e4c3858d26ff47bc6909513607e6a3c31184aabe78fb272ed08e1d62a750f0", + "https://deno.land/x/hono@v4.3.11/middleware/serve-static/index.ts": "14b760bbbc4478cc3a7fb9728730bc6300581c890365b7101b80c16e70e4b21e", + "https://deno.land/x/hono@v4.3.11/middleware/timing/index.ts": "6fddbb3e47ae875c16907cf23b9bb503ec2ad858406418b5f38f1e7fbca8c6f6", + "https://deno.land/x/hono@v4.3.11/middleware/trailing-slash/index.ts": "419cf0af99a137f591b72cc71c053e524fe3574393ce81e0e9dbce84a4046e24", + "https://deno.land/x/hono@v4.3.11/mod.ts": "35fd2a2e14b52365e0ad66f168b067363fd0a60d75cbcb1b01685b04de97d60e", + "https://deno.land/x/hono@v4.3.11/request.ts": "7b08602858e642d1626c3106c0bedc2aa8d97e30691a079351d9acef7c5955e6", + "https://deno.land/x/hono@v4.3.11/router.ts": "880316f561918fc197481755aac2165fdbe2f530b925c5357a9f98d6e2cc85c7", + "https://deno.land/x/hono@v4.3.11/router/linear-router/index.ts": "8a2a7144c50b1f4a92d9ee99c2c396716af144c868e10608255f969695efccd0", + "https://deno.land/x/hono@v4.3.11/router/linear-router/router.ts": "928d29894e4b45b047a4f453c7f1745c8b1869cd68447e1cb710c7bbf99a4e29", + "https://deno.land/x/hono@v4.3.11/router/pattern-router/index.ts": "304a66c50e243872037ed41c7dd79ed0c89d815e17e172e7ad7cdc4bc07d3383", + "https://deno.land/x/hono@v4.3.11/router/pattern-router/router.ts": "1b5f68e6af942579d3a40ee834294fea3d1f05fd5f70514e46ae301dd0107e46", + "https://deno.land/x/hono@v4.3.11/router/reg-exp-router/index.ts": "52755829213941756159b7a963097bafde5cc4fc22b13b1c7c9184dc0512d1db", + "https://deno.land/x/hono@v4.3.11/router/reg-exp-router/node.ts": "7efaa6f4301efc2aad0519c84973061be8555da02e5868409293a1fd98536aaf", + "https://deno.land/x/hono@v4.3.11/router/reg-exp-router/router.ts": "632f2fa426b3e45a66aeed03f7205dad6d13e8081bed6f8d1d987b6cad8fb455", + "https://deno.land/x/hono@v4.3.11/router/reg-exp-router/trie.ts": "852ce7207e6701e47fa30889a0d2b8bfcd56d8862c97e7bc9831e0a64bd8835f", + "https://deno.land/x/hono@v4.3.11/router/smart-router/index.ts": "74f9b4fe15ea535900f2b9b048581915f12fe94e531dd2b0032f5610e61c3bef", + "https://deno.land/x/hono@v4.3.11/router/smart-router/router.ts": "dc22a8505a0f345476f07dca3054c0c50a64d7b81c9af5a904476490dfd5cbb4", + "https://deno.land/x/hono@v4.3.11/router/trie-router/index.ts": "3eb75e7f71ba81801631b30de6b1f5cefb2c7239c03797e2b2cbab5085911b41", + "https://deno.land/x/hono@v4.3.11/router/trie-router/node.ts": "d3e00e8f1ba7fb26896459d5bba882356891a07793387c4655d1864c519a91de", + "https://deno.land/x/hono@v4.3.11/router/trie-router/router.ts": "54ced78d35676302c8fcdda4204f7bdf5a7cc907fbf9967c75674b1e394f830d", + "https://deno.land/x/hono@v4.3.11/utils/body.ts": "774cb319dfbe886a9d39f12c43dea15a39f9d01e45de0323167cdd5d0aad14d4", + "https://deno.land/x/hono@v4.3.11/utils/buffer.ts": "2fae689954b427b51fb84ad02bed11a72eae96692c2973802b3b4c1e39cd5b9c", + "https://deno.land/x/hono@v4.3.11/utils/color.ts": "10575c221f48bc806887710da8285f859f51daf9e6878bbdf99cb406b8494457", + "https://deno.land/x/hono@v4.3.11/utils/cookie.ts": "662529d55703d2c0bad8736cb1274eb97524c0ef7882d99254fc7c8fa925b46c", + "https://deno.land/x/hono@v4.3.11/utils/crypto.ts": "bda0e141bbe46d3a4a20f8fbcb6380d473b617123d9fdfa93e4499410b537acc", + "https://deno.land/x/hono@v4.3.11/utils/encode.ts": "311dfdfae7eb0b6345e9680f7ebbb3a692e872ed964e2029aca38567af8d1f33", + "https://deno.land/x/hono@v4.3.11/utils/filepath.ts": "a83e5fe87396bb291a6c5c28e13356fcbea0b5547bad2c3ba9660100ff964000", + "https://deno.land/x/hono@v4.3.11/utils/html.ts": "6ea4f6bf41587a51607dff7a6d2865ef4d5001e4203b07e5c8a45b63a098e871", + "https://deno.land/x/hono@v4.3.11/utils/jwt/index.ts": "3b66f48cdd3fcc2caed5e908ca31776e11b1c30391008931276da3035e6ba1e9", + "https://deno.land/x/hono@v4.3.11/utils/jwt/jwa.ts": "6874cacd8b6dde386636b81b5ea2754f8e4c61757802fa908dd1ce54b91a52fa", + "https://deno.land/x/hono@v4.3.11/utils/jwt/jws.ts": "878fa7d1966b0db20ae231cfee279ba2bb198943e949049cab3f5845cd5ee2d1", + "https://deno.land/x/hono@v4.3.11/utils/jwt/jwt.ts": "80452edc3498c6670a211fdcd33cfc4d5c00dfac79aa9f403b0623dedc039554", + "https://deno.land/x/hono@v4.3.11/utils/jwt/types.ts": "b6659ac85e7f8fcdd8cdfc7d51f5d1a91107ad8dfb647a8e4ea9c80f0f02afee", + "https://deno.land/x/hono@v4.3.11/utils/jwt/utf8.ts": "17c507f68f23ccb82503ea6183e54b5f748a6fe621eb60994adfb4a8c2a3f561", + "https://deno.land/x/hono@v4.3.11/utils/mime.ts": "d1fc2c047191ccb01d736c6acf90df731324536298181dba0ecc2259e5f7d661", + "https://deno.land/x/hono@v4.3.11/utils/url.ts": "855169632c61d03703bd08cafb27664ba3fdb352892f01687d5cce8fd49e3cb1", + "https://deno.land/x/hono@v4.3.11/validator/index.ts": "6c986e8b91dcf857ecc8164a506ae8eea8665792a4ff7215471df669c632ae7c", + "https://deno.land/x/hono@v4.3.11/validator/validator.ts": "53f3d2ad442e22f0bc2d85b7d8d90320d4e5ecf5fdd58882f906055d33a18e13", + "https://git.arx-ccn.com/Arx/ts-utils/raw/commit/c1d309ba097ada64cd072ec1e3e97edaaf8773b6/src/cashu.ts": "9fe2676838da581a051ebc4bcfca78b4b5eb04e05b5fbdd7a487767c16fec6e7", + "https://git.arx-ccn.com/Arx/ts-utils/raw/commit/c1d309ba097ada64cd072ec1e3e97edaaf8773b6/src/email.ts": "ee77141a139894b10bbd0bc68338ebf1a6261a241b97732de547c791e6b0462c", + "https://git.arx-ccn.com/Arx/ts-utils/raw/commit/c1d309ba097ada64cd072ec1e3e97edaaf8773b6/src/general.ts": "692b4c44ec137cf7ef7128337f63a4a96ef8b09057beb7ec9940a6939a615bb4", + "https://git.arx-ccn.com/Arx/ts-utils/raw/commit/c1d309ba097ada64cd072ec1e3e97edaaf8773b6/src/index.ts": "fae9d057707d0632a2d82611e773a9627f199fc038cf823bfc4ecca0cfa0f064", + "https://git.arx-ccn.com/Arx/ts-utils/raw/commit/c1d309ba097ada64cd072ec1e3e97edaaf8773b6/src/nostr.ts": "c72faf0cb4a76a746f141965d366926868b8cbc36bec1b559f921481402521c4" + }, + "workspace": { + "dependencies": [ + "jsr:@nostr/tools@^2.10.4", + "jsr:@std/assert@1", + "jsr:@std/bytes@^1.0.4", + "jsr:@std/dotenv@~0.225.2", + "jsr:@std/encoding@^1.0.5", + "npm:@cashu/cashu-ts@2", + "npm:@libsql/client@*", + "npm:@nostr-dev-kit/ndk@^2.10.7", + "npm:smtp-server@^3.13.6", + "npm:winston@^3.17.0" + ] + } +} diff --git a/package.json b/package.json deleted file mode 100644 index 582753b..0000000 --- a/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "mail-server", - "version": "1.0.0", - "description": "", - "main": "src/index.ts", - "scripts": { - "start": "DEBUG='ndk:*' bun --watch src/index.ts", - "db:generate": "prisma generate", - "db:migrate": "prisma migrate dev" - }, - "dependencies": { - "@arx/utils": "git+ssh://git@git.arx-ccn.com:222/Arx/ts-utils#v0.0.4", - "@elysiajs/cors": "^1.1.1", - "@elysiajs/server-timing": "^1.1.0", - "@elysiajs/swagger": "^1.1.6", - "@libsql/client": "^0.14.0", - "@nostr-dev-kit/ndk": "^2.10.7", - "@prisma/adapter-libsql": "^5.22.0", - "@prisma/client": "5.22.0", - "elysia": "^1.1.25", - "node-forge": "^1.3.1", - "smtp-server": "^3.13.6", - "websocket-polyfill": "^1.0.0", - "winston": "^3.17.0" - }, - "devDependencies": { - "@types/node-forge": "^1.3.11", - "@types/smtp-server": "^3.5.10", - "bun-types": "latest", - "prisma": "5.22.0", - "typescript": "^5.7.2" - }, - "private": true -} diff --git a/prisma/migrations/20241125122247_init/migration.sql b/prisma/migrations/20241125122247_init/migration.sql deleted file mode 100644 index aa6b44d..0000000 --- a/prisma/migrations/20241125122247_init/migration.sql +++ /dev/null @@ -1,29 +0,0 @@ --- CreateTable -CREATE TABLE "users" ( - "npub" TEXT NOT NULL PRIMARY KEY, - "registeredAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "lastPayment" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "subscriptionDuration" INTEGER -); - --- CreateTable -CREATE TABLE "aliases" ( - "npub" TEXT NOT NULL, - "alias" TEXT NOT NULL, - - PRIMARY KEY ("npub", "alias"), - CONSTRAINT "aliases_npub_fkey" FOREIGN KEY ("npub") REFERENCES "users" ("npub") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateTable -CREATE TABLE "mail_queue" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "alias" TEXT NOT NULL, - "sender" TEXT NOT NULL, - "data" TEXT NOT NULL, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "mail_queue_alias_fkey" FOREIGN KEY ("alias") REFERENCES "aliases" ("alias") ON DELETE RESTRICT ON UPDATE CASCADE -); - --- CreateIndex -CREATE UNIQUE INDEX "aliases_alias_key" ON "aliases"("alias"); diff --git a/prisma/migrations/20241126193747_remove_mail_queue/migration.sql b/prisma/migrations/20241126193747_remove_mail_queue/migration.sql deleted file mode 100644 index a0e1043..0000000 --- a/prisma/migrations/20241126193747_remove_mail_queue/migration.sql +++ /dev/null @@ -1,10 +0,0 @@ -/* - Warnings: - - - You are about to drop the `mail_queue` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropTable -PRAGMA foreign_keys=off; -DROP TABLE "mail_queue"; -PRAGMA foreign_keys=on; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml deleted file mode 100644 index e5e5c47..0000000 --- a/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "sqlite" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma deleted file mode 100644 index 42ccefa..0000000 --- a/prisma/schema.prisma +++ /dev/null @@ -1,28 +0,0 @@ -generator client { - provider = "prisma-client-js" - previewFeatures = ["driverAdapters"] -} - -datasource db { - provider = "sqlite" - url = env("DB_URL") -} - -model User { - npub String @id - registeredAt DateTime @default(now()) - lastPayment DateTime @default(now()) - subscriptionDuration Int? - aliases Alias[] - - @@map("users") -} - -model Alias { - npub String - alias String @unique - user User @relation(fields: [npub], references: [npub]) - - @@id([npub, alias]) - @@map("aliases") -} diff --git a/src/httpServer.ts b/src/httpServer.ts index e56b85e..997b897 100644 --- a/src/httpServer.ts +++ b/src/httpServer.ts @@ -1,247 +1,241 @@ +import {Context as HonoContext, Hono,} from "https://deno.land/x/hono@v4.3.11/mod.ts"; +import {cors} from "https://deno.land/x/hono@v4.3.11/middleware.ts"; import {CashuMint, CashuWallet, getEncodedToken} from "@cashu/cashu-ts"; -import {logger} from "./utils"; +import {getAlias, getUserByNpub} from "./models.ts"; +import {logger} from "./utils/index.ts"; import * as nip98 from "nostr-tools/nip98"; -import {Elysia, t} from "elysia"; -import {swagger} from "@elysiajs/swagger"; -import {serverTiming} from "@elysiajs/server-timing"; -import {PrismaClient} from "@prisma/client"; -import {TokenInfoWithMailSubscriptionDuration} from "@arx/utils/cashu.ts"; -import {npubToPubKeyString, pubKeyStringToNpub} from "@arx/utils/nostr.ts"; -import cors from "@elysiajs/cors"; +import {Client as LibSQL} from "@libsql/client"; +import {npubToPubKeyString, pubKeyStringToNpub, TokenInfoWithMailSubscriptionDuration,} from "@arx/utils"; -const npubType = t.String({ - pattern: `^npub1[023456789acdefghjklmnpqrstuvwxyz]{58}$`, - error: 'Invalid npub format' -}); - -const cashuTokenType = t.String({ - pattern: '^cashu[A-Za-z0-9+-_]*={0,3}$', - error: 'Invalid Cashu token format' -}) +const NPUB_REGEX = /^npub1[023456789acdefghjklmnpqrstuvwxyz]{58}$/; +const CASHU_REGEX = /^cashu[A-Za-z0-9+-_]*={0,3}$/; export class HttpServer { - constructor(private db: PrismaClient, port: number) { - new Elysia() - .use(swagger({ - documentation: { - info: { - title: 'npub.email Documentation', - version: '0.0.1' - } - } - })) - .use(serverTiming()) - .use(cors()) - .get('/', 'nostr.email server') - .get('/subscription/:npub', this.getSubscriptionForNpub, { - params: t.Object({ - npub: npubType - }) - }) - .get('/aliases/:npub', this.getAliasesForNpub, { - params: t.Object({ - npub: npubType, - }), - }) - .get('/alias/:alias', this.getNpubForAlias, { - params: t.Object({ - alias: t.String(), - }), - }) - .post('/addAlias', this.addAlias, { - body: t.Object({ - alias: t.String() - }) - }) - .post('/addTime/:npub', this.addTimeToNpub, { - params: t.Object({ - npub: npubType, - }), - body: t.Object({ - tokenString: cashuTokenType - }) - }) - .listen(port) + private app: Hono; + + constructor(private db: LibSQL, port: number) { + this.app = new Hono(); + + this.app + .use("*", cors()) + .get("/", (c: HonoContext) => c.text("nostr.email server")) + .get("/subscription/:npub", this.getSubscriptionForNpub) + .get("/aliases/:npub", this.getAliasesForNpub) + .get("/alias/:alias", this.getNpubForAlias) + .post("/addAlias", this.addAlias) + .post("/addTime/:npub", this.addTimeToNpub); + + Deno.serve({ port }, this.app.fetch); logger.info(`HTTP Server running on port ${port}`); } - getSubscriptionForNpub = async ({params: {npub}}: { - params: { - npub: string + getSubscriptionForNpub = async (c: HonoContext) => { + const npub = c.req.param("npub"); + if (!NPUB_REGEX.test(npub)) { + return c.json({ error: "Invalid npub format" }, 400); } - }) => { - const user = await this.db.user.findFirst({ - where: { - npub - }, - include: { - aliases: true - } - }); - if (!user) return { - subscribed: false - }; - return { + + const user = await getUserByNpub(this.db, npub); + + if (!user) { + return c.json({ + subscribed: false, + }); + } + return c.json({ subscribed: true, - subscribedUntil: user.subscriptionDuration == null ? Infinity : Math.floor(user.lastPayment.getTime() / 1000) + user.subscriptionDuration - }; - } + subscribedUntil: user.subscriptionDuration == null + ? Infinity + : Math.floor(user.lastPayment.getTime() / 1000) + + user.subscriptionDuration, + }); + }; - getNpubForAlias = async ({params: {alias}}: { - params: { - alias: string + getNpubForAlias = async (c: HonoContext) => { + const aliasParam = c.req.param("alias"); + + const alias = await getAlias(this.db, aliasParam); + + if (!alias) { + return c.json({ error: "Not found" }, 404); } - }) => { - const user = await this.db.user.findFirst({ - where: { - aliases: { - some: { - alias - } - } - } - }); - if (!user) return new Response('Not found', { - status: 404 - }); - return user.npub; - } + return c.json({ npub: alias.npub }); + }; - getAliasesForNpub = async ({params: {npub}, headers}: { - params: { - npub: string - }, - headers: Record - }) => { - const unpacked = await this.getUnpackedAuthHeader(headers, `/aliases/${npub}`); - const npubAsPubkey = npubToPubKeyString(npub); - if (unpacked.pubkey !== npubAsPubkey) - return new Response('Unauthorized', { - status: 401 - }) - const user = await this.db.user.findFirst({ - where: { - npub - }, - include: { - aliases: true - } - }); - if (!user) return new Response('Not found', { - status: 404 - }); - return user.aliases.map(alias => alias.alias); - } - - addAlias = async ({body: {alias}, headers}: { - body: { - alias: string - }, - headers: Record - }) => { - const unpacked = await this.getUnpackedAuthHeader(headers, '/addAlias'); - const unpackedKeyToNpub = pubKeyStringToNpub(unpacked.pubkey); - const userInDb = await this.db.user.findFirst({ - where: { - npub: unpackedKeyToNpub - } - }); - if (!userInDb) return new Response('Unauthorized', { - status: 401 - }); - - const stillHasSubscription = userInDb.subscriptionDuration === null || Math.floor(userInDb.lastPayment.getTime() / 1000) + userInDb.subscriptionDuration > Date.now() / 1000; - if (!stillHasSubscription) return new Response('User has no subscription', { - status: 400 - }); - const aliasInDb = await this.db.alias.findFirst({ - where: { - alias - } - }); - if (aliasInDb) return new Response('Alias already exists', { - status: 400 - }); - return this.db.user.update({ - where: { - npub: unpackedKeyToNpub - }, - data: { - aliases: { - create: { - alias - } - } - } - }); - } - - addTimeToNpub = async ({params: {npub}, body: {tokenString}}: { - params: { - npub: string - }, - body: { - tokenString: string + getAliasesForNpub = async (c: HonoContext) => { + const npub = c.req.param("npub"); + if (!NPUB_REGEX.test(npub)) { + return c.json({ error: "Invalid npub format" }, 400); } - }) => { - const userInDb = await this.db.user.findFirst({ - where: { - npub - } - }); - if (userInDb && (userInDb.subscriptionDuration === null || userInDb.subscriptionDuration === -1)) - return new Response('User has unlimited subscription', { - status: 400 - }) + try { + const unpacked = await this.getUnpackedAuthHeader( + c.req.header("Authorization"), + `/aliases/${npub}`, + ); + const npubAsPubkey = npubToPubKeyString(npub); + + if (unpacked.pubkey !== npubAsPubkey) { + return c.json({ error: "Unauthorized" }, 401); + } + + const user = await getUserByNpub(this.db, npub); + + if (!user) { + return c.json({ error: "Not found" }, 404); + } + + return c.json(user.aliases); + } catch (error) { + if (error instanceof Error) { + return c.json({ error: error.message }, 401); + } else { + return c.json({ error: `${error}` }, 401); + } + } + }; + + addAlias = async (c: HonoContext) => { + const { alias } = await c.req.json<{ + alias: string; + }>(); + + try { + const unpacked = await this.getUnpackedAuthHeader( + c.req.header("Authorization"), + "/addAlias", + ); + const unpackedKeyToNpub = pubKeyStringToNpub(unpacked.pubkey); + + const user = await getUserByNpub(this.db, unpackedKeyToNpub); + + if (!user) { + return c.json({ error: "Unauthorized" }, 401); + } + + const stillHasSubscription = user.subscriptionDuration === null || + Math.floor(user.lastPayment.getTime() / 1000) + + user.subscriptionDuration > Date.now() / 1000; + + if (!stillHasSubscription) { + return c.json({ error: "User has no subscription" }, 400); + } + + const aliasInDb = await getAlias(this.db, alias); + + if (aliasInDb) { + return c.json({ error: "Alias already exists" }, 400); + } + + await this.db.execute({ + sql: ` + INSERT INTO aliases (alias, npub) + VALUES ($alias, $npub) + ON CONFLICT (alias) DO NOTHING + `, + args: { alias, npub: unpackedKeyToNpub }, + }); + + return c.json({ alias, npub: unpackedKeyToNpub }); + } catch (error) { + if (error instanceof Error) { + return c.json({ error: error.message }, 401); + } else { + return c.json({ error: `${error}` }, 401); + } + } + }; + + addTimeToNpub = async (c: HonoContext) => { + const npub = c.req.param("npub"); + const { tokenString } = await c.req.json(); + + if (!NPUB_REGEX.test(npub)) { + return c.json({ error: "Invalid npub format" }, 400); + } + if (!CASHU_REGEX.test(tokenString)) { + return c.json({ error: "Invalid Cashu token format" }, 400); + } + + const user = await getUserByNpub(this.db, npub); + + if ( + user && + (user.subscriptionDuration === null || + user.subscriptionDuration === -1) + ) { + return c.json({ error: "User has unlimited subscription" }, 400); + } const tokenInfo = new TokenInfoWithMailSubscriptionDuration(tokenString); const mint = new CashuMint(tokenInfo.mint); const wallet = new CashuWallet(mint); const newToken = await wallet.receive(tokenString); const encodedToken = getEncodedToken({ - token: [{ - mint: tokenInfo.mint, - proofs: newToken - }] + mint: tokenInfo.mint, + proofs: newToken, }); logger.info(`New cashu token: ${encodedToken}`); - if (userInDb) { - let timeRemaining = Math.max(0, Math.floor((+new Date(userInDb.lastPayment.getTime() + userInDb.subscriptionDuration! * 1000) - +new Date()) / 1000)); + if (user) { + let timeRemaining = Math.max( + 0, + Math.floor( + (+new Date( + user.lastPayment.getTime() + + user.subscriptionDuration! * 1000, + ) - +new Date()) / 1000, + ), + ); timeRemaining += tokenInfo.duration; - await this.db.user.update({ - where: { - npub - }, - data: { + await this.db.execute({ + sql: ` + UPDATE users + SET lastPayment = $lastPayment, subscriptionDuration = $subscriptionDuration + WHERE npub = $npub + `, + args: { lastPayment: new Date(), - subscriptionDuration: timeRemaining - } + subscriptionDuration: timeRemaining, + npub, + }, + }); + return c.json({ + newTimeRemaining: timeRemaining, }); - return { - newTimeRemaining: timeRemaining - } } - await this.db.user.create({ - data: { + await this.db.execute({ + sql: ` + INSERT INTO users (npub, registeredAt, lastPayment, subscriptionDuration) + VALUES ($npub, $registeredAt, $lastPayment, $subscriptionDuration) + `, + args: { npub, registeredAt: new Date(), lastPayment: new Date(), - subscriptionDuration: tokenInfo.duration - } + subscriptionDuration: tokenInfo.duration, + }, }); - return { - newTimeRemaining: tokenInfo.duration - } - } + return c.json({ + newTimeRemaining: tokenInfo.duration, + }); + }; - private getUnpackedAuthHeader = async (headers: Record, url: string) => { - if (!headers.authorization) - throw new Error('Unauthorized'); - const authHeader = headers.authorization.split(' ')[1]; - const validate = await nip98.validateToken(authHeader, `${process.env.PUBLIC_API_BASE_URL!}${url}`, "POST"); - if (!validate) - throw new Error('Unauthorized'); + private getUnpackedAuthHeader = async ( + auth: string | undefined, + url: string, + ) => { + if (!auth) { + throw new Error("Unauthorized"); + } + const authHeader = auth.split(" ")[1]; + const validate = await nip98.validateToken( + authHeader, + `${Deno.env.get("PUBLIC_API_BASE_URL")!}${url}`, + "POST", + ); + if (!validate) { + throw new Error("Unauthorized"); + } return await nip98.unpackEventFromToken(authHeader); - } + }; } diff --git a/src/index.ts b/src/index.ts index 2d87a08..b0323a2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,26 +1,34 @@ -import {createClient as createLibSQLClient} from "@libsql/client"; -import "websocket-polyfill"; -import {PrismaClient} from "@prisma/client"; -import {PrismaLibSQL} from "@prisma/adapter-libsql"; -import {NostrSmtpServer} from "./smtpServer"; -import {HttpServer} from "./httpServer"; +import {NostrSmtpServer} from "./smtpServer.ts"; +import {HttpServer} from "./httpServer.ts"; +import {createClient as createDB} from "@libsql/client/sqlite3"; +import "@std/dotenv/load"; -if (!process.env.BASE_DOMAIN) - throw new Error("BASE_DOMAIN is not set"); -if (!process.env.DB_URL) - throw new Error("DB_URL is not set"); -if (!process.env.PUBLIC_API_BASE_URL) - throw new Error("PUBLIC_API_BASE_URL is not set"); -if (!process.env.MASTER_NSEC) - throw new Error("MASTER_NSEC is not set"); +const requiredEnvVars = [ + "BASE_DOMAIN", + "DB_URL", + "PUBLIC_API_BASE_URL", + "MASTER_NSEC", +]; -const dbClient = createLibSQLClient({ - url: process.env.DB_URL, +for (const envVar of requiredEnvVars) { + if (!Deno.env.has(envVar)) { + throw new Error(`${envVar} is not set`); + } +} + +export const db = createDB({ + url: Deno.env.get("DB_URL")!, }); -const db = new PrismaClient({ - adapter: new PrismaLibSQL(dbClient) -}); - -new NostrSmtpServer(db, parseInt(process.env.SMTP_PORT || '6587')); -new HttpServer(db, parseInt(process.env.HTTP_PORT || '3000')); \ No newline at end of file +new NostrSmtpServer( + db, + parseInt( + Deno.env.get("SMTP_PORT") || "6587", + ), +); +new HttpServer( + db, + parseInt( + Deno.env.get("HTTP_PORT") || "3000", + ), +); diff --git a/src/models.ts b/src/models.ts new file mode 100644 index 0000000..f18303b --- /dev/null +++ b/src/models.ts @@ -0,0 +1,113 @@ +import {Client as LibSQL} from "@libsql/client"; + +type User = { + npub: string; + registeredAt: Date; + lastPayment: Date; + subscriptionDuration: number | null; +}; + +type Alias = { + alias: string; + npub: string; +}; + +type UserWithAliases = User & { + aliases: string[]; +}; + +export type AliasRowResult = { + rows: Array<{ + alias: string; + npub: string; + }>; + columns: string[]; + rowsAffected: number; +}; + +export type UserRowResult = { + rows: Array<{ + npub: string; + registeredAt: Date; + lastPayment: Date; + subscriptionDuration: number | null; + alias: string | null; + }>; + columns: string[]; + rowsAffected: number; +}; + +export const mapSingleAlias = (rows: AliasRowResult["rows"]): Alias | null => { + if (rows.length === 0) { + return null; + } + const firstRow = rows[0]; + return { + alias: firstRow.alias, + npub: firstRow.npub, + }; +}; + +export const mapSingleUserWithAliases = ( + rows: UserRowResult["rows"], +): UserWithAliases | null => { + if (rows.length === 0) { + return null; + } + const firstRow = rows[0]; + return { + npub: firstRow.npub, + registeredAt: firstRow.registeredAt, + lastPayment: new Date(firstRow.lastPayment), + subscriptionDuration: firstRow.subscriptionDuration, + aliases: rows + .filter((row) => row.alias !== null) + .map((row) => row.alias!), + }; +}; + +export const getUserByNpub = async ( + db: LibSQL, + npub: string, +): Promise => { + const result: UserRowResult = await db.execute({ + sql: ` + SELECT u.*, a.* + FROM users u LEFT JOIN aliases a ON u.npub = a.npub + WHERE u.npub = $npub; + `, + args: { npub }, + }) as any as UserRowResult; + + return mapSingleUserWithAliases(result.rows); +}; + +export const getUserByAlias = async ( + db: LibSQL, + alias: string, +): Promise => { + const result: UserRowResult = await db.execute({ + sql: ` + SELECT u.*, a.* + FROM users u LEFT JOIN aliases a ON u.npub = a.npub + WHERE a.alias = $alias; + `, + args: { alias }, + }) as any as UserRowResult; + + return mapSingleUserWithAliases(result.rows); +}; + +export const getAlias = async (db: LibSQL, aliasParam: string) => { + const result: AliasRowResult = await db.execute({ + sql: ` + SELECT * + FROM aliases + WHERE alias = $alias + LIMIT 1; + `, + args: { alias: aliasParam }, + }) as any as AliasRowResult; + + return mapSingleAlias(result.rows); +}; diff --git a/src/smtpServer.ts b/src/smtpServer.ts index 933d387..1d83597 100644 --- a/src/smtpServer.ts +++ b/src/smtpServer.ts @@ -1,10 +1,12 @@ -import {SMTPServer, SMTPServerAddress, SMTPServerDataStream, SMTPServerSession} from "smtp-server"; -import {deriveNsecForEmail, getNDK, logger} from "./utils"; +import {SMTPServer, SMTPServerAddress, SMTPServerDataStream, SMTPServerSession,} from "smtp-server"; +import {deriveNsecForEmail, getNDK, getUserByAlias, logger,} from "./utils/index.ts"; import {NDKEvent, NDKKind, NDKPrivateKeySigner} from "@nostr-dev-kit/ndk"; -import {PrismaClient} from "@prisma/client"; +import {Client as LibSQL} from "@libsql/client"; import {encryptEventForRecipient, parseEmail} from "@arx/utils"; -import * as path from "node:path"; -import fs from 'node:fs/promises'; +import {concat} from "@std/bytes"; +import path from "node:path"; +import fs from "node:fs/promises"; +import process from "node:process"; interface QueuedEmail { id: string; @@ -19,20 +21,24 @@ export class NostrSmtpServer { private emailQueue: QueuedEmail[] = []; private isProcessing: boolean = false; private readonly MAX_RETRIES = 3; - private readonly BACKUP_DIR = path.join(process.cwd(), 'email-backups'); + private readonly BACKUP_DIR = path.join(Deno.cwd(), "email-backups"); - constructor(private db: PrismaClient, port: number) { + constructor(private db: LibSQL, port: number) { this.server = new SMTPServer({ authOptional: true, logger: false, - onData: (stream, session, callback) => this.handleEmailData(stream, session, callback, db) + onData: ( + stream: SMTPServerDataStream, + session: SMTPServerSession, + callback: () => void, + ) => this.handleEmailData(stream, session, callback), }); - this.server.listen(port, '0.0.0.0'); + this.server.listen(port, "0.0.0.0"); logger.info(`SMTP Server running on port ${port}`); - fs.mkdir(this.BACKUP_DIR, {recursive: true}).catch(err => { - logger.error('Failed to create backup directory:', err); + fs.mkdir(this.BACKUP_DIR, { recursive: true }).catch((err) => { + logger.error("Failed to create backup directory:", err); }); this.setupGracefulShutdown(); @@ -43,7 +49,9 @@ export class NostrSmtpServer { const files = await fs.readdir(this.BACKUP_DIR); for (const file of files) { try { - const data = JSON.parse(await fs.readFile(path.join(this.BACKUP_DIR, file), 'utf-8')); + const data = JSON.parse( + await fs.readFile(path.join(this.BACKUP_DIR, file), "utf-8"), + ); this.emailQueue.push(data); } catch (error) { logger.error(`Failed to recover backup ${file}:`, error); @@ -54,71 +62,78 @@ export class NostrSmtpServer { private setupGracefulShutdown(): void { const shutdown = async () => { - logger.info('Graceful shutdown initiated'); + logger.info("Graceful shutdown initiated"); this.server.close(); - while (this.isProcessing) - await new Promise(resolve => setTimeout(resolve, 100)); + while (this.isProcessing) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } - for (const email of this.emailQueue) + for (const email of this.emailQueue) { await this.backupEmail(email); + } - logger.info('Graceful shutdown completed'); + logger.info("Graceful shutdown completed"); process.exit(0); }; - process.on('SIGTERM', shutdown); - process.on('SIGINT', shutdown); + process.on("SIGTERM", shutdown); + process.on("SIGINT", shutdown); } - private async handleEmailData(stream: SMTPServerDataStream, session: SMTPServerSession, callback: () => void, db: PrismaClient) { - const chunks: Buffer[] = []; + private async handleEmailData( + stream: SMTPServerDataStream, + session: SMTPServerSession, + callback: () => void, + ) { + const chunks: Uint8Array[] = []; - stream.on('data', (chunk: Buffer) => { - chunks.push(chunk); - }); + try { + for await (const chunk of stream) { + chunks.push(chunk); + } - stream.on('end', async () => { if (!this.validateSender(session)) { callback(); return; } - const mailData = Buffer.concat(chunks).toString(); + const mailData = new TextDecoder().decode( + concat(chunks), + ); - try { - const queuedEmail: QueuedEmail = { - id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, - mailData, - session, - attempts: 0, - createdAt: Date.now() - }; + const queuedEmail: QueuedEmail = { + id: `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`, + mailData, + session, + attempts: 0, + createdAt: Date.now(), + }; - this.emailQueue.push(queuedEmail); - await this.backupEmail(queuedEmail); - this.processQueue(); - } catch (e) { - logger.error(`Error processing recipients: ${e}`, e); - } + this.emailQueue.push(await this.backupEmail(queuedEmail)); + this.processQueue(); + } catch (e) { + logger.error(`Error processing recipients: ${e}`, e); + } - callback(); - }); + callback(); } - private async backupEmail(email: QueuedEmail): Promise { + private async backupEmail(email: QueuedEmail): Promise { try { const backupPath = path.join(this.BACKUP_DIR, `${email.id}.json`); await fs.writeFile(backupPath, JSON.stringify(email)); } catch (error) { - logger.error('Failed to backup email:', error); + logger.error("Failed to backup email:", error); } + return JSON.parse(JSON.stringify(email)) as QueuedEmail; // returning the email because of a weird bug with smtp-server } private async processQueue(): Promise { - if (this.isProcessing) + if (this.isProcessing) { return; + } this.isProcessing = true; while (this.emailQueue.length > 0) { const email = this.emailQueue[0]; @@ -128,7 +143,9 @@ export class NostrSmtpServer { } try { - const parsedEmail: ReturnType = parseEmail(email.mailData); + const parsedEmail: ReturnType = parseEmail( + email.mailData, + ); await this.processRecipients(email.session, parsedEmail, this.db); // Remove from queue and delete backup if successful @@ -149,77 +166,93 @@ export class NostrSmtpServer { const backupPath = path.join(this.BACKUP_DIR, `${id}.json`); await fs.unlink(backupPath); } catch (error) { - logger.error(`[TOXIC DATA!!!] Failed to delete email backup ${id}.json:`, error); + logger.error( + `[TOXIC DATA!!!] Failed to delete email backup ${id}.json:`, + error, + ); } } private validateSender(session: SMTPServerSession): boolean { if (!session.envelope.mailFrom) { - logger.warn('Ignoring email without sender'); + logger.warn("Ignoring email without sender"); return false; } return true; } - private async processRecipients(session: SMTPServerSession, parsedEmail: ReturnType, db: PrismaClient) { + private async processRecipients( + session: SMTPServerSession, + parsedEmail: ReturnType, + db: LibSQL, + ) { for (const recipientEmail of session.envelope.rcptTo) { const address = recipientEmail.address; - const [alias, domain] = address.split('@'); + const [alias, domain] = address.split("@"); - if (domain !== process.env.BASE_DOMAIN) { - logger.warn(`Not sending email to ${address} because it is not in the allowed domain`); + if (domain !== Deno.env.get("BASE_DOMAIN")) { + logger.warn( + `Not sending email to ${address} because it is not in the allowed domain`, + ); continue; } const user = await this.getUser(alias, db); - if (!user || !this.isSubscriptionValid(user)) continue; + if (!user || !this.isSubscriptionValid(user)) return; await this.sendNostrLetter(session, parsedEmail, user.npub); } } - private async getUser(alias: string, db: PrismaClient) { - const user = await db.alias.findUnique({ - where: {alias}, - include: {user: true} - }); + private async getUser(alias: string, db: LibSQL) { + const user = await getUserByAlias(db, alias); if (!user) { - logger.warn('No user found for', alias, 'skipping'); + logger.warn("No user found for", alias, "skipping"); return null; } return user; } - private isSubscriptionValid(user: NonNullable>>): boolean { + private isSubscriptionValid( + user: NonNullable>>, + ): boolean { // If there's no duration set, it's an unlimited subscription - if (user.user.subscriptionDuration === null) + if (user.subscriptionDuration === null) { return true; + } - const subscriptionDurationMs = user.user.subscriptionDuration * 1000; - const lastPaymentTimestamp = user.user.lastPayment.getTime(); + const subscriptionDurationMs = user.subscriptionDuration * 1000; + const lastPaymentTimestamp = user.lastPayment.getTime(); const currentTimestamp = Date.now(); const subscriptionEndTime = lastPaymentTimestamp + subscriptionDurationMs; const timeRemaining = subscriptionEndTime - currentTimestamp; if (timeRemaining <= 0) { - logger.warn(`Subscription has expired for ${user.alias}`); + logger.warn(`Subscription has expired for ${user.npub}`); return false; } return true; } - private async sendNostrLetter(session: SMTPServerSession, parsedEmail: ReturnType, recipient: string) { + private async sendNostrLetter( + session: SMTPServerSession, + parsedEmail: ReturnType, + recipient: string, + ) { const randomKeySinger = new NDKPrivateKeySigner( - deriveNsecForEmail(process.env.MASTER_NSEC!, (session.envelope.mailFrom as SMTPServerAddress).address) + deriveNsecForEmail( + Deno.env.get("MASTER_NSEC")!, + (session.envelope.mailFrom as SMTPServerAddress).address, + ), ); const ndk = getNDK(); ndk.signer = randomKeySinger; await ndk.connect(); - const ndkUser = ndk.getUser({npub: recipient}); + const ndkUser = ndk.getUser({ npub: recipient }); const randomKeyUser = await randomKeySinger.user(); const event = new NDKEvent(); event.kind = NDKKind.Article; @@ -227,17 +260,17 @@ export class NostrSmtpServer { event.created_at = Math.floor(Date.now() / 1000); event.pubkey = randomKeyUser.pubkey; event.tags.push( - ['p', ndkUser.pubkey], - ['subject', parsedEmail.subject], - ['email:localIP', session.localAddress], - ['email:remoteIP', session.remoteAddress], - ['email:isEmail', 'true'], - ['email:session', session.id], - ['email:from', (session.envelope.mailFrom as SMTPServerAddress).address] + ["p", ndkUser.pubkey], + ["subject", parsedEmail.subject], + ["email:localIP", session.localAddress], + ["email:remoteIP", session.remoteAddress], + ["email:isEmail", "true"], + ["email:session", session.id], + ["email:from", (session.envelope.mailFrom as SMTPServerAddress).address], ); for (const to of session.envelope.rcptTo) { - event.tags.push(['email:to', to.address]); + event.tags.push(["email:to", to.address]); } for (const header of Object.keys(parsedEmail.headers)) { diff --git a/src/utils/index.ts b/src/utils/index.ts index 43362bc..257f4ca 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,15 +1,16 @@ import NDK from "@nostr-dev-kit/ndk"; import * as crypto from "node:crypto"; +import {decodeHex} from "@std/encoding/hex"; -export * from "./logs"; +export * from "./logs.ts"; +export * from "../models.ts"; export function getNDK() { return new NDK({ explicitRelayUrls: [ - 'wss://relay.primal.net', - 'wss://relay.damus.io', - 'wss://relay.nostr.band', - 'wss://offchain.pub' + "wss://relay.primal.net", + "wss://relay.nostr.band", + "wss://offchain.pub", ], autoConnectUserRelays: false, enableOutboxModel: true, @@ -27,9 +28,16 @@ export function getNDK() { * @param email - The email address. * @returns The nostr private key derived from the master key and email address as a uint8array. */ -export function deriveNsecForEmail(masterNsec: string, email: string): Uint8Array { - const masterNsecHash = crypto.createHash('sha256').update(masterNsec).digest('hex'); - const emailHash = crypto.createHash('sha256').update(email).digest('hex'); - const sharedSecret = crypto.createHash('sha256').update(masterNsecHash + emailHash).digest('hex'); - return Uint8Array.from(Buffer.from(sharedSecret, 'hex')); -} \ No newline at end of file +export function deriveNsecForEmail( + masterNsec: string, + email: string, +): Uint8Array { + const masterNsecHash = crypto.createHash("sha256").update(masterNsec).digest( + "hex", + ); + const emailHash = crypto.createHash("sha256").update(email).digest("hex"); + const sharedSecret = crypto.createHash("sha256").update( + masterNsecHash + emailHash, + ).digest("hex"); + return decodeHex(sharedSecret); +} diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 80c0465..e95a6c0 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -1,22 +1,22 @@ import winston from "winston"; -const {combine, timestamp, printf, align, colorize, json} = winston.format; +const { combine, timestamp, printf, align, colorize, json } = winston.format; export const logger = winston.createLogger({ - level: 'info', + level: "info", transports: [ new winston.transports.Console({ format: combine( - colorize({all: true}), + colorize({ all: true }), timestamp({ - format: 'YYYY-MM-DD hh:mm:ss.SSS A', + format: "YYYY-MM-DD hh:mm:ss.SSS A", }), align(), - printf((info) => `[${info.timestamp}] ${info.level}: ${info.message}`) + printf((info) => `[${info.timestamp}] ${info.level}: ${info.message}`), ), }), new winston.transports.File({ - filename: process.env.LOG_FILE || '/tmp/nostr-email.log', + filename: Deno.env.get("LOG_FILE") || "/tmp/nostr-email.log", format: combine(timestamp(), json()), }), ],