add deletion timer, other small fixes

AuthorKonata <konata@posteo.jp>
Date
Commitde611b1e07e4de06f908e857bb4d34c55d46afdc
Parent311eb46
6 files changed, 145 insertions(+), 15 deletions(-)
Massets/default.css
@@ -73,3 +73,16 @@ input[type=reset] {
7373 max-width: 25rem;
7474 align-self: center;
7575 }
76+
77+
78+dialog {
79+ background-color: #120c0e;
80+ color: #d9d8dc;
81+ border-color: #eb99a1;
82+
83+ form {
84+ display: flex;
85+ flex-direction: column;
86+ align-items: center;
87+ }
88+}
Mbun.lock
@@ -5,8 +5,10 @@
55 "name": "zigbin",
66 "dependencies": {
77 "@chneau/elysia-compression": "^1.0.11",
8+ "@elysiajs/cron": "^1.2.0",
9+ "@elysiajs/html": "^1.2.0",
810 "@elysiajs/static": "^1.2.0",
9- "elysia": "latest",
11+ "elysia": "^1.2.0",
1012 "file-type": "^20.0.1",
1113 "highlight.js": "^11.11.1",
1214 "pkcs7-padding": "^0.1.1",
@@ -22,8 +24,16 @@
2224 "packages": {
2325 "@chneau/elysia-compression": ["@chneau/elysia-compression@1.0.11", "", { "dependencies": { "elysia": "^1.1.9" } }, "sha512-J4wfz5Qs68p/DNvkg5DXxo8sxJKSibzaUBP4W56uvqgs9NtHG0LC6tnyv7sAPEjNKzh21l6kinMoXbtVDp/KSQ=="],
2426
27+ "@elysiajs/cron": ["@elysiajs/cron@1.2.0", "", { "dependencies": { "croner": "^6.0.3" }, "peerDependencies": { "elysia": ">= 1.2.0" } }, "sha512-Xg2bUCZNEp2zRzsYeNQ6BUP3uslF5BEPnuLHLnsytiAEqzFTHzS7813XbmHs8xNZKuUAkAnJNTVnQb1Jt5zTwg=="],
28+
29+ "@elysiajs/html": ["@elysiajs/html@1.2.0", "", { "dependencies": { "@kitajs/html": "^4.1.0", "@kitajs/ts-html-plugin": "^4.0.1" }, "peerDependencies": { "elysia": ">= 1.2.0" } }, "sha512-j/exB9TLn2/KOMWRfPcJ7MNOsmr85uHRjC+kKPLyb1lRtNDWzgab4deUQrxtIv4gt4PDSKmywNnhyD+GrdnH3w=="],
30+
2531 "@elysiajs/static": ["@elysiajs/static@1.2.0", "", { "dependencies": { "node-cache": "^5.1.2" }, "peerDependencies": { "elysia": ">= 1.2.0" } }, "sha512-oLpAi8c+maPpA0XhhK3BELaIjIG+nXg/K9p8cFfW4q5ayRD59a3MOMOOGgpiXZkHJzLPWcouhhyyLAYtaANW4g=="],
2632
33+ "@kitajs/html": ["@kitajs/html@4.2.7", "", { "dependencies": { "csstype": "^3.1.3" } }, "sha512-HMAHy0GLH7i7wY1o0W1TeugNhtWefUkhb9l6zx6v2fIkR1cPlUtQQdkTt/jkBA7723yO7uBERcXFPZpWT7HWPQ=="],
34+
35+ "@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=="],
36+
2737 "@sinclair/typebox": ["@sinclair/typebox@0.34.15", "", {}, "sha512-xeIzl3h1Znn9w/LTITqpiwag0gXjA+ldi2ZkXIBxGEppGCW211Tza+eL6D4pKqs10bj5z2umBWk5WL6spQ2OCQ=="],
2838
2939 "@tokenizer/inflate": ["@tokenizer/inflate@0.2.6", "", { "dependencies": { "debug": "^4.3.7", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-SdR/i05U7Xhnsq36iyIq/ZiGGw4PKzw4ww3bOq80Pjj4wyXpqyTcgrgdDdGlcatnlvzNJx8CQw3hp6QZvkUwhA=="],
@@ -38,24 +48,50 @@
3848
3949 "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
4050
51+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
52+
53+ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
54+
4155 "bun-types": ["bun-types@1.2.2", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-RCbMH5elr9gjgDGDhkTTugA21XtJAy/9jkKe/G3WR2q17VPGhcquf9Sir6uay9iW+7P/BV0CAHA1XlHXMAVKHg=="],
4256
57+ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
58+
59+ "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=="],
60+
4361 "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="],
4462
63+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
64+
65+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
66+
4567 "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
4668
69+ "croner": ["croner@6.0.7", "", {}, "sha512-k3Xx3Rcclfr60Yx4TmvsF3Yscuiql8LSvYLaphTsaq5Hk8La4Z/udmUANMOTKpgGGroI2F6/XOr9cU9OFkYluQ=="],
70+
71+ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
72+
4773 "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
4874
4975 "elysia": ["elysia@1.2.10", "", { "dependencies": { "@sinclair/typebox": "^0.34.13", "cookie": "^1.0.2", "memoirist": "^0.2.0", "openapi-types": "^12.1.3" }, "peerDependencies": { "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-QcNl2FjhHFRpKaqy1NoMpyCjJ7OcKBnHwLUkqGu09QwIV84PFb82ILvYJG4GS1RbGv76OA50luaqBLrM3SLZ2w=="],
5076
77+ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
78+
79+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
80+
5181 "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
5282
5383 "file-type": ["file-type@20.0.1", "", { "dependencies": { "@tokenizer/inflate": "^0.2.6", "strtok3": "^10.2.0", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-R80W+NZ+s1M8PsC8L7xn+J0N72vEyxstMH+4vwIyt5B7ojfG80h/nKu2Qw/NYJTIYyGUeaPNLnzfJd78SWnG3A=="],
5484
85+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
86+
87+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
88+
5589 "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="],
5690
5791 "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
5892
93+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
94+
5995 "memoirist": ["memoirist@0.2.0", "", {}, "sha512-DA1V11OWsKmYjgYHfT1luus0FtTjUbILfI9s5M+ckK29tBLON6GDhH5GwxDz7E1ou4Bdzm9vhbeCaRAWxwG+0g=="],
6096
6197 "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@@ -70,10 +106,20 @@
70106
71107 "pkcs7-padding": ["pkcs7-padding@0.1.1", "", {}, "sha512-tM/sUOL5FdH7x6gSLHTNDFi1bAut/EXGFp/Ih8uRkVd2IdXwb5QWnxBgQXn6buadj1pLi3CYKCnrxb+XoTz+Ww=="],
72108
109+ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
110+
111+ "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=="],
112+
113+ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
114+
73115 "strtok3": ["strtok3@10.2.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^6.1.0" } }, "sha512-S884oIGzokq3LkL/6jXw5c5oJXRiGt4jB42cuWdaooJdxFMSn99snaShh3cQmJx3jALV2eoyW3rV/TJxwOaBPA=="],
74116
117+ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
118+
75119 "token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="],
76120
121+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
122+
77123 "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
78124
79125 "uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="],
@@ -81,5 +127,13 @@
81127 "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
82128
83129 "utf-8-validate": ["utf-8-validate@6.0.5", "", { "dependencies": { "node-gyp-build": "^4.3.0" } }, "sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA=="],
130+
131+ "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=="],
132+
133+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
134+
135+ "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=="],
136+
137+ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
84138 }
85139 }
Mpackage.json
@@ -8,8 +8,9 @@
88 },
99 "dependencies": {
1010 "@chneau/elysia-compression": "^1.0.11",
11- "@elysiajs/static": "^1.2.0",
11+ "@elysiajs/cron": "^1.2.0",
1212 "@elysiajs/html": "^1.2.0",
13+ "@elysiajs/static": "^1.2.0",
1314 "elysia": "^1.2.0",
1415 "file-type": "^20.0.1",
1516 "highlight.js": "^11.11.1",
Msrc/client-index.ts
@@ -29,6 +29,7 @@ async function encrypt(content: Uint8Array, password: string): Promise<ArrayBuff
2929 }
3030
3131 async function uploadFile() {
32+ (document.getElementById("upload-dialog") as HTMLDialogElement | null)?.showModal()
3233 const passwordInput = document.getElementById("password") as HTMLInputElement | null
3334 const fileInput = document.getElementById("file") as HTMLInputElement | null
3435 const filenameInput = document.getElementById("filename") as HTMLInputElement | null
@@ -65,12 +66,13 @@ async function uploadFile() {
6566 form.submit()
6667 }
6768
68-(document.getElementById("uploadForm") as HTMLFormElement | null)?.reset()
69-
7069 const jsguards = document.getElementsByClassName("jsguard")
7170 for (let i = 0; i < jsguards.length; i++) {
7271 jsguards[i].remove()
7372 }
7473
74+(document.getElementById("uploadForm") as HTMLFormElement | null)?.reset();
7575 window.uploadFile = uploadFile
76-
76+//disable bfcache, otherwise dialog will stay open when navigating back
77+window.addEventListener('unload', function () { });
78+window.addEventListener('beforeunload', function () { });
Msrc/components.tsx
@@ -6,7 +6,7 @@ import hljs from 'highlight.js'
66 import isValidUTF8 from 'utf-8-validate'
77 import { humanFileSize } from './shared'
88
9-const filetypes = ["none", "blob"].concat(hljs.listLanguages().sort())
9+export const filetypes = ["none", "blob"].concat(hljs.listLanguages().sort())
1010
1111 function Template(props: PropsWithChildren<{ css: string }>) {
1212 return (
@@ -38,6 +38,9 @@ export function NotFound() {
3838 export function Index(hostname: string, withJs: boolean) {
3939 let body = (
4040 <>
41+ <dialog id="upload-dialog">
42+ File is being uploaded, please wait
43+ </dialog>
4144 {withJs ?
4245 <div style="text-align: center">JS version with client-side encryption, for the version without js an with server-side encryption, go <a href="/">here</a>.</div>
4346 : <div style="text-align: center">JS-less version with server-side encryption, for the version with client-side encryption, go to <a href="/js">/js</a>.</div>
@@ -58,6 +61,9 @@ export function Index(hostname: string, withJs: boolean) {
5861 ))
5962 }
6063 </select>
64+ <label for="delete-in">Delete in:</label>
65+ <small>Optional. If set, file will be deleted after the given number of minutes.</small>
66+ <input type="number" id="delete-in" name="delete_in_minutes" min="0" placeholder="0" />
6167 <label for="password">Password:</label>
6268 <small>Optional. If set, file will be encrypted and requires the given password to open.</small>
6369 <input type="password" id="password" name="password" placeholder="Password" />
@@ -77,6 +83,8 @@ export function Index(hostname: string, withJs: boolean) {
7783 # optional{"\n"}
7884 -F filename="file name" \{"\n"}
7985 # optional{"\n"}
86+ -F delete_in_minutes="60" \{"\n"}
87+ # optional{"\n"}
8088 -F password="mypassword" \{"\n"}
8189 # optional{"\n"}
8290 -F encrypted="on"</pre>
@@ -137,7 +145,24 @@ export function WrongPassword() {
137145 )
138146 }
139147
140-export async function ShowFile(filename: string, uuid: string, content: Uint8Array | null, filetype: string) {
148+function humanReadableTime(minutes: number) {
149+ const years = Math.floor(minutes / 525600);
150+ minutes %= 525600;
151+ const days = Math.floor(minutes / 1440);
152+ minutes %= 1440;
153+ const hours = Math.floor(minutes / 60);
154+ minutes %= 60;
155+
156+ let result = "";
157+ if (years > 0) result += `${years} year${years > 1 ? "s" : ""} `
158+ if (days > 0) result += `${days} day${days > 1 ? "s" : ""} `
159+ if (hours > 0) result += `${hours} hour${hours > 1 ? "s" : ""} `
160+ if (minutes > 0) result += `${minutes} minute${minutes > 1 ? "s" : ""} `
161+ if (result === "") result = "0 minutes"
162+ return result.trim();
163+}
164+
165+export async function ShowFile(filename: string, uuid: string, content: Uint8Array | null, filetype: string, delete_at: number | null) {
141166 //if content is null, this is for the js frontend
142167 let preview;
143168 if (!content) {
@@ -183,6 +208,7 @@ export async function ShowFile(filename: string, uuid: string, content: Uint8Arr
183208 <hr />
184209 <p id="filesize">size: {content ? humanFileSize(content.byteLength) : "unknown"}</p>
185210 <p>declared type: {filetype}</p>
211+ {delete_at ? <p>delete at: {new Date(delete_at * 1000).toISOString()} (in {humanReadableTime(Math.floor((delete_at - (Date.now() / 1000)) / 60))})</p> : ""}
186212 <form id="download-form" action={`/raw/${uuid}`} method="get">
187213 <button>Download</button>
188214 </form>
Msrc/index.ts
@@ -4,13 +4,16 @@ import { randomUUIDv7 } from "bun"
44 import staticPlugin from "@elysiajs/static"
55 import { html } from '@elysiajs/html'
66 import crypto from 'crypto'
7-import { DecryptFile, Index, NotFound, SetCookie, ShowFile, WrongPassword } from "./components"
7+import { DecryptFile, filetypes, Index, NotFound, SetCookie, ShowFile, WrongPassword } from "./components"
8+import cron from "@elysiajs/cron"
89
910
11+//TODO: add deletion timer
12+
1013 const db = new Database("./db.sqlite")
1114 db.exec("PRAGMA foreign_keys = ON")
1215 db.exec("PRAGMA journal_mode = WAL2")
13-db.exec("CREATE TABLE IF NOT EXISTS files (uuid TEXT PRIMARY KEY, filename TEXT NOT NULL, content BLOB NOT NULL, filetype TEXT NOT NULL, encrypted INTEGER NOT NULL) STRICT")
16+db.exec("CREATE TABLE IF NOT EXISTS files (uuid TEXT PRIMARY KEY, filename TEXT NOT NULL, content BLOB NOT NULL, filetype TEXT NOT NULL, encrypted INTEGER NOT NULL, delete_at INTEGER) STRICT")
1417 db.exec("PRAGMA optimize")
1518
1619
@@ -32,6 +35,15 @@ function decrypt(encrypted_content: Uint8Array, password: string): Buffer {
3235 return content
3336 }
3437
38+function stringArrayToEnum<T extends string>(
39+ arr: readonly T[]
40+): { [K in T]: K } {
41+ return arr.reduce((acc, key) => {
42+ acc[key] = key;
43+ return acc;
44+ }, Object.create(null));
45+}
46+
3547 const app = new Elysia({
3648 serve: {
3749 maxRequestBodySize: 1024 * 1024 * 1024 // 1GB
@@ -39,6 +51,13 @@ const app = new Elysia({
3951 })
4052 .use(staticPlugin({ assets: "./assets", prefix: "/" }))
4153 .use(html())
54+ .use(cron({
55+ name: "delete",
56+ pattern: "*/5 * * * * *",
57+ run() {
58+ db.exec("DELETE FROM files WHERE delete_at < strftime('%s', 'now')")
59+ }
60+ }))
4261 .get("/", ({ server }) => Index(server?.url.toString()!, false))
4362 .get("/js", ({ server }) => Index(server?.url.toString()!, true))
4463 .post("/upload", async ({ set, body, query }) => {
@@ -46,13 +65,18 @@ const app = new Elysia({
4665 const uuid = randomUUIDv7()
4766 let content = Buffer.from(await body.file.bytes())
4867 let encrypted = false;
68+ let delete_at: number | null = null
69+ if (body.delete_in_minutes) {
70+ delete_at = Math.floor(Date.now() / 1000) + Number(body.delete_in_minutes) * 60
71+ }
4972 if (body.encrypted === "on") {
5073 encrypted = true
5174 } else if (body.password) {
5275 content = encrypt(content, body.password)
5376 encrypted = true
5477 }
55- db.exec("INSERT INTO files (uuid, filename, content, filetype, encrypted) VALUES (?, ?, ?, ?, ?)", [uuid, body.filename || body.file.name, content, body.filetype, encrypted])
78+ db.exec("INSERT INTO files (uuid, filename, content, filetype, encrypted, delete_at) VALUES (?, ?, ?, ?, ?, ?)",
79+ [uuid, body.filename || body.file.name, content, body.filetype, encrypted, delete_at])
5680 set.status = StatusMap["See Other"]
5781 if (query.withJs) {
5882 set.headers["location"] = `/show-js/${uuid}`
@@ -62,12 +86,21 @@ const app = new Elysia({
6286 return `Created with id: ${uuid}`
6387
6488 }, {
65- body: t.Object({ file: t.File(), filename: t.Optional(t.String()), filetype: t.String()/*todo:verifiy via t.Enum*/, password: t.Optional(t.String()), encrypted: t.Optional(t.String()) }),
89+ body: t.Object({
90+ file: t.File(),
91+ filename: t.Optional(t.String()),
92+ //filetype: t.String()/*todo:verifiy via t.Enum*/,
93+ filetype: t.Enum(stringArrayToEnum(filetypes)),
94+ password: t.Optional(t.String()),
95+ encrypted: t.Optional(t.String()),
96+ delete_in_minutes: t.String({ format: "regex", pattern: "(^$|^[0-9]+$)" })
97+ }),
6698 query: t.Object({ withJs: t.Optional(t.Boolean()) })
6799 })
68100 .get("/show/:uuid", async ({ set, params, cookie }) => {
69101
70- const result = db.prepare("SELECT filename, content, filetype, encrypted FROM files WHERE uuid = ?").get(params.uuid) as { filename: string, content: Uint8Array, filetype: string, encrypted: boolean } || null
102+ const result = db.prepare("SELECT filename, content, filetype, encrypted, delete_at FROM files WHERE uuid = ?").get(params.uuid) as
103+ { filename: string, content: Uint8Array, filetype: string, encrypted: boolean, delete_at: number | null } || null
71104 if (!result) {
72105 set.status = StatusMap["Not Found"]
73106 return NotFound()
@@ -89,17 +122,18 @@ const app = new Elysia({
89122 }
90123 }
91124 }
92- return ShowFile(result.filename, params.uuid, result.content, result.filetype)
125+ return ShowFile(result.filename, params.uuid, result.content, result.filetype, result.delete_at)
93126
94127 }, { params: t.Object({ uuid: t.String() }) })
95128 .get("/show-js/:uuid", async ({ set, params, cookie }) => {
96129
97- const result = db.prepare("SELECT filename, content, filetype, encrypted FROM files WHERE uuid = ?").get(params.uuid) as { filename: string, content: Uint8Array, filetype: string, encrypted: boolean } || null
130+ const result = db.prepare("SELECT filename, content, filetype, encrypted, delete_at FROM files WHERE uuid = ?").get(params.uuid) as
131+ { filename: string, content: Uint8Array, filetype: string, encrypted: boolean, delete_at: number | null } || null
98132 if (!result) {
99133 set.status = StatusMap["Not Found"]
100134 return NotFound()
101135 }
102- return ShowFile(result.filename, params.uuid, null, result.filetype)
136+ return ShowFile(result.filename, params.uuid, null, result.filetype, result.delete_at)
103137
104138 }, { params: t.Object({ uuid: t.String() }) })
105139 .post("/set-cookie/:uuid", ({ set, body, params }) => {