implement export all

This commit is contained in:
Ean Milligan
2026-04-24 14:28:01 -04:00
parent 31e3e13ab9
commit f412f06e10
7 changed files with 85 additions and 24 deletions

View File

@@ -23,6 +23,7 @@
"nodeModulesDir": "none", "nodeModulesDir": "none",
"imports": { "imports": {
"@bcrypt": "https://deno.land/x/bcrypt@v0.4.1/mod.ts", "@bcrypt": "https://deno.land/x/bcrypt@v0.4.1/mod.ts",
"@deno-zip": "https://deno.land/x/jszip@0.11.0/mod.ts",
"@mysql": "https://deno.land/x/mysql@v2.12.1/mod.ts", "@mysql": "https://deno.land/x/mysql@v2.12.1/mod.ts",
"@nanoid": "https://deno.land/x/nanoid@v3.0.0/mod.ts", "@nanoid": "https://deno.land/x/nanoid@v3.0.0/mod.ts",
"@std/http": "jsr:@std/http@1.0.15", "@std/http": "jsr:@std/http@1.0.15",

53
deno.lock generated
View File

@@ -9,7 +9,19 @@
} }
}, },
"redirects": { "redirects": {
"https://deno.land/x/bcrypt/mod.ts": "https://deno.land/x/bcrypt@v0.4.1/mod.ts" "https://deno.land/x/bcrypt/mod.ts": "https://deno.land/x/bcrypt@v0.4.1/mod.ts",
"https://esm.sh/core-util-is@~1.0.0?target=denonext": "https://esm.sh/core-util-is@1.0.3?target=denonext",
"https://esm.sh/immediate@~3.0.5?target=denonext": "https://esm.sh/immediate@3.0.6?target=denonext",
"https://esm.sh/isarray@~1.0.0?target=denonext": "https://esm.sh/isarray@1.0.0?target=denonext",
"https://esm.sh/lie@~3.3.0?target=denonext": "https://esm.sh/lie@3.3.0?target=denonext",
"https://esm.sh/pako@~1.0.2?target=denonext": "https://esm.sh/pako@1.0.11?target=denonext",
"https://esm.sh/process-nextick-args@~2.0.0?target=denonext": "https://esm.sh/process-nextick-args@2.0.1?target=denonext",
"https://esm.sh/readable-stream@~2.3.6?target=denonext": "https://esm.sh/readable-stream@2.3.8?target=denonext",
"https://esm.sh/safe-buffer@~5.1.0?target=denonext": "https://esm.sh/safe-buffer@5.1.2?target=denonext",
"https://esm.sh/safe-buffer@~5.1.1?target=denonext": "https://esm.sh/safe-buffer@5.1.2?target=denonext",
"https://esm.sh/set-immediate-shim@~1.0.1?target=denonext": "https://esm.sh/set-immediate-shim@1.0.1?target=denonext",
"https://esm.sh/string_decoder@~1.1.1?target=denonext": "https://esm.sh/string_decoder@1.1.1?target=denonext",
"https://esm.sh/util-deprecate@~1.0.1?target=denonext": "https://esm.sh/util-deprecate@1.0.2?target=denonext"
}, },
"remote": { "remote": {
"https://deno.land/std@0.104.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", "https://deno.land/std@0.104.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
@@ -40,6 +52,18 @@
"https://deno.land/std@0.104.0/log/mod.ts": "91711789b28803082b1bdfb123d2c9685a7e01767f2e79c0a82706063ad964d8", "https://deno.land/std@0.104.0/log/mod.ts": "91711789b28803082b1bdfb123d2c9685a7e01767f2e79c0a82706063ad964d8",
"https://deno.land/std@0.104.0/testing/_diff.ts": "5d3693155f561d1a5443ac751ac70aab9f5d67b4819a621d4b96b8a1a1c89620", "https://deno.land/std@0.104.0/testing/_diff.ts": "5d3693155f561d1a5443ac751ac70aab9f5d67b4819a621d4b96b8a1a1c89620",
"https://deno.land/std@0.104.0/testing/asserts.ts": "e4311d45d956459d4423bc267208fe154b5294989da2ed93257b6a85cae0427e", "https://deno.land/std@0.104.0/testing/asserts.ts": "e4311d45d956459d4423bc267208fe154b5294989da2ed93257b6a85cae0427e",
"https://deno.land/std@0.116.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
"https://deno.land/std@0.116.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac",
"https://deno.land/std@0.116.0/fs/walk.ts": "31464d75099aa3fc7764212576a8772dfabb2692783e6eabb910f874a26eac54",
"https://deno.land/std@0.116.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853",
"https://deno.land/std@0.116.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4",
"https://deno.land/std@0.116.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b",
"https://deno.land/std@0.116.0/path/common.ts": "f41a38a0719a1e85aa11c6ba3bea5e37c15dd009d705bd8873f94c833568cbc4",
"https://deno.land/std@0.116.0/path/glob.ts": "ea87985765b977cc284b92771003b2070c440e0807c90e1eb0ff3e095911a820",
"https://deno.land/std@0.116.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12",
"https://deno.land/std@0.116.0/path/posix.ts": "34349174b9cd121625a2810837a82dd8b986bbaaad5ade690d1de75bbb4555b2",
"https://deno.land/std@0.116.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c",
"https://deno.land/std@0.116.0/path/win32.ts": "11549e8c6df8307a8efcfa47ad7b2a75da743eac7d4c89c9723a944661c8bd2e",
"https://deno.land/std@0.77.0/fmt/colors.ts": "c5665c66f1a67228f21c5989bbb04b36d369b98dd7ceac06f5e26856c81c2531", "https://deno.land/std@0.77.0/fmt/colors.ts": "c5665c66f1a67228f21c5989bbb04b36d369b98dd7ceac06f5e26856c81c2531",
"https://deno.land/x/bcrypt@v0.4.1/mod.ts": "ff09bdae282583cf5f7d87efe37ddcecef7f14f6d12e8b8066a3058db8c6c2f7", "https://deno.land/x/bcrypt@v0.4.1/mod.ts": "ff09bdae282583cf5f7d87efe37ddcecef7f14f6d12e8b8066a3058db8c6c2f7",
"https://deno.land/x/bcrypt@v0.4.1/src/bcrypt/base64.ts": "b8266450a4f1eb6960f60f2f7986afc4dde6b45bd2d7ee7ba10789e67e17b9f7", "https://deno.land/x/bcrypt@v0.4.1/src/bcrypt/base64.ts": "b8266450a4f1eb6960f60f2f7986afc4dde6b45bd2d7ee7ba10789e67e17b9f7",
@@ -49,6 +73,7 @@
"https://deno.land/x/bytes_formater@v1.4.0/deps.ts": "4f98f74e21145423b873a5ca6ead66dc3e674fa81e230a0a395f9b86aafeceea", "https://deno.land/x/bytes_formater@v1.4.0/deps.ts": "4f98f74e21145423b873a5ca6ead66dc3e674fa81e230a0a395f9b86aafeceea",
"https://deno.land/x/bytes_formater@v1.4.0/format.ts": "657c41b9f180c3ed0f934dcf75f77b09b6a610be98bb07525bffe2acfd5af4d5", "https://deno.land/x/bytes_formater@v1.4.0/format.ts": "657c41b9f180c3ed0f934dcf75f77b09b6a610be98bb07525bffe2acfd5af4d5",
"https://deno.land/x/bytes_formater@v1.4.0/mod.ts": "c6bf35303f53d74e9134eb13f666fb388fb4c62c6b12b17542bbadade250a864", "https://deno.land/x/bytes_formater@v1.4.0/mod.ts": "c6bf35303f53d74e9134eb13f666fb388fb4c62c6b12b17542bbadade250a864",
"https://deno.land/x/jszip@0.11.0/mod.ts": "5661ddc18e9ac9c07e3c5d2483bc912a7022b6af0d784bb7b05035973e640ba1",
"https://deno.land/x/mysql@v2.12.1/deps.ts": "68635959a41bb08bc87db007679fb8449febc55d48202dff20b93cc23ef5820d", "https://deno.land/x/mysql@v2.12.1/deps.ts": "68635959a41bb08bc87db007679fb8449febc55d48202dff20b93cc23ef5820d",
"https://deno.land/x/mysql@v2.12.1/mod.ts": "3246c9c259434563be69cc95d5b792f8aac7ef5d10b8a6c6589aa54ebf1bd266", "https://deno.land/x/mysql@v2.12.1/mod.ts": "3246c9c259434563be69cc95d5b792f8aac7ef5d10b8a6c6589aa54ebf1bd266",
"https://deno.land/x/mysql@v2.12.1/src/auth.ts": "129ea08b180d3e90e567c3f71e60432bb266304c224e17ea39d604bbcc1160d8", "https://deno.land/x/mysql@v2.12.1/src/auth.ts": "129ea08b180d3e90e567c3f71e60432bb266304c224e17ea39d604bbcc1160d8",
@@ -82,7 +107,31 @@
"https://deno.land/x/nanoid@v3.0.0/nanoid.ts": "8d119bc89a0f34e7bbe0c2dbdc280d01753e431af553d189663492310a31085d", "https://deno.land/x/nanoid@v3.0.0/nanoid.ts": "8d119bc89a0f34e7bbe0c2dbdc280d01753e431af553d189663492310a31085d",
"https://deno.land/x/nanoid@v3.0.0/random.ts": "4da71d5f72f2bfcc6a4ee79b5d4e72f48dcf4fe4c3835fd5ebab08b9f33cd598", "https://deno.land/x/nanoid@v3.0.0/random.ts": "4da71d5f72f2bfcc6a4ee79b5d4e72f48dcf4fe4c3835fd5ebab08b9f33cd598",
"https://deno.land/x/nanoid@v3.0.0/urlAlphabet.ts": "8b1511deb1ecb23c66202b6000dc10fb68f9a96b5550c6c8cef5009324793431", "https://deno.land/x/nanoid@v3.0.0/urlAlphabet.ts": "8b1511deb1ecb23c66202b6000dc10fb68f9a96b5550c6c8cef5009324793431",
"https://deno.land/x/sql_builder@v1.9.1/util.ts": "b9855dc435972704cf82655019f4ec168ac83550ab4db596c5f6b6d201466384" "https://deno.land/x/sql_builder@v1.9.1/util.ts": "b9855dc435972704cf82655019f4ec168ac83550ab4db596c5f6b6d201466384",
"https://esm.sh/core-util-is@1.0.3/denonext/core-util-is.mjs": "cfcf1ae63d56751cbe4b3b90b90b7eea577c5380c4adc272ddea4b7db2bdbbf2",
"https://esm.sh/core-util-is@1.0.3?target=denonext": "6c72958f8a1c8f42016b48c984a0f3d799ea1e0cd321f499fec0bf8db916c17f",
"https://esm.sh/immediate@3.0.6/denonext/immediate.mjs": "7148ba33cb905f7aca49affbacfa6a8257cd6b89e8c3c7c728d2d0387b4cce29",
"https://esm.sh/immediate@3.0.6?target=denonext": "fba8d9ddb37f19ff27c0b1c5b4486ab82805114b14959379d92ca05d6351c5d3",
"https://esm.sh/isarray@1.0.0/denonext/isarray.mjs": "0f26133cd58fc8580f99bbfd81f6290718328dc2a683c313c36f6b1e8c174edc",
"https://esm.sh/isarray@1.0.0?target=denonext": "00e227f6d016cb5a5f832f6f2de91dd8ab092c7ac830c551bfcf0f63284d89e6",
"https://esm.sh/jszip@3.7.1": "5161d6a228d844791a60ab58360bd3b76c4d3921b4a725616cd7403203519249",
"https://esm.sh/jszip@3.7.1/denonext/jszip.mjs": "325e8509d94e1460a8bb0bbb58b47a7b70c63b48568a60993e2880dba3a3062d",
"https://esm.sh/lie@3.3.0/denonext/lie.mjs": "20db2fef139e87d467b7cf24a9e53053e96460fefedde5910f925b1d0ddc0cba",
"https://esm.sh/lie@3.3.0?target=denonext": "74a2c724bd2fef30c46c612632dfd2ee37394f1a4540eb112e0df2ef98df0434",
"https://esm.sh/pako@1.0.11/denonext/pako.mjs": "4895feb3e2441ef725da4052a5dc93d219d065fef1decc4452bb9b7ee1477c0d",
"https://esm.sh/pako@1.0.11?target=denonext": "bc43f66ed245d58d468bf9867b3e9080c5b0590b4c14038ea308954490e0b2ea",
"https://esm.sh/process-nextick-args@2.0.1/denonext/process-nextick-args.mjs": "adffdd507c6571957aaab9d3f0a2aa54febdda1b4d546a57967fd2299505339e",
"https://esm.sh/process-nextick-args@2.0.1?target=denonext": "b80260031d83086964facc0efc6e2cc8fd878d9ce14dfcf6999e508a4d8d13d0",
"https://esm.sh/readable-stream@2.3.8/denonext/readable-stream.mjs": "ce8c7e2e7783c4487c1e9fcaf8824f0af26d48e6f5fe02fa9cbd70c34799ef98",
"https://esm.sh/readable-stream@2.3.8?target=denonext": "a8d158c470101e7518fdf293728d4cb8b2ab2cac73140940c8a9ee5542194e13",
"https://esm.sh/safe-buffer@5.1.2/denonext/safe-buffer.mjs": "848e2c2dafb98ea738399526e4396607872d1118acf8eb56eecd2a5f3be75568",
"https://esm.sh/safe-buffer@5.1.2?target=denonext": "3126988c629e3dc2d6126b26f654aceae10ad989622a21cb2a73ee72603f7df8",
"https://esm.sh/set-immediate-shim@1.0.1/denonext/set-immediate-shim.mjs": "a0fc9b90f281a6541c474dbf55184ef3a9360248f53cb3fa9479480cd24cdd40",
"https://esm.sh/set-immediate-shim@1.0.1?target=denonext": "8d30997d25a26dbcd4d79b613e6f400af85194f8e18e8e7014bc5fe3c9ffd429",
"https://esm.sh/string_decoder@1.1.1/denonext/string_decoder.mjs": "494e5a7fae95d5326e8aee93b4adfde75e389eea7a54bc1feea8549e786da032",
"https://esm.sh/string_decoder@1.1.1?target=denonext": "092c97b62b99368a40fa044c402188472658bc71529415f73c16f66c05aaf6bf",
"https://esm.sh/util-deprecate@1.0.2/denonext/util-deprecate.mjs": "083639894972cb68837eef26346c43bdd01357977149e0a4493f76192a4008b8",
"https://esm.sh/util-deprecate@1.0.2?target=denonext": "859f4df8ba771a4c33143185d3db6a7edb824fab1ed4f9a4b96ac0e6bc3ef1a4"
}, },
"workspace": { "workspace": {
"dependencies": [ "dependencies": [

21
mod.ts
View File

@@ -1,4 +1,5 @@
import { hash, compare } from '@bcrypt'; import { hash, compare } from '@bcrypt';
import { JSZip } from '@deno-zip';
import { customAlphabet } from '@nanoid'; import { customAlphabet } from '@nanoid';
import { STATUS_CODE, STATUS_TEXT, StatusCode } from '@std/http/status'; import { STATUS_CODE, STATUS_TEXT, StatusCode } from '@std/http/status';
@@ -82,14 +83,28 @@ Deno.serve({ port: config.api.port }, async (req) => {
return genericResponse(STATUS_CODE.OK, JSON.stringify(plans)); return genericResponse(STATUS_CODE.OK, JSON.stringify(plans));
} else if (path.startsWith('/export/')) { } else if (path.startsWith('/export/')) {
const userId = path.replace('/export/', ''); const userId = path.replace('/export/', '');
const userMatch = await dbClient.query('SELECT id FROM users WHERE id = ?', [userId]).catch(() => { const userMatch = await dbClient.query('SELECT id, name FROM users WHERE id = ?', [userId]).catch(() => {
failed = true; failed = true;
}); });
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB."); if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
if (!userMatch.length) return genericResponse(STATUS_CODE.NotFound, 'User ID does not exist.'); if (!userMatch.length) return genericResponse(STATUS_CODE.NotFound, 'User ID does not exist.');
// WIP: export plans to zip code goes here const plans = await dbClient
return genericResponse(STATUS_CODE.NotImplemented, 'Export function WIP.'); .query('SELECT name, folder, data FROM plans WHERE ownerId = ? AND deleted = 0 ORDER BY folder ASC,name ASC', [userId])
.catch(() => {
failed = true;
});
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
const zip = new JSZip();
for (const plan of plans) {
zip.addFile(`${plan.folder}${plan.folder ? '/' : ''}${plan.name}.xivplan`, plan.data);
}
return new Response(await zip.generateAsync({ type: 'blob' }), {
status: STATUS_CODE.OK,
statusText: STATUS_TEXT[STATUS_CODE.OK],
});
} }
} else if (req.method === 'POST' && path === '/enroll') { } else if (req.method === 'POST' && path === '/enroll') {
const body = await req.json(); const body = await req.json();

View File

@@ -11,8 +11,8 @@ interface Plan {
const makePlanButtons = (planId: string, deleted: boolean) => const makePlanButtons = (planId: string, deleted: boolean) =>
deleted deleted
? `<button onclick="doAction('undelete','${planId}')">restore</button><button onclick="doAction('perm-delete','${planId}')">perm delete</button>` ? `<button class="btn" onclick="doAction('undelete','${planId}')">restore</button><button class="btn" onclick="doAction('perm-delete','${planId}')">perm delete</button>`
: `<button onclick="openPlan('${planId}')">open</button><button onclick="sharePlan('${planId}')">share</button><button onclick="doAction('rename','${planId}')">rename</button><button onclick="doAction('move','${planId}')">move</button><button onclick="doAction('delete','${planId}')">delete</button>`; : `<button class="btn" onclick="openPlan('${planId}')">open</button><button class="btn" onclick="sharePlan('${planId}')">share</button><button class="btn" onclick="doAction('rename','${planId}')">rename</button><button class="btn" onclick="doAction('move','${planId}')">move</button><button class="btn" onclick="doAction('delete','${planId}')">delete</button>`;
const makePlanItem = (plan: Plan, deleted: boolean) => const makePlanItem = (plan: Plan, deleted: boolean) =>
`<li>${plan.folder}${plan.folder && '/'}${plan.name} - ${plan.lastUpdated.toLocaleString()} - ${makePlanButtons(plan.id, deleted)}</li>`; `<li>${plan.folder}${plan.folder && '/'}${plan.name} - ${plan.lastUpdated.toLocaleString()} - ${makePlanButtons(plan.id, deleted)}</li>`;
@@ -82,19 +82,6 @@ function doAction(action, planId) {
} }
}); });
} }
function exportPlans() {
fetch('/api/export/${userId}')
.catch((e) => {
e.text().then((text) => {
alert(text);
});
})
.then((r) => {
r.text().then((text) => {
alert(text);
});
});
}
function openPlan(planId) { function openPlan(planId) {
window.open(\`${config.api.publicDomain}share#\${planId}\`); window.open(\`${config.api.publicDomain}share#\${planId}\`);
} }
@@ -165,7 +152,7 @@ function deleteAccount() {
<p>Please note: anything modifying data will require you to enter your PIN again as both the web view you are looking at and server behind it are completely stateless for simplicity.</p> <p>Please note: anything modifying data will require you to enter your PIN again as both the web view you are looking at and server behind it are completely stateless for simplicity.</p>
<p>DateTimeStamps on this page are all displayed in the US Eastern time zone. I don't care enough to make this extremely basic page dynamic.</p> <p>DateTimeStamps on this page are all displayed in the US Eastern time zone. I don't care enough to make this extremely basic page dynamic.</p>
<h3>${userName}'s Plans:</h3> <h3>${userName}'s Plans:</h3>
<button onclick="exportPlans()" disabled style="color:black;cursor:not-allowed;">WIP: export all plans</button> <a href="/api/export/${userId}" download="${userName}-plans.zip" class="btn">download all plans</a>
<ul> <ul>
${plans.map((plan) => makePlanItem(plan, false)).join('')} ${plans.map((plan) => makePlanItem(plan, false)).join('')}
</ul> </ul>
@@ -173,6 +160,6 @@ ${plans.map((plan) => makePlanItem(plan, false)).join('')}
<ul> <ul>
${deletedPlans.map((plan) => makePlanItem(plan, true)).join('')} ${deletedPlans.map((plan) => makePlanItem(plan, true)).join('')}
</ul> </ul>
<button onclick="deleteAccount()">Delete my account</button> <button class="btn" onclick="deleteAccount()">Delete my account</button>
</div>`; </div>`;
}; };

View File

@@ -20,6 +20,15 @@ a {
text-decoration:none; text-decoration:none;
color:white; color:white;
} }
.btn {
font: 13.333px Sans;
padding: 1px 6px;
border: 1px outset buttonborder;
border-radius: 3px;
color: buttontext;
background-color: buttonface;
text-decoration: none;
}
li { li {
margin:0.5rem 0; margin:0.5rem 0;
} }

View File

@@ -1 +1 @@
deno run --allow-net --allow-import mod.ts deno run --allow-net --allow-import --allow-env mod.ts

View File

@@ -13,7 +13,7 @@ xivplan_db_log="/var/log/xivplan_db.log"
xivplan_db_chdir="${xivplan_db_root}" xivplan_db_chdir="${xivplan_db_root}"
command="/usr/sbin/daemon" command="/usr/sbin/daemon"
command_args="-f -R 5 -P ${pidfile} -o ${xivplan_db_log} /usr/local/bin/deno run --allow-net --allow-import ${xivplan_db_root}/mod.ts" command_args="-f -R 5 -P ${pidfile} -o ${xivplan_db_log} /usr/local/bin/deno run --allow-net --allow-import --allow-env ${xivplan_db_root}/mod.ts"
load_rc_config xivplan-db load_rc_config xivplan-db
run_rc_command "$1" run_rc_command "$1"