a cache for slack profile pictures and emojis

feat: migrate to just bun server itself and make stuff way faster

dunkirk.sh 50fb165c dbc5cd8a

verified
-70
bun.lock
···
"": {
"name": "cachet",
"dependencies": {
-
"@elysiajs/cors": "1.3.1",
-
"@elysiajs/cron": "1.2.0",
-
"@elysiajs/html": "1.3.0",
-
"@elysiajs/swagger": "1.1.6",
"@sentry/bun": "^9.40.0",
"@tqman/nice-logger": "^1.0.7",
"@types/node-cron": "^3.0.11",
···
},
},
"packages": {
-
"@elysiajs/cors": ["@elysiajs/cors@1.3.1", "", { "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-JYfVDD1ndplEVw7tcZBYcpUcPzKfDTTZCyAuml1ld7uKS3r+yIzbySvGaJI2A9q1gWY/r9DsSwd0N86B2TPUQQ=="],
-
-
"@elysiajs/cron": ["@elysiajs/cron@1.2.0", "", { "dependencies": { "croner": "^6.0.3" }, "peerDependencies": { "elysia": ">= 1.2.0" } }, "sha512-Xg2bUCZNEp2zRzsYeNQ6BUP3uslF5BEPnuLHLnsytiAEqzFTHzS7813XbmHs8xNZKuUAkAnJNTVnQb1Jt5zTwg=="],
-
-
"@elysiajs/html": ["@elysiajs/html@1.3.0", "", { "dependencies": { "@kitajs/html": "^4.1.0", "@kitajs/ts-html-plugin": "^4.0.1" }, "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-NpujllWwiEXdsX8GJhbBppOv7+aJr+OU7Gn3K8fVXpwieutwau0/B/M6vzjYXsh9OaoGByUTpL8U9rA/tVSn7w=="],
-
-
"@elysiajs/swagger": ["@elysiajs/swagger@1.1.6", "", { "dependencies": { "@scalar/types": "^0.0.12", "openapi-types": "^12.1.3", "pathe": "^1.1.2" }, "peerDependencies": { "elysia": ">= 1.1.0" } }, "sha512-B1airTG3eh6eFgFxGS2UtsdZ7Xc2vrn3YKIFLFai9YeZVROSHmi3ZaXZvGAn3DnkXHT6I+qx960xnrqoNiopUw=="],
-
-
"@kitajs/html": ["@kitajs/html@4.2.9", "", { "dependencies": { "csstype": "^3.1.3" } }, "sha512-FDHHf5Mi5nR0D+Btq86IV1O9XfsePVCiC5rwU4PXjw2aHja16FmIiwLZBO0CS16rJxKkibjMldyRLAW2ni2mzA=="],
-
-
"@kitajs/ts-html-plugin": ["@kitajs/ts-html-plugin@4.1.1", "", { "dependencies": { "chalk": "^4.1.2", "tslib": "^2.8.1", "yargs": "^17.7.2" }, "peerDependencies": { "@kitajs/html": "^4.2.5", "typescript": "^5.6.2" }, "bin": { "ts-html-plugin": "dist/cli.js", "xss-scan": "dist/cli.js" } }, "sha512-wmjyV8hmJmDOnUM/ZyPkc0UBYgUYmf32/93rkW8wr8h+HiHVMU0tEKFnmRdBjTcy9jwoC9Bnt2NuzS9l67lq5g=="],
-
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
"@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.57.2", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A=="],
···
"@prisma/instrumentation": ["@prisma/instrumentation@6.11.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" }, "peerDependencies": { "@opentelemetry/api": "^1.8" } }, "sha512-mrZOev24EDhnefmnZX7WVVT7v+r9LttPRqf54ONvj6re4XMF7wFTpK2tLJi4XHB7fFp/6xhYbgRel8YV7gQiyA=="],
-
"@scalar/openapi-types": ["@scalar/openapi-types@0.1.1", "", {}, "sha512-NMy3QNk6ytcCoPUGJH0t4NNr36OWXgZhA3ormr3TvhX1NDgoF95wFyodGVH8xiHeUyn2/FxtETm8UBLbB5xEmg=="],
-
-
"@scalar/types": ["@scalar/types@0.0.12", "", { "dependencies": { "@scalar/openapi-types": "0.1.1", "@unhead/schema": "^1.9.5" } }, "sha512-XYZ36lSEx87i4gDqopQlGCOkdIITHHEvgkuJFrXFATQs9zHARop0PN0g4RZYWj+ZpCUclOcaOjbCt8JGe22mnQ=="],
-
"@sentry/bun": ["@sentry/bun@9.40.0", "", { "dependencies": { "@sentry/core": "9.40.0", "@sentry/node": "9.40.0" } }, "sha512-QozLfHUp3aFhhW9qbPbuk3hqiE1fI48/gl6FsYvGixtDRdCLt/7/3VujnVRTuhgn/Yv20y8vdPtqIPuVyPH09Q=="],
"@sentry/core": ["@sentry/core@9.40.0", "", {}, "sha512-cZkuz6BDna6VXSqvlWnrRsaDx4QBKq1PcfQrqhVz8ljs0M7Gcl+Mtj8dCzUxx12fkYM62hQXG72DEGNlAQpH/Q=="],
···
"@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="],
-
"@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="],
-
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="],
-
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
-
-
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
-
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="],
···
"bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="],
-
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
-
"cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="],
-
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
-
"coffee-script": ["coffee-script@1.12.7", "", { "bin": { "coffee": "./bin/coffee", "cake": "./bin/cake" } }, "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw=="],
-
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
-
-
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
-
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
-
"croner": ["croner@6.0.7", "", {}, "sha512-k3Xx3Rcclfr60Yx4TmvsF3Yscuiql8LSvYLaphTsaq5Hk8La4Z/udmUANMOTKpgGGroI2F6/XOr9cU9OFkYluQ=="],
-
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"elysia": ["elysia@1.1.26", "", { "dependencies": { "@sinclair/typebox": "0.32.34", "cookie": "^1.0.1", "fast-decode-uri-component": "^1.0.1", "openapi-types": "^12.1.3" }, "peerDependencies": { "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-wtHa46hP4cMzzgq+WlqVMz9th56FlEdS1UDJgtAJD3rQkjovidySKxFfdKEr16pulaleBo5OC1BQL35XhGaW0Q=="],
-
-
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
-
-
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
···
"forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
-
-
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
-
-
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
-
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
-
"import-in-the-middle": ["import-in-the-middle@1.14.2", "", { "dependencies": { "acorn": "^8.14.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^1.2.2", "module-details-from-path": "^1.0.3" } }, "sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw=="],
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
-
-
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
···
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
-
"pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
-
"pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
"pg-protocol": ["pg-protocol@1.10.3", "", {}, "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ=="],
···
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
-
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
-
"require-in-the-middle": ["require-in-the-middle@7.5.2", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3", "resolve": "^1.22.8" } }, "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ=="],
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
···
"shimmer": ["shimmer@1.2.1", "", {}, "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="],
-
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
-
-
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
-
-
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
-
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
-
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
-
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"underscore": ["underscore@1.13.7", "", {}, "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g=="],
···
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
"uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
-
-
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
-
-
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
-
-
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
-
-
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
-
-
"zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="],
"@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
+4 -6
package.json
···
{
"name": "cachet",
-
"version": "0.2.0",
+
"version": "0.3.0",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
-
"dev": "bun run --watch src/index.ts"
+
"dev": "bun run --watch src/index.ts",
+
"start": "bun run src/server.ts",
+
"build": "bun build --target=bun --production --outdir=dist ./src/server.ts"
},
"dependencies": {
-
"@elysiajs/cors": "1.3.1",
-
"@elysiajs/cron": "1.2.0",
-
"@elysiajs/html": "1.3.0",
-
"@elysiajs/swagger": "1.1.6",
"@sentry/bun": "^9.40.0",
"@tqman/nice-logger": "^1.0.7",
"@types/node-cron": "^3.0.11",
+616 -567
src/index.ts
···
-
import { cors } from "@elysiajs/cors";
-
import { cron } from "@elysiajs/cron";
-
import { html } from "@elysiajs/html";
-
import { swagger } from "@elysiajs/swagger";
+
import { serve } from "bun";
import * as Sentry from "@sentry/bun";
-
import { logger } from "@tqman/nice-logger";
-
import { Elysia, t } from "elysia";
-
import { version } from "../package.json";
+
import { SlackCache } from "./cache";
+
import { SlackWrapper } from "./slackWrapper";
import { getEmojiUrl } from "../utils/emojiHelper";
-
import { SlackCache } from "./cache";
-
import dashboard from "./dashboard.html" with { type: "text" };
import type { SlackUser } from "./slack";
-
import { SlackWrapper } from "./slackWrapper";
+
import swaggerSpec from "./swagger";
+
import dashboard from "./dashboard.html";
+
import swagger from "./swagger.html";
+
// Initialize Sentry if DSN is provided
if (process.env.SENTRY_DSN) {
console.log("Sentry DSN provided, error monitoring is enabled");
Sentry.init({
···
console.warn("Sentry DSN not provided, error monitoring is disabled");
}
+
// Initialize SlackWrapper and Cache
const slackApp = new SlackWrapper();
-
const cache = new SlackCache(
process.env.DATABASE_PATH ?? "./data/cachet.db",
25,
···
);
console.log("Batch inserting emojis");
-
await cache.batchInsertEmojis(emojiEntries);
-
console.log("Finished batch inserting emojis");
},
);
-
const app = new Elysia()
-
.use(html())
-
.use(
-
logger({
-
mode: "combined",
-
}),
-
)
-
.use(
-
cors({
-
origin: true,
-
}),
-
)
-
.derive(({ headers }) => ({
-
startTime: Date.now(),
-
userAgent: headers["user-agent"],
-
ipAddress: headers["x-forwarded-for"] || headers["x-real-ip"] || "unknown",
-
}))
-
.onAfterHandle(async ({ request, set, startTime, userAgent, ipAddress }) => {
-
const responseTime = Date.now() - startTime;
-
const endpoint = new URL(request.url).pathname;
+
// Setup cron jobs
+
setupCronJobs();
-
// Don't track favicon or swagger requests
-
if (endpoint !== "/favicon.ico" && !endpoint.startsWith("/swagger")) {
-
await cache.recordRequest(
-
endpoint,
-
request.method,
-
(set.status as number) || 200,
-
userAgent,
-
ipAddress,
-
responseTime,
-
);
-
}
-
})
-
.use(
-
cron({
-
name: "heartbeat",
-
pattern: "0 0 * * *",
-
async run() {
-
await cache.purgeAll();
-
},
-
}),
-
)
-
.use(
-
cron({
-
name: "purgeSpecificUserCache",
-
pattern: "5 * * * *", // Run at 5 minutes after each hour
-
async run() {
-
const userId = "U062UG485EE";
-
console.log(`Purging cache for user ${userId}`);
-
const result = await cache.purgeUserCache(userId);
-
console.log(
-
`Cache purge for user ${userId}: ${result ? "successful" : "no cache entry found"}`,
+
// Start the server
+
const server = serve({
+
routes: {
+
// HTML routes
+
"/dashboard": dashboard,
+
"/swagger": swagger,
+
"/swagger.json": async (request) => {
+
return Response.json(swaggerSpec);
+
},
+
"/favicon.ico": async (request) => {
+
return new Response(Bun.file("./favicon.ico"));
+
},
+
+
// Root route - redirect to dashboard for browsers
+
"/": async (request) => {
+
const startTime = Date.now();
+
const recordAnalytics = async (statusCode: number) => {
+
const userAgent = request.headers.get("user-agent") || "";
+
const ipAddress =
+
request.headers.get("x-forwarded-for") ||
+
request.headers.get("x-real-ip") ||
+
"unknown";
+
+
await cache.recordRequest(
+
"/",
+
request.method,
+
statusCode,
+
userAgent,
+
ipAddress,
+
Date.now() - startTime,
);
-
},
-
}),
-
)
-
.use(
-
swagger({
-
exclude: ["/", "favicon.ico"],
-
documentation: {
-
info: {
-
version: version,
-
title: "Cachet",
-
description:
-
"Hi 👋\n\nThis is a pretty simple API that acts as a middleman caching layer between slack and the outside world. There may be authentication in the future, but for now, it's just a simple cache.\n\nThe `/r` endpoints are redirects to the actual image URLs, so you can use them as direct image links.",
-
contact: {
-
name: "Kieran Klukas",
-
email: "me@dunkirk.sh",
-
},
-
license: {
-
name: "AGPL 3.0",
-
url: "https://github.com/taciturnaxoltol/cachet/blob/master/LICENSE.md",
-
},
-
},
-
tags: [
-
{
-
name: "The Cache!",
-
description: "*must be read in an ominous voice*",
-
},
-
{
-
name: "Status",
-
description: "*Rather boring status endpoints :(*",
-
},
-
],
-
},
-
}),
-
)
-
.onError(({ code, error, request, set }) => {
-
if (error instanceof Error)
-
console.error(
-
`\x1b[31m x\x1b[0m unhandled error: \x1b[31m${error.message}\x1b[0m`,
+
};
+
+
const userAgent = request.headers.get("user-agent") || "";
+
if (
+
userAgent.toLowerCase().includes("mozilla") ||
+
userAgent.toLowerCase().includes("chrome") ||
+
userAgent.toLowerCase().includes("safari")
+
) {
+
recordAnalytics(302);
+
return new Response(null, {
+
status: 302,
+
headers: { Location: "/dashboard" },
+
});
+
}
+
+
recordAnalytics(200);
+
return new Response(
+
"Hello World from Cachet 😊\n\n---\nSee /swagger for docs\nSee /dashboard for analytics\n---",
);
+
},
-
// Don't send 404 errors to Sentry
-
const is404 =
-
set.status === 404 ||
-
(error instanceof Error &&
-
(error.message === "Not Found" ||
-
error.message === "user_not_found" ||
-
error.message === "emoji_not_found"));
+
// Health check endpoint
+
"/health": {
+
async GET(request) {
+
const startTime = Date.now();
+
const recordAnalytics = async (statusCode: number) => {
+
const userAgent = request.headers.get("user-agent") || "";
+
const ipAddress =
+
request.headers.get("x-forwarded-for") ||
+
request.headers.get("x-real-ip") ||
+
"unknown";
-
if (!is404) {
-
Sentry.withScope((scope) => {
-
scope.setExtra("url", request.url);
-
scope.setExtra("code", code);
-
Sentry.captureException(error);
-
});
-
}
+
await cache.recordRequest(
+
"/health",
+
"GET",
+
statusCode,
+
userAgent,
+
ipAddress,
+
Date.now() - startTime,
+
);
+
};
-
if (code === "VALIDATION") {
-
return error.message;
-
}
-
})
-
.get("/", ({ redirect, headers }) => {
-
// check if its a browser
+
return handleHealthCheck(request, recordAnalytics);
+
},
+
},
-
if (
-
headers["user-agent"]?.toLowerCase().includes("mozilla") ||
-
headers["user-agent"]?.toLowerCase().includes("chrome") ||
-
headers["user-agent"]?.toLowerCase().includes("safari")
-
) {
-
return redirect("/dashboard", 302);
-
}
+
// User endpoints
+
"/users/:id": {
+
async GET(request) {
+
const startTime = Date.now();
+
const recordAnalytics = async (statusCode: number) => {
+
const userAgent = request.headers.get("user-agent") || "";
+
const ipAddress =
+
request.headers.get("x-forwarded-for") ||
+
request.headers.get("x-real-ip") ||
+
"unknown";
-
return "Hello World from Cachet 😊\n\n---\nSee /swagger for docs\nSee /dashboard for analytics\n---";
-
})
-
.get("/favicon.ico", Bun.file("./favicon.ico"))
-
.get("/dashboard", () => dashboard)
-
.get(
-
"/health",
-
async ({ error }) => {
-
const slackConnection = await slackApp.testAuth();
+
await cache.recordRequest(
+
request.url,
+
"GET",
+
statusCode,
+
userAgent,
+
ipAddress,
+
Date.now() - startTime,
+
);
+
};
-
const databaseConnection = await cache.healthCheck();
+
return handleGetUser(request, recordAnalytics);
+
},
+
},
-
if (!slackConnection || !databaseConnection)
-
return error(500, {
-
http: false,
-
slack: slackConnection,
-
database: databaseConnection,
-
});
+
"/users/:id/r": {
+
async GET(request) {
+
const startTime = Date.now();
+
const recordAnalytics = async (statusCode: number) => {
+
const userAgent = request.headers.get("user-agent") || "";
+
const ipAddress =
+
request.headers.get("x-forwarded-for") ||
+
request.headers.get("x-real-ip") ||
+
"unknown";
-
return {
-
http: true,
-
slack: true,
-
database: true,
-
};
+
await cache.recordRequest(
+
request.url,
+
"GET",
+
statusCode,
+
userAgent,
+
ipAddress,
+
Date.now() - startTime,
+
);
+
};
+
+
return handleUserRedirect(request, recordAnalytics);
+
},
},
-
{
-
tags: ["Status"],
-
response: {
-
200: t.Object({
-
http: t.Boolean(),
-
slack: t.Boolean(),
-
database: t.Boolean(),
-
}),
-
500: t.Object({
-
http: t.Boolean({
-
default: false,
-
}),
-
slack: t.Boolean({
-
default: false,
-
}),
-
database: t.Boolean({
-
default: false,
-
}),
-
}),
+
+
"/users/:id/purge": {
+
async POST(request) {
+
const startTime = Date.now();
+
const recordAnalytics = async (statusCode: number) => {
+
const userAgent = request.headers.get("user-agent") || "";
+
const ipAddress =
+
request.headers.get("x-forwarded-for") ||
+
request.headers.get("x-real-ip") ||
+
"unknown";
+
+
await cache.recordRequest(
+
request.url,
+
"POST",
+
statusCode,
+
userAgent,
+
ipAddress,
+
Date.now() - startTime,
+
);
+
};
+
+
return handlePurgeUser(request, recordAnalytics);
},
},
-
)
-
.get(
-
"/users/:user",
-
async ({ params, error, request }) => {
-
const user = await cache.getUser(params.user);
-
// if not found then check slack first
-
if (!user || !user.imageUrl) {
-
let slackUser: SlackUser;
-
try {
-
slackUser = await slackApp.getUserInfo(params.user);
-
} catch (e) {
-
if (e instanceof Error && e.message === "user_not_found")
-
return error(404, { message: "User not found" });
+
// Emoji endpoints
+
"/emojis": {
+
async GET(request) {
+
const startTime = Date.now();
+
const recordAnalytics = async (statusCode: number) => {
+
const userAgent = request.headers.get("user-agent") || "";
+
const ipAddress =
+
request.headers.get("x-forwarded-for") ||
+
request.headers.get("x-real-ip") ||
+
"unknown";
-
Sentry.withScope((scope) => {
-
scope.setExtra("url", request.url);
-
scope.setExtra("user", params.user);
-
Sentry.captureException(e);
-
});
+
await cache.recordRequest(
+
"/emojis",
+
"GET",
+
statusCode,
+
userAgent,
+
ipAddress,
+
Date.now() - startTime,
+
);
+
};
-
if (e instanceof Error)
-
console.warn(
-
`\x1b[38;5;214m ⚠️ WARN\x1b[0m error on fetching user from slack: \x1b[38;5;208m${e.message}\x1b[0m`,
-
);
+
return handleListEmojis(request, recordAnalytics);
+
},
+
},
-
return error(500, {
-
message: `Error fetching user from Slack: ${e}`,
-
});
-
}
+
"/emojis/:name": {
+
async GET(request) {
+
const startTime = Date.now();
+
const recordAnalytics = async (statusCode: number) => {
+
const userAgent = request.headers.get("user-agent") || "";
+
const ipAddress =
+
request.headers.get("x-forwarded-for") ||
+
request.headers.get("x-real-ip") ||
+
"unknown";
-
const displayName =
-
slackUser.profile.display_name_normalized ||
-
slackUser.profile.real_name_normalized;
+
await cache.recordRequest(
+
request.url,
+
"GET",
+
statusCode,
+
userAgent,
+
ipAddress,
+
Date.now() - startTime,
+
);
+
};
-
await cache.insertUser(
-
slackUser.id,
-
displayName,
-
slackUser.profile.pronouns,
-
slackUser.profile.image_512,
-
);
+
return handleGetEmoji(request, recordAnalytics);
+
},
+
},
-
return {
-
id: slackUser.id,
-
expiration: new Date().toISOString(),
-
user: slackUser.id,
-
displayName: displayName,
-
pronouns: slackUser.profile.pronouns || null,
-
image: slackUser.profile.image_512,
+
"/emojis/:name/r": {
+
async GET(request) {
+
const startTime = Date.now();
+
const recordAnalytics = async (statusCode: number) => {
+
const userAgent = request.headers.get("user-agent") || "";
+
const ipAddress =
+
request.headers.get("x-forwarded-for") ||
+
request.headers.get("x-real-ip") ||
+
"unknown";
+
+
await cache.recordRequest(
+
request.url,
+
"GET",
+
statusCode,
+
userAgent,
+
ipAddress,
+
Date.now() - startTime,
+
);
};
-
}
-
return {
-
id: user.id,
-
expiration: user.expiration.toISOString(),
-
user: user.userId,
-
displayName: user.displayName,
-
pronouns: user.pronouns,
-
image: user.imageUrl,
-
};
+
return handleEmojiRedirect(request, recordAnalytics);
+
},
},
-
{
-
tags: ["The Cache!"],
-
params: t.Object({
-
user: t.String(),
-
}),
-
response: {
-
404: t.Object({
-
message: t.String({
-
default: "User not found",
-
}),
-
}),
-
500: t.Object({
-
message: t.String({
-
default: "Error fetching user from Slack",
-
}),
-
}),
-
200: t.Object({
-
id: t.String({
-
default: "90750e24-c2f0-4c52-8681-e6176da6e7ab",
-
}),
-
expiration: t.String({
-
default: new Date().toISOString(),
-
}),
-
user: t.String({
-
default: "U12345678",
-
}),
-
displayName: t.String({
-
default: "krn",
-
}),
-
pronouns: t.Nullable(t.String({ default: "possibly/blank" })),
-
image: t.String({
-
default:
-
"https://avatars.slack-edge.com/2024-11-30/8105375749571_53898493372773a01a1f_original.jpg",
-
}),
-
}),
+
+
// Reset cache endpoint
+
"/reset": {
+
async POST(request) {
+
const startTime = Date.now();
+
const recordAnalytics = async (statusCode: number) => {
+
const userAgent = request.headers.get("user-agent") || "";
+
const ipAddress =
+
request.headers.get("x-forwarded-for") ||
+
request.headers.get("x-real-ip") ||
+
"unknown";
+
+
await cache.recordRequest(
+
"/reset",
+
"POST",
+
statusCode,
+
userAgent,
+
ipAddress,
+
Date.now() - startTime,
+
);
+
};
+
+
return handleResetCache(request, recordAnalytics);
},
},
-
)
-
.get(
-
"/users/:user/r",
-
async ({ params, error, redirect, request }) => {
-
const user = await cache.getUser(params.user);
-
// if not found then check slack first
-
if (!user || !user.imageUrl) {
-
let slackUser: SlackUser;
-
try {
-
slackUser = await slackApp.getUserInfo(params.user.toUpperCase());
-
} catch (e) {
-
if (e instanceof Error && e.message === "user_not_found") {
-
console.warn(
-
`\x1b[38;5;214m ⚠️ WARN\x1b[0m user not found: \x1b[38;5;208m${params.user}\x1b[0m`,
-
);
+
// Stats endpoint
+
"/stats": {
+
async GET(request) {
+
const startTime = Date.now();
+
const recordAnalytics = async (statusCode: number) => {
+
const userAgent = request.headers.get("user-agent") || "";
+
const ipAddress =
+
request.headers.get("x-forwarded-for") ||
+
request.headers.get("x-real-ip") ||
+
"unknown";
-
return redirect(
-
"https://api.dicebear.com/9.x/thumbs/svg?seed={username_hash}",
-
307,
-
);
-
}
+
await cache.recordRequest(
+
"/stats",
+
"GET",
+
statusCode,
+
userAgent,
+
ipAddress,
+
Date.now() - startTime,
+
);
+
};
-
Sentry.withScope((scope) => {
-
scope.setExtra("url", request.url);
-
scope.setExtra("user", params.user);
-
Sentry.captureException(e);
-
});
+
return handleGetStats(request, recordAnalytics);
+
},
+
},
+
},
-
if (e instanceof Error)
-
console.warn(
-
`\x1b[38;5;214m ⚠️ WARN\x1b[0m error on fetching user from slack: \x1b[38;5;208m${e.message}\x1b[0m`,
-
);
+
// Enable development mode for hot reloading
+
development: {
+
hmr: true,
+
console: true,
+
},
-
return error(500, {
-
message: `Error fetching user from Slack: ${e}`,
-
});
-
}
+
// Fallback fetch handler for unmatched routes and error handling
+
async fetch(request) {
+
const url = new URL(request.url);
+
const path = url.pathname;
+
const method = request.method;
+
const startTime = Date.now();
-
await cache.insertUser(
-
slackUser.id,
-
slackUser.profile.display_name_normalized ||
-
slackUser.profile.real_name_normalized,
-
slackUser.profile.pronouns,
-
slackUser.profile.image_512,
+
// Record request analytics (except for favicon and swagger)
+
const recordAnalytics = async (statusCode: number) => {
+
if (path !== "/favicon.ico" && !path.startsWith("/swagger")) {
+
const userAgent = request.headers.get("user-agent") || "";
+
const ipAddress =
+
request.headers.get("x-forwarded-for") ||
+
request.headers.get("x-real-ip") ||
+
"unknown";
+
+
await cache.recordRequest(
+
path,
+
method,
+
statusCode,
+
userAgent,
+
ipAddress,
+
Date.now() - startTime,
);
+
}
+
};
-
return redirect(slackUser.profile.image_512, 302);
+
try {
+
// Not found
+
recordAnalytics(404);
+
return new Response("Not Found", { status: 404 });
+
} catch (error) {
+
console.error(
+
`\x1b[31m x\x1b[0m unhandled error: \x1b[31m${error instanceof Error ? error.message : String(error)}\x1b[0m`,
+
);
+
+
// Don't send 404 errors to Sentry
+
const is404 =
+
error instanceof Error &&
+
(error.message === "Not Found" ||
+
error.message === "user_not_found" ||
+
error.message === "emoji_not_found");
+
+
if (!is404 && error instanceof Error) {
+
Sentry.withScope((scope) => {
+
scope.setExtra("url", request.url);
+
Sentry.captureException(error);
+
});
}
-
return redirect(user.imageUrl, 302);
-
},
-
{
-
tags: ["The Cache!"],
-
query: t.Object({
-
r: t.Optional(t.String()),
-
}),
-
params: t.Object({
-
user: t.String(),
-
}),
-
},
-
)
-
.get(
-
"/emojis",
-
async () => {
-
const emojis = await cache.listEmojis();
+
recordAnalytics(500);
+
return new Response("Internal Server Error", { status: 500 });
+
}
+
},
-
return emojis.map((emoji) => ({
-
id: emoji.id,
-
expiration: emoji.expiration.toISOString(),
-
name: emoji.name,
-
...(emoji.alias ? { alias: emoji.alias } : {}),
-
image: emoji.imageUrl,
-
}));
-
},
-
{
-
tags: ["The Cache!"],
-
response: {
-
200: t.Array(
-
t.Object({
-
id: t.String({
-
default: "5427fe70-686f-4684-9da5-95d9ef4c1090",
-
}),
-
expiration: t.String({
-
default: new Date().toISOString(),
-
}),
-
name: t.String({
-
default: "blahaj-heart",
-
}),
-
alias: t.Optional(
-
t.String({
-
default: "blobhaj-heart",
-
}),
-
),
-
image: t.String({
-
default:
-
"https://emoji.slack-edge.com/T0266FRGM/blahaj-heart/db9adf8229e9a4fb.png",
-
}),
-
}),
-
),
-
},
-
},
-
)
-
.get(
-
"/emojis/:emoji",
-
async ({ params, error }) => {
-
const emoji = await cache.getEmoji(params.emoji);
+
port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
+
});
-
if (!emoji) return error(404, { message: "Emoji not found" });
+
console.log(
+
`\n---\n\n🐰 Bun server is running at ${server.url} on ${process.env.NODE_ENV}\n\n---\n`,
+
);
-
return {
-
id: emoji.id,
-
expiration: emoji.expiration.toISOString(),
-
name: emoji.name,
-
...(emoji.alias ? { alias: emoji.alias } : {}),
-
image: emoji.imageUrl,
-
};
-
},
-
{
-
tags: ["The Cache!"],
-
params: t.Object({
-
emoji: t.String(),
-
}),
-
response: {
-
404: t.Object({
-
message: t.String({
-
default: "Emoji not found",
-
}),
-
}),
-
200: t.Object({
-
id: t.String({
-
default: "9ed0a560-928d-409c-89fc-10fe156299da",
-
}),
-
expiration: t.String({
-
default: new Date().toISOString(),
-
}),
-
name: t.String({
-
default: "orphmoji-yay",
-
}),
-
image: t.String({
-
default:
-
"https://emoji.slack-edge.com/T0266FRGM/orphmoji-yay/23a37f4af47092d3.png",
-
}),
-
}),
+
// Handler functions
+
async function handleHealthCheck(
+
request: Request,
+
recordAnalytics: (statusCode: number) => Promise<void>,
+
) {
+
const slackConnection = await slackApp.testAuth();
+
const databaseConnection = await cache.healthCheck();
+
+
if (!slackConnection || !databaseConnection) {
+
await recordAnalytics(500);
+
return Response.json(
+
{
+
http: false,
+
slack: slackConnection,
+
database: databaseConnection,
},
-
},
-
)
-
.get(
-
"/emojis/:emoji/r",
-
async ({ params, error, redirect }) => {
-
const emoji = await cache.getEmoji(params.emoji);
+
{ status: 500 },
+
);
+
}
-
if (!emoji) return error(404, { message: "Emoji not found" });
+
await recordAnalytics(200);
+
return Response.json({
+
http: true,
+
slack: true,
+
database: true,
+
});
+
}
-
return redirect(emoji.imageUrl, 302);
-
},
-
{
-
tags: ["The Cache!"],
-
params: t.Object({
-
emoji: t.String(),
-
}),
-
},
-
)
-
.post(
-
"/reset",
-
async ({ headers, set }) => {
-
if (headers.authorization !== `Bearer ${process.env.BEARER_TOKEN}`) {
-
set.status = 401;
-
return "Unauthorized";
+
async function handleGetUser(
+
request: Request,
+
recordAnalytics: (statusCode: number) => Promise<void>,
+
) {
+
const url = new URL(request.url);
+
const userId = url.pathname.split("/").pop() || "";
+
const user = await cache.getUser(userId);
+
+
// If not found then check slack first
+
if (!user || !user.imageUrl) {
+
let slackUser: SlackUser;
+
try {
+
slackUser = await slackApp.getUserInfo(userId);
+
} catch (e) {
+
if (e instanceof Error && e.message === "user_not_found") {
+
await recordAnalytics(404);
+
return Response.json({ message: "User not found" }, { status: 404 });
}
-
return await cache.purgeAll();
-
},
-
{
-
tags: ["The Cache!"],
-
headers: t.Object({
-
authorization: t.String({
-
default: "Bearer <token>",
-
}),
-
}),
-
response: {
-
200: t.Object({
-
message: t.String(),
-
users: t.Number(),
-
emojis: t.Number(),
-
}),
-
401: t.String({ default: "Unauthorized" }),
-
},
-
},
-
)
-
.post(
-
"/users/:user/purge",
-
async ({ headers, params, set }) => {
-
if (headers.authorization !== `Bearer ${process.env.BEARER_TOKEN}`) {
-
set.status = 401;
-
return "Unauthorized";
+
Sentry.withScope((scope) => {
+
scope.setExtra("url", request.url);
+
scope.setExtra("user", userId);
+
Sentry.captureException(e);
+
});
+
+
if (e instanceof Error)
+
console.warn(
+
`\x1b[38;5;214m ⚠️ WARN\x1b[0m error on fetching user from slack: \x1b[38;5;208m${e.message}\x1b[0m`,
+
);
+
+
await recordAnalytics(500);
+
return Response.json(
+
{ message: `Error fetching user from Slack: ${e}` },
+
{ status: 500 },
+
);
+
}
+
+
const displayName =
+
slackUser.profile.display_name_normalized ||
+
slackUser.profile.real_name_normalized;
+
+
await cache.insertUser(
+
slackUser.id,
+
displayName,
+
slackUser.profile.pronouns,
+
slackUser.profile.image_512,
+
);
+
+
await recordAnalytics(200);
+
return Response.json({
+
id: slackUser.id,
+
expiration: new Date().toISOString(),
+
user: slackUser.id,
+
displayName: displayName,
+
pronouns: slackUser.profile.pronouns || null,
+
image: slackUser.profile.image_512,
+
});
+
}
+
+
await recordAnalytics(200);
+
return Response.json({
+
id: user.id,
+
expiration: user.expiration.toISOString(),
+
user: user.userId,
+
displayName: user.displayName,
+
pronouns: user.pronouns,
+
image: user.imageUrl,
+
});
+
}
+
+
async function handleUserRedirect(
+
request: Request,
+
recordAnalytics: (statusCode: number) => Promise<void>,
+
) {
+
const url = new URL(request.url);
+
const parts = url.pathname.split("/");
+
const userId = parts[2] || "";
+
const user = await cache.getUser(userId);
+
+
// If not found then check slack first
+
if (!user || !user.imageUrl) {
+
let slackUser: SlackUser;
+
try {
+
slackUser = await slackApp.getUserInfo(userId.toUpperCase());
+
} catch (e) {
+
if (e instanceof Error && e.message === "user_not_found") {
+
console.warn(
+
`\x1b[38;5;214m ⚠️ WARN\x1b[0m user not found: \x1b[38;5;208m${userId}\x1b[0m`,
+
);
+
+
await recordAnalytics(307);
+
return new Response(null, {
+
status: 307,
+
headers: {
+
Location:
+
"https://api.dicebear.com/9.x/thumbs/svg?seed={username_hash}",
+
},
+
});
}
-
const success = await cache.purgeUserCache(params.user);
+
Sentry.withScope((scope) => {
+
scope.setExtra("url", request.url);
+
scope.setExtra("user", userId);
+
Sentry.captureException(e);
+
});
+
+
if (e instanceof Error)
+
console.warn(
+
`\x1b[38;5;214m ⚠️ WARN\x1b[0m error on fetching user from slack: \x1b[38;5;208m${e.message}\x1b[0m`,
+
);
+
+
await recordAnalytics(500);
+
return Response.json(
+
{ message: `Error fetching user from Slack: ${e}` },
+
{ status: 500 },
+
);
+
}
+
+
await cache.insertUser(
+
slackUser.id,
+
slackUser.profile.display_name_normalized ||
+
slackUser.profile.real_name_normalized,
+
slackUser.profile.pronouns,
+
slackUser.profile.image_512,
+
);
+
+
await recordAnalytics(302);
+
return new Response(null, {
+
status: 302,
+
headers: { Location: slackUser.profile.image_512 },
+
});
+
}
+
+
await recordAnalytics(302);
+
return new Response(null, {
+
status: 302,
+
headers: { Location: user.imageUrl },
+
});
+
}
+
+
async function handleListEmojis(
+
request: Request,
+
recordAnalytics: (statusCode: number) => Promise<void>,
+
) {
+
const emojis = await cache.listEmojis();
-
return {
-
message: success ? "User cache purged" : "User not found in cache",
-
userId: params.user,
-
success,
-
};
-
},
-
{
-
tags: ["The Cache!"],
-
headers: t.Object({
-
authorization: t.String({
-
default: "Bearer <token>",
-
}),
-
}),
-
params: t.Object({
-
user: t.String(),
-
}),
-
response: {
-
200: t.Object({
-
message: t.String(),
-
userId: t.String(),
-
success: t.Boolean(),
-
}),
-
401: t.String({ default: "Unauthorized" }),
-
},
-
},
-
)
-
.get(
-
"/stats",
-
async ({ query }) => {
-
const days = query.days ? parseInt(query.days) : 7;
-
const analytics = await cache.getAnalytics(days);
+
await recordAnalytics(200);
+
return Response.json(
+
emojis.map((emoji) => ({
+
id: emoji.id,
+
expiration: emoji.expiration.toISOString(),
+
name: emoji.name,
+
...(emoji.alias ? { alias: emoji.alias } : {}),
+
image: emoji.imageUrl,
+
})),
+
);
+
}
+
+
async function handleGetEmoji(
+
request: Request,
+
recordAnalytics: (statusCode: number) => Promise<void>,
+
) {
+
const url = new URL(request.url);
+
const emojiName = url.pathname.split("/").pop() || "";
+
const emoji = await cache.getEmoji(emojiName);
+
+
if (!emoji) {
+
await recordAnalytics(404);
+
return Response.json({ message: "Emoji not found" }, { status: 404 });
+
}
+
+
await recordAnalytics(200);
+
return Response.json({
+
id: emoji.id,
+
expiration: emoji.expiration.toISOString(),
+
name: emoji.name,
+
...(emoji.alias ? { alias: emoji.alias } : {}),
+
image: emoji.imageUrl,
+
});
+
}
+
+
async function handleEmojiRedirect(
+
request: Request,
+
recordAnalytics: (statusCode: number) => Promise<void>,
+
) {
+
const url = new URL(request.url);
+
const parts = url.pathname.split("/");
+
const emojiName = parts[2] || "";
+
const emoji = await cache.getEmoji(emojiName);
+
+
if (!emoji) {
+
await recordAnalytics(404);
+
return Response.json({ message: "Emoji not found" }, { status: 404 });
+
}
+
+
await recordAnalytics(302);
+
return new Response(null, {
+
status: 302,
+
headers: { Location: emoji.imageUrl },
+
});
+
}
+
+
async function handleResetCache(
+
request: Request,
+
recordAnalytics: (statusCode: number) => Promise<void>,
+
) {
+
const authHeader = request.headers.get("authorization") || "";
+
+
if (authHeader !== `Bearer ${process.env.BEARER_TOKEN}`) {
+
await recordAnalytics(401);
+
return new Response("Unauthorized", { status: 401 });
+
}
+
+
const result = await cache.purgeAll();
+
await recordAnalytics(200);
+
return Response.json(result);
+
}
+
+
async function handlePurgeUser(
+
request: Request,
+
recordAnalytics: (statusCode: number) => Promise<void>,
+
) {
+
const authHeader = request.headers.get("authorization") || "";
+
+
if (authHeader !== `Bearer ${process.env.BEARER_TOKEN}`) {
+
await recordAnalytics(401);
+
return new Response("Unauthorized", { status: 401 });
+
}
+
+
const url = new URL(request.url);
+
const parts = url.pathname.split("/");
+
const userId = parts[2] || "";
+
const success = await cache.purgeUserCache(userId);
+
+
await recordAnalytics(200);
+
return Response.json({
+
message: success ? "User cache purged" : "User not found in cache",
+
userId: userId,
+
success,
+
});
+
}
+
+
async function handleGetStats(
+
request: Request,
+
recordAnalytics: (statusCode: number) => Promise<void>,
+
) {
+
const url = new URL(request.url);
+
const params = new URLSearchParams(url.search);
+
const days = params.get("days") ? parseInt(params.get("days")!) : 7;
+
const analytics = await cache.getAnalytics(days);
+
+
await recordAnalytics(200);
+
return Response.json(analytics);
+
}
+
+
// Setup cron jobs for cache maintenance
+
function setupCronJobs() {
+
// Daily purge of all expired items
+
const dailyPurge = setInterval(async () => {
+
const now = new Date();
+
if (now.getHours() === 0 && now.getMinutes() === 0) {
+
await cache.purgeAll();
+
}
+
}, 60 * 1000); // Check every minute
-
return analytics;
-
},
-
{
-
tags: ["Status"],
-
query: t.Object({
-
days: t.Optional(
-
t.String({ description: "Number of days to look back (default: 7)" }),
-
),
-
}),
-
response: {
-
200: t.Object({
-
totalRequests: t.Number(),
-
requestsByEndpoint: t.Array(
-
t.Object({
-
endpoint: t.String(),
-
count: t.Number(),
-
averageResponseTime: t.Number(),
-
}),
-
),
-
requestsByStatus: t.Array(
-
t.Object({
-
status: t.Number(),
-
count: t.Number(),
-
averageResponseTime: t.Number(),
-
}),
-
),
-
requestsByDay: t.Array(
-
t.Object({
-
date: t.String(),
-
count: t.Number(),
-
averageResponseTime: t.Number(),
-
}),
-
),
-
averageResponseTime: t.Nullable(t.Number()),
-
topUserAgents: t.Array(
-
t.Object({
-
userAgent: t.String(),
-
count: t.Number(),
-
}),
-
),
-
latencyAnalytics: t.Object({
-
percentiles: t.Object({
-
p50: t.Nullable(t.Number()),
-
p75: t.Nullable(t.Number()),
-
p90: t.Nullable(t.Number()),
-
p95: t.Nullable(t.Number()),
-
p99: t.Nullable(t.Number()),
-
}),
-
distribution: t.Array(
-
t.Object({
-
range: t.String(),
-
count: t.Number(),
-
percentage: t.Number(),
-
}),
-
),
-
slowestEndpoints: t.Array(
-
t.Object({
-
endpoint: t.String(),
-
averageResponseTime: t.Number(),
-
count: t.Number(),
-
}),
-
),
-
latencyOverTime: t.Array(
-
t.Object({
-
time: t.String(),
-
averageResponseTime: t.Number(),
-
p95: t.Nullable(t.Number()),
-
count: t.Number(),
-
}),
-
),
-
}),
-
performanceMetrics: t.Object({
-
uptime: t.Number(),
-
errorRate: t.Number(),
-
throughput: t.Number(),
-
apdex: t.Number(),
-
cachehitRate: t.Number(),
-
}),
-
peakTraffic: t.Object({
-
peakHour: t.String(),
-
peakRequests: t.Number(),
-
peakDay: t.String(),
-
peakDayRequests: t.Number(),
-
}),
-
dashboardMetrics: t.Object({
-
statsRequests: t.Number(),
-
totalWithStats: t.Number(),
-
}),
-
trafficOverview: t.Array(
-
t.Object({
-
time: t.String(),
-
routes: t.Record(t.String(), t.Number()),
-
total: t.Number(),
-
}),
-
),
-
}),
-
},
-
},
-
)
-
.listen(process.env.PORT ?? 3000);
+
// Hourly purge of specific user cache
+
const hourlyUserPurge = setInterval(async () => {
+
const now = new Date();
+
if (now.getMinutes() === 5) {
+
const userId = "U062UG485EE";
+
console.log(`Purging cache for user ${userId}`);
+
const result = await cache.purgeUserCache(userId);
+
console.log(
+
`Cache purge for user ${userId}: ${result ? "successful" : "no cache entry found"}`,
+
);
+
}
+
}, 60 * 1000); // Check every minute
-
console.log(
-
`\n---\n\n🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port} on v${version}@${process.env.NODE_ENV}\n\n---\n`,
-
);
+
// Clean up on process exit
+
process.on("exit", () => {
+
clearInterval(dailyPurge);
+
clearInterval(hourlyUserPurge);
+
});
+
}
+53
src/swagger.html
···
+
<!DOCTYPE html>
+
<html lang="en">
+
<head>
+
<meta charset="UTF-8">
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+
<title>Cachet API Documentation</title>
+
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui.css">
+
<style>
+
html {
+
box-sizing: border-box;
+
overflow: -moz-scrollbars-vertical;
+
overflow-y: scroll;
+
}
+
+
*,
+
*:before,
+
*:after {
+
box-sizing: inherit;
+
}
+
+
body {
+
margin: 0;
+
background: #fafafa;
+
}
+
</style>
+
</head>
+
<body>
+
<div id="swagger-ui"></div>
+
+
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui-bundle.js"></script>
+
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.9.0/swagger-ui-standalone-preset.js"></script>
+
<script>
+
window.onload = function() {
+
// Begin Swagger UI call region
+
const ui = SwaggerUIBundle({
+
url: "/swagger.json",
+
dom_id: '#swagger-ui',
+
deepLinking: true,
+
presets: [
+
SwaggerUIBundle.presets.apis,
+
SwaggerUIStandalonePreset
+
],
+
plugins: [
+
SwaggerUIBundle.plugins.DownloadUrl
+
],
+
layout: "StandaloneLayout"
+
});
+
// End Swagger UI call region
+
window.ui = ui;
+
};
+
</script>
+
</body>
+
</html>
+554
src/swagger.ts
···
+
import { serve } from "bun";
+
import { version } from "../package.json";
+
+
// Define the Swagger specification
+
const swaggerSpec = {
+
openapi: "3.0.0",
+
info: {
+
title: "Cachet",
+
version: version,
+
description:
+
"Hi 👋\n\nThis is a pretty simple API that acts as a middleman caching layer between slack and the outside world. There may be authentication in the future, but for now, it's just a simple cache.\n\nThe `/r` endpoints are redirects to the actual image URLs, so you can use them as direct image links.",
+
contact: {
+
name: "Kieran Klukas",
+
email: "me@dunkirk.sh",
+
},
+
license: {
+
name: "AGPL 3.0",
+
url: "https://github.com/taciturnaxoltol/cachet/blob/master/LICENSE.md",
+
},
+
},
+
tags: [
+
{
+
name: "The Cache!",
+
description: "*must be read in an ominous voice*",
+
},
+
{
+
name: "Status",
+
description: "*Rather boring status endpoints :(*",
+
},
+
],
+
paths: {
+
"/users/{user}": {
+
get: {
+
tags: ["The Cache!"],
+
summary: "Get user information",
+
description:
+
"Retrieves user information from the cache or from Slack if not cached",
+
parameters: [
+
{
+
name: "user",
+
in: "path",
+
required: true,
+
schema: {
+
type: "string",
+
},
+
description: "Slack user ID",
+
},
+
],
+
responses: {
+
"200": {
+
description: "User information",
+
content: {
+
"application/json": {
+
schema: {
+
type: "object",
+
properties: {
+
id: {
+
type: "string",
+
example: "90750e24-c2f0-4c52-8681-e6176da6e7ab",
+
},
+
expiration: {
+
type: "string",
+
format: "date-time",
+
example: new Date().toISOString(),
+
},
+
user: {
+
type: "string",
+
example: "U12345678",
+
},
+
displayName: {
+
type: "string",
+
example: "krn",
+
},
+
pronouns: {
+
type: "string",
+
nullable: true,
+
example: "possibly/blank",
+
},
+
image: {
+
type: "string",
+
example:
+
"https://avatars.slack-edge.com/2024-11-30/8105375749571_53898493372773a01a1f_original.jpg",
+
},
+
},
+
},
+
},
+
},
+
},
+
"404": {
+
description: "User not found",
+
content: {
+
"application/json": {
+
schema: {
+
type: "object",
+
properties: {
+
message: {
+
type: "string",
+
example: "User not found",
+
},
+
},
+
},
+
},
+
},
+
},
+
"500": {
+
description: "Error fetching user from Slack",
+
content: {
+
"application/json": {
+
schema: {
+
type: "object",
+
properties: {
+
message: {
+
type: "string",
+
example: "Error fetching user from Slack",
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
"/users/{user}/r": {
+
get: {
+
tags: ["The Cache!"],
+
summary: "Redirect to user profile image",
+
description: "Redirects to the user's profile image URL",
+
parameters: [
+
{
+
name: "user",
+
in: "path",
+
required: true,
+
schema: {
+
type: "string",
+
},
+
description: "Slack user ID",
+
},
+
],
+
responses: {
+
"302": {
+
description: "Redirect to user profile image",
+
},
+
"307": {
+
description: "Redirect to default image when user not found",
+
},
+
"500": {
+
description: "Error fetching user from Slack",
+
content: {
+
"application/json": {
+
schema: {
+
type: "object",
+
properties: {
+
message: {
+
type: "string",
+
example: "Error fetching user from Slack",
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
"/users/{user}/purge": {
+
post: {
+
tags: ["The Cache!"],
+
summary: "Purge user cache",
+
description: "Purges a specific user's cache",
+
parameters: [
+
{
+
name: "user",
+
in: "path",
+
required: true,
+
schema: {
+
type: "string",
+
},
+
description: "Slack user ID",
+
},
+
{
+
name: "authorization",
+
in: "header",
+
required: true,
+
schema: {
+
type: "string",
+
example: "Bearer <token>",
+
},
+
description: "Bearer token for authentication",
+
},
+
],
+
responses: {
+
"200": {
+
description: "User cache purged",
+
content: {
+
"application/json": {
+
schema: {
+
type: "object",
+
properties: {
+
message: {
+
type: "string",
+
example: "User cache purged",
+
},
+
userId: {
+
type: "string",
+
example: "U12345678",
+
},
+
success: {
+
type: "boolean",
+
example: true,
+
},
+
},
+
},
+
},
+
},
+
},
+
"401": {
+
description: "Unauthorized",
+
content: {
+
"text/plain": {
+
schema: {
+
type: "string",
+
example: "Unauthorized",
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
"/emojis": {
+
get: {
+
tags: ["The Cache!"],
+
summary: "Get all emojis",
+
description: "Retrieves all emojis from the cache",
+
responses: {
+
"200": {
+
description: "List of emojis",
+
content: {
+
"application/json": {
+
schema: {
+
type: "array",
+
items: {
+
type: "object",
+
properties: {
+
id: {
+
type: "string",
+
example: "5427fe70-686f-4684-9da5-95d9ef4c1090",
+
},
+
expiration: {
+
type: "string",
+
format: "date-time",
+
example: new Date().toISOString(),
+
},
+
name: {
+
type: "string",
+
example: "blahaj-heart",
+
},
+
alias: {
+
type: "string",
+
nullable: true,
+
example: "blobhaj-heart",
+
},
+
image: {
+
type: "string",
+
example:
+
"https://emoji.slack-edge.com/T0266FRGM/blahaj-heart/db9adf8229e9a4fb.png",
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
"/emojis/{emoji}": {
+
get: {
+
tags: ["The Cache!"],
+
summary: "Get emoji information",
+
description: "Retrieves information about a specific emoji",
+
parameters: [
+
{
+
name: "emoji",
+
in: "path",
+
required: true,
+
schema: {
+
type: "string",
+
},
+
description: "Emoji name",
+
},
+
],
+
responses: {
+
"200": {
+
description: "Emoji information",
+
content: {
+
"application/json": {
+
schema: {
+
type: "object",
+
properties: {
+
id: {
+
type: "string",
+
example: "9ed0a560-928d-409c-89fc-10fe156299da",
+
},
+
expiration: {
+
type: "string",
+
format: "date-time",
+
example: new Date().toISOString(),
+
},
+
name: {
+
type: "string",
+
example: "orphmoji-yay",
+
},
+
image: {
+
type: "string",
+
example:
+
"https://emoji.slack-edge.com/T0266FRGM/orphmoji-yay/23a37f4af47092d3.png",
+
},
+
},
+
},
+
},
+
},
+
},
+
"404": {
+
description: "Emoji not found",
+
content: {
+
"application/json": {
+
schema: {
+
type: "object",
+
properties: {
+
message: {
+
type: "string",
+
example: "Emoji not found",
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
"/emojis/{emoji}/r": {
+
get: {
+
tags: ["The Cache!"],
+
summary: "Redirect to emoji image",
+
description: "Redirects to the emoji image URL",
+
parameters: [
+
{
+
name: "emoji",
+
in: "path",
+
required: true,
+
schema: {
+
type: "string",
+
},
+
description: "Emoji name",
+
},
+
],
+
responses: {
+
"302": {
+
description: "Redirect to emoji image",
+
},
+
"404": {
+
description: "Emoji not found",
+
content: {
+
"application/json": {
+
schema: {
+
type: "object",
+
properties: {
+
message: {
+
type: "string",
+
example: "Emoji not found",
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
"/reset": {
+
post: {
+
tags: ["The Cache!"],
+
summary: "Reset cache",
+
description: "Purges all items from the cache",
+
parameters: [
+
{
+
name: "authorization",
+
in: "header",
+
required: true,
+
schema: {
+
type: "string",
+
example: "Bearer <token>",
+
},
+
description: "Bearer token for authentication",
+
},
+
],
+
responses: {
+
"200": {
+
description: "Cache purged",
+
content: {
+
"application/json": {
+
schema: {
+
type: "object",
+
properties: {
+
message: {
+
type: "string",
+
example: "Cache purged",
+
},
+
users: {
+
type: "number",
+
example: 10,
+
},
+
emojis: {
+
type: "number",
+
example: 100,
+
},
+
},
+
},
+
},
+
},
+
},
+
"401": {
+
description: "Unauthorized",
+
content: {
+
"text/plain": {
+
schema: {
+
type: "string",
+
example: "Unauthorized",
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
"/health": {
+
get: {
+
tags: ["Status"],
+
summary: "Health check",
+
description:
+
"Checks the health of the API, Slack connection, and database",
+
responses: {
+
"200": {
+
description: "Health check passed",
+
content: {
+
"application/json": {
+
schema: {
+
type: "object",
+
properties: {
+
http: {
+
type: "boolean",
+
example: true,
+
},
+
slack: {
+
type: "boolean",
+
example: true,
+
},
+
database: {
+
type: "boolean",
+
example: true,
+
},
+
},
+
},
+
},
+
},
+
},
+
"500": {
+
description: "Health check failed",
+
content: {
+
"application/json": {
+
schema: {
+
type: "object",
+
properties: {
+
http: {
+
type: "boolean",
+
example: false,
+
},
+
slack: {
+
type: "boolean",
+
example: false,
+
},
+
database: {
+
type: "boolean",
+
example: false,
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
"/stats": {
+
get: {
+
tags: ["Status"],
+
summary: "Get analytics statistics",
+
description: "Retrieves analytics statistics for the API",
+
parameters: [
+
{
+
name: "days",
+
in: "query",
+
required: false,
+
schema: {
+
type: "string",
+
},
+
description: "Number of days to look back (default: 7)",
+
},
+
],
+
responses: {
+
"200": {
+
description: "Analytics statistics",
+
content: {
+
"application/json": {
+
schema: {
+
type: "object",
+
properties: {
+
totalRequests: {
+
type: "number",
+
},
+
requestsByEndpoint: {
+
type: "array",
+
items: {
+
type: "object",
+
properties: {
+
endpoint: {
+
type: "string",
+
},
+
count: {
+
type: "number",
+
},
+
averageResponseTime: {
+
type: "number",
+
},
+
},
+
},
+
},
+
// Additional properties omitted for brevity
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
},
+
};
+
+
// Export the Swagger specification for use in other files
+
export default swaggerSpec;