Compare commits
13 Commits
353e6e978d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b36742150 | ||
|
|
f412f06e10 | ||
|
|
31e3e13ab9 | ||
|
|
b31886ce7b | ||
|
|
884b5b81a0 | ||
|
|
1ceae4d158 | ||
|
|
e9b46d136d | ||
|
|
0c0c175f5c | ||
|
|
f01a3e3abf | ||
|
|
aab802b66a | ||
|
|
7bd835c68a | ||
|
|
8126e6dc95 | ||
|
|
1f4c4723d3 |
@@ -1,6 +1,10 @@
|
||||
# XIVPlan+DB Mod Edition
|
||||
|
||||
This is just XIVPlan with a very basic DB implementation to make sharing links to a plan much more accessible. This repo contains the modification to XIVPlan as a git `.patch` file, and the rest of the source in this repo is the server that acts as an interface between XIVPlan and the DB.
|
||||
This is just XIVPlan with a very basic DB implementation to make sharing links to a plan much more accessible. This repo contains the modification to XIVPlan as a git `db-version.patch` file, and the rest of the source in this repo is the server that acts as an interface between XIVPlan and the DB.
|
||||
|
||||
## Applying the patch
|
||||
|
||||
`git apply db-version.patch`
|
||||
|
||||
## Ideas around the mod
|
||||
|
||||
|
||||
1358
db-version.patch
Normal file
1358
db-version.patch
Normal file
File diff suppressed because it is too large
Load Diff
@@ -16,8 +16,8 @@ console.log('Tables dropped');
|
||||
console.log('Attempting to create table users');
|
||||
await dbClient.execute(`
|
||||
CREATE TABLE users (
|
||||
id varchar(20) NOT NULL,
|
||||
name varchar(20) NOT NULL,
|
||||
id varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
name varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
hash varchar(60) NOT NULL,
|
||||
email varchar(255) NULL,
|
||||
deleteCode varchar(20) NULL,
|
||||
@@ -31,8 +31,8 @@ console.log('Table created');
|
||||
console.log('Attempting to create table plans');
|
||||
await dbClient.execute(`
|
||||
CREATE TABLE plans (
|
||||
id varchar(20) NOT NULL,
|
||||
ownerId varchar(20) NOT NULL,
|
||||
id varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
ownerId varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
|
||||
name varchar(200) NOT NULL,
|
||||
folder varchar(200) NOT NULL,
|
||||
lastUpdated timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
},
|
||||
"nodeModulesDir": "none",
|
||||
"imports": {
|
||||
"@bcrypt": "jsr:@felix/bcrypt",
|
||||
"@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",
|
||||
"@nanoid": "https://deno.land/x/nanoid@v3.0.0/mod.ts",
|
||||
"@std/http": "jsr:@std/http@1.0.15",
|
||||
|
||||
145
deno.lock
generated
145
deno.lock
generated
@@ -1,94 +1,28 @@
|
||||
{
|
||||
"version": "5",
|
||||
"specifiers": {
|
||||
"jsr:@denosaurs/plug@^1.1.0": "1.1.0",
|
||||
"jsr:@felix/bcrypt@*": "1.0.8",
|
||||
"jsr:@std/cli@^1.0.17": "1.0.17",
|
||||
"jsr:@std/encoding@1": "1.0.10",
|
||||
"jsr:@std/encoding@^1.0.10": "1.0.10",
|
||||
"jsr:@std/fmt@1": "1.0.7",
|
||||
"jsr:@std/fmt@^1.0.7": "1.0.7",
|
||||
"jsr:@std/fs@1": "1.0.23",
|
||||
"jsr:@std/html@^1.0.3": "1.0.3",
|
||||
"jsr:@std/http@1.0.15": "1.0.15",
|
||||
"jsr:@std/internal@^1.0.12": "1.0.13",
|
||||
"jsr:@std/media-types@^1.1.0": "1.1.0",
|
||||
"jsr:@std/net@^1.0.4": "1.0.4",
|
||||
"jsr:@std/path@1": "1.0.9",
|
||||
"jsr:@std/path@^1.0.9": "1.0.9",
|
||||
"jsr:@std/path@^1.1.4": "1.1.4",
|
||||
"jsr:@std/streams@^1.0.9": "1.0.9"
|
||||
"jsr:@std/http@1.0.15": "1.0.15"
|
||||
},
|
||||
"jsr": {
|
||||
"@denosaurs/plug@1.1.0": {
|
||||
"integrity": "eb2f0b7546c7bca2000d8b0282c54d50d91cf6d75cb26a80df25a6de8c4bc044",
|
||||
"dependencies": [
|
||||
"jsr:@std/encoding@1",
|
||||
"jsr:@std/fmt@1",
|
||||
"jsr:@std/fs",
|
||||
"jsr:@std/path@1"
|
||||
]
|
||||
},
|
||||
"@felix/bcrypt@1.0.8": {
|
||||
"integrity": "59c41160fc027882479c512db5d53792c4d91aadcd49467c85caa2f1679046f2",
|
||||
"dependencies": [
|
||||
"jsr:@denosaurs/plug"
|
||||
]
|
||||
},
|
||||
"@std/cli@1.0.17": {
|
||||
"integrity": "e15b9abe629e17be90cc6216327f03a29eae613365f1353837fa749aad29ce7b"
|
||||
},
|
||||
"@std/encoding@1.0.10": {
|
||||
"integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
|
||||
},
|
||||
"@std/fmt@1.0.7": {
|
||||
"integrity": "2a727c043d8df62cd0b819b3fb709b64dd622e42c3b1bb817ea7e6cc606360fb"
|
||||
},
|
||||
"@std/fs@1.0.23": {
|
||||
"integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37",
|
||||
"dependencies": [
|
||||
"jsr:@std/internal",
|
||||
"jsr:@std/path@^1.1.4"
|
||||
]
|
||||
},
|
||||
"@std/html@1.0.3": {
|
||||
"integrity": "7a0ac35e050431fb49d44e61c8b8aac1ebd55937e0dc9ec6409aa4bab39a7988"
|
||||
},
|
||||
"@std/http@1.0.15": {
|
||||
"integrity": "435a4934b4e196e82a8233f724da525f7b7112f3566502f28815e94764c19159",
|
||||
"dependencies": [
|
||||
"jsr:@std/cli",
|
||||
"jsr:@std/encoding@^1.0.10",
|
||||
"jsr:@std/fmt@^1.0.7",
|
||||
"jsr:@std/html",
|
||||
"jsr:@std/media-types",
|
||||
"jsr:@std/net",
|
||||
"jsr:@std/path@^1.0.9",
|
||||
"jsr:@std/streams"
|
||||
]
|
||||
},
|
||||
"@std/internal@1.0.13": {
|
||||
"integrity": "2f9546691d4ac2d32859c82dff284aaeac980ddeca38430d07941e7e288725c0"
|
||||
},
|
||||
"@std/media-types@1.1.0": {
|
||||
"integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
|
||||
},
|
||||
"@std/net@1.0.4": {
|
||||
"integrity": "2f403b455ebbccf83d8a027d29c5a9e3a2452fea39bb2da7f2c04af09c8bc852"
|
||||
},
|
||||
"@std/path@1.0.9": {
|
||||
"integrity": "260a49f11edd3db93dd38350bf9cd1b4d1366afa98e81b86167b4e3dd750129e"
|
||||
},
|
||||
"@std/path@1.1.4": {
|
||||
"integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5",
|
||||
"dependencies": [
|
||||
"jsr:@std/internal"
|
||||
]
|
||||
},
|
||||
"@std/streams@1.0.9": {
|
||||
"integrity": "a9d26b1988cdd7aa7b1f4b51e1c36c1557f3f252880fa6cc5b9f37078b1a5035"
|
||||
"integrity": "435a4934b4e196e82a8233f724da525f7b7112f3566502f28815e94764c19159"
|
||||
}
|
||||
},
|
||||
"redirects": {
|
||||
"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": {
|
||||
"https://deno.land/std@0.104.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
|
||||
"https://deno.land/std@0.104.0/async/deadline.ts": "1d6ac7aeaee22f75eb86e4e105d6161118aad7b41ae2dd14f4cfd3bf97472b93",
|
||||
@@ -118,10 +52,28 @@
|
||||
"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/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/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/bcrypt.ts": "ec221648cc6453ea5e3803bc817c01157dada06aa6f7a0ba6b9f87aae32b21e2",
|
||||
"https://deno.land/x/bcrypt@v0.4.1/src/main.ts": "08d201b289c8d9c46f8839c69cd6625b213863db29775c7a200afc3b540e64f8",
|
||||
"https://deno.land/x/bcrypt@v0.4.1/src/worker.ts": "5a73bdfee9c9e622f47c9733d374b627dce52fb3ec1e74c8226698b3fc57ffac",
|
||||
"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/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/mod.ts": "3246c9c259434563be69cc95d5b792f8aac7ef5d10b8a6c6589aa54ebf1bd266",
|
||||
"https://deno.land/x/mysql@v2.12.1/src/auth.ts": "129ea08b180d3e90e567c3f71e60432bb266304c224e17ea39d604bbcc1160d8",
|
||||
@@ -155,11 +107,34 @@
|
||||
"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/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": {
|
||||
"dependencies": [
|
||||
"jsr:@felix/bcrypt@*",
|
||||
"jsr:@std/http@1.0.15"
|
||||
]
|
||||
}
|
||||
|
||||
74
mod.ts
74
mod.ts
@@ -1,4 +1,5 @@
|
||||
import { hash, verify } from '@bcrypt';
|
||||
import { hash, compare } from '@bcrypt';
|
||||
import { JSZip } from '@deno-zip';
|
||||
import { customAlphabet } from '@nanoid';
|
||||
import { STATUS_CODE, STATUS_TEXT, StatusCode } from '@std/http/status';
|
||||
|
||||
@@ -43,7 +44,7 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
||||
} else if (path.startsWith('/home/')) {
|
||||
// SSR "home page"
|
||||
const userId = path.replace('/home/', '');
|
||||
const userMatch = await dbClient.query('SELECT name FROM users WHERE BINARY id = ?', [userId]).catch(() => {
|
||||
const userMatch = await dbClient.query('SELECT name FROM users WHERE id = ?', [userId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return buildPage("Couldn't read DB. Please try again.");
|
||||
@@ -53,7 +54,7 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
||||
} else if (path.startsWith('/read/')) {
|
||||
const planId = path.replace('/read/', '');
|
||||
|
||||
const plans = await dbClient.query('SELECT name, folder, data FROM plans WHERE BINARY id = ? AND deleted = 0', [planId]).catch(() => {
|
||||
const plans = await dbClient.query('SELECT name, folder, data FROM plans WHERE id = ? AND deleted = 0', [planId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
||||
@@ -62,14 +63,14 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
||||
return genericResponse(STATUS_CODE.OK, JSON.stringify(plans[0]));
|
||||
} else if (path.startsWith('/list/')) {
|
||||
const userId = path.replace('/list/', '');
|
||||
const userMatch = await dbClient.query('SELECT id FROM users WHERE BINARY id = ?', [userId]).catch(() => {
|
||||
const userMatch = await dbClient.query('SELECT id FROM users WHERE id = ?', [userId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
||||
if (!userMatch.length) return genericResponse(STATUS_CODE.NotFound, 'User ID does not exist.');
|
||||
|
||||
const plans = await dbClient
|
||||
.query('SELECT id, name, folder, lastUpdated FROM plans WHERE BINARY ownerId = ? AND deleted = 0 ORDER BY folder ASC,name ASC', [userId])
|
||||
.query('SELECT id, name, folder, lastUpdated FROM plans WHERE ownerId = ? AND deleted = 0 ORDER BY folder ASC,name ASC', [userId])
|
||||
.catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
@@ -82,19 +83,33 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
||||
return genericResponse(STATUS_CODE.OK, JSON.stringify(plans));
|
||||
} else if (path.startsWith('/export/')) {
|
||||
const userId = path.replace('/export/', '');
|
||||
const userMatch = await dbClient.query('SELECT id FROM users WHERE BINARY id = ?', [userId]).catch(() => {
|
||||
const userMatch = await dbClient.query('SELECT id, name FROM users WHERE id = ?', [userId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
||||
if (!userMatch.length) return genericResponse(STATUS_CODE.NotFound, 'User ID does not exist.');
|
||||
|
||||
// WIP: export plans to zip code goes here
|
||||
return genericResponse(STATUS_CODE.NotImplemented, 'Export function WIP.');
|
||||
const plans = await dbClient
|
||||
.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') {
|
||||
const body = await req.json();
|
||||
|
||||
const userNameMatches = await dbClient.query('SELECT name FROM users WHERE BINARY name = ?', [body.name]).catch(() => {
|
||||
const userNameMatches = await dbClient.query('SELECT name FROM users WHERE name = ?', [body.name]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
||||
@@ -123,21 +138,22 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
||||
} else {
|
||||
const body = await req.json();
|
||||
|
||||
const loginMatch = await dbClient.query('SELECT id, hash, email, deleteCode FROM users WHERE BINARY name = ?', [body.name]).catch(() => {
|
||||
const loginMatch = await dbClient.query('SELECT id, hash, email, deleteCode FROM users WHERE name = ?', [body.name]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
||||
if (loginMatch.length === 0) return genericResponse(STATUS_CODE.Forbidden, 'Invalid name/PIN combination. Remember name is case sensitive.');
|
||||
if (!(await verify(body.pin, loginMatch[0].hash))) return genericResponse(STATUS_CODE.Forbidden, 'Invalid name/PIN combination.');
|
||||
if (!(await compare(body.pin, loginMatch[0].hash))) return genericResponse(STATUS_CODE.Forbidden, 'Invalid name/PIN combination.');
|
||||
const id = loginMatch[0].id;
|
||||
const email = loginMatch[0].email;
|
||||
const hasEmail = email.length > 0;
|
||||
const hasEmail = (email ?? '').length > 0;
|
||||
const deleteCode = loginMatch[0].deleteCode;
|
||||
const deleteCodeSet = (deleteCode ?? '').length > 0;
|
||||
|
||||
switch (req.method) {
|
||||
case 'POST':
|
||||
if (path === '/auth') {
|
||||
return genericResponse(STATUS_CODE.OK, JSON.stringify({ id, hasEmail }));
|
||||
return genericResponse(STATUS_CODE.OK, JSON.stringify({ id, hasEmail, deleteCodeSet }));
|
||||
} else if (path === '/create') {
|
||||
if (body.planName.trim().length > 200) return genericResponse(STATUS_CODE.BadRequest, 'Name too long.');
|
||||
if (body.folder.trim() && body.folder.trim().length > 200) return genericResponse(STATUS_CODE.BadRequest, 'Folder name too long.');
|
||||
@@ -155,28 +171,28 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
||||
case 'PUT':
|
||||
if (path.startsWith('/undelete/')) {
|
||||
const planId = path.replace('/undelete/', '');
|
||||
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE BINARY id = ?', [planId]).catch(() => {
|
||||
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE id = ?', [planId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
||||
if (!planMatch.length) return genericResponse(STATUS_CODE.NotFound, 'Plan ID does not exist.');
|
||||
if (planMatch[0].ownerId !== id) return genericResponse(STATUS_CODE.Forbidden, "You don't own this plan.");
|
||||
|
||||
await dbClient.execute('UPDATE plans SET deleted = 0, lastUpdated = ? WHERE BINARY id = ?', [new Date(), planId]).catch(() => {
|
||||
await dbClient.execute('UPDATE plans SET deleted = 0, lastUpdated = ? WHERE id = ?', [new Date(), planId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't update DB.");
|
||||
return genericResponse(STATUS_CODE.OK, 'Plan restored.');
|
||||
} else if (path.startsWith('/update/')) {
|
||||
const planId = path.replace('/update/', '');
|
||||
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE BINARY id = ?', [planId]).catch(() => {
|
||||
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE id = ?', [planId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
||||
if (!planMatch.length) return genericResponse(STATUS_CODE.NotFound, 'Plan ID does not exist.');
|
||||
if (planMatch[0].ownerId !== id) return genericResponse(STATUS_CODE.Forbidden, "You don't own this plan.");
|
||||
|
||||
await dbClient.execute('UPDATE plans SET data = ?, lastUpdated = ? WHERE BINARY id = ?', [body.data, new Date(), planId]).catch(() => {
|
||||
await dbClient.execute('UPDATE plans SET data = ?, lastUpdated = ? WHERE id = ?', [body.data, new Date(), planId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't update DB.");
|
||||
@@ -185,14 +201,14 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
||||
if (body.planName.trim().length > 200) return genericResponse(STATUS_CODE.BadRequest, 'Name too long.');
|
||||
|
||||
const planId = path.replace('/rename/', '');
|
||||
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE BINARY id = ?', [planId]).catch(() => {
|
||||
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE id = ?', [planId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
||||
if (!planMatch.length) return genericResponse(STATUS_CODE.NotFound, 'Plan ID does not exist.');
|
||||
if (planMatch[0].ownerId !== id) return genericResponse(STATUS_CODE.Forbidden, "You don't own this plan.");
|
||||
|
||||
await dbClient.execute('UPDATE plans SET name = ?, lastUpdated = ? WHERE BINARY id = ?', [body.planName, new Date(), planId]).catch(() => {
|
||||
await dbClient.execute('UPDATE plans SET name = ?, lastUpdated = ? WHERE id = ?', [body.planName, new Date(), planId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't update DB.");
|
||||
@@ -201,14 +217,14 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
||||
if (body.folder.trim().length > 200) return genericResponse(STATUS_CODE.BadRequest, 'Folder name too long.');
|
||||
|
||||
const planId = path.replace('/move/', '');
|
||||
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE BINARY id = ?', [planId]).catch(() => {
|
||||
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE id = ?', [planId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
||||
if (!planMatch.length) return genericResponse(STATUS_CODE.NotFound, 'Plan ID does not exist.');
|
||||
if (planMatch[0].ownerId !== id) return genericResponse(STATUS_CODE.Forbidden, "You don't own this plan.");
|
||||
|
||||
await dbClient.execute('UPDATE plans SET folder = ?, lastUpdated = ? WHERE BINARY id = ?', [body.folder, new Date(), planId]).catch(() => {
|
||||
await dbClient.execute('UPDATE plans SET folder = ?, lastUpdated = ? WHERE id = ?', [body.folder, new Date(), planId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't update DB.");
|
||||
@@ -218,12 +234,12 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
||||
case 'DELETE':
|
||||
if (path === '/unenroll') {
|
||||
if (!hasEmail || body.deleteCode.trim() === deleteCode) {
|
||||
await dbClient.execute('DELETE FROM plans WHERE BINARY ownerId = ?', [id]).catch(() => {
|
||||
await dbClient.execute('DELETE FROM plans WHERE ownerId = ?', [id]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't delete plans.");
|
||||
|
||||
await dbClient.execute('DELETE FROM users WHERE BINARY id = ?', [id]).catch(() => {
|
||||
await dbClient.execute('DELETE FROM users WHERE id = ?', [id]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't delete user.");
|
||||
@@ -231,7 +247,7 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
||||
return genericResponse(STATUS_CODE.OK, 'Deleted user and plans.');
|
||||
} else if (hasEmail && !deleteCode) {
|
||||
const newDeleteCode = nanoid();
|
||||
await dbClient.execute('UPDATE users SET deleteCode = ? WHERE BINARY id = ?', [newDeleteCode, id]).catch(() => {
|
||||
await dbClient.execute('UPDATE users SET deleteCode = ? WHERE id = ?', [newDeleteCode, id]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't set deleteCode.");
|
||||
@@ -258,7 +274,7 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
||||
fromAddress: config.email.address,
|
||||
toAddress: email,
|
||||
subject: 'XIVPlan+DB Delete Code',
|
||||
content: `Notice: account deletion is permanent and will delete all plans saved under your account.<br/><br/>Please use the following Delete Code to delete your account:<br/><br/>${newDeleteCode}`,
|
||||
content: `Notice: Account deletion is permanent and will permanently delete all plans saved under your account.<br/><br/>Please use the following Delete Code to delete your account:<br/><br/>${newDeleteCode}`,
|
||||
}),
|
||||
}).catch(() => {
|
||||
failed = true;
|
||||
@@ -282,21 +298,21 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
||||
}
|
||||
} else if (path.startsWith('/delete/')) {
|
||||
const planId = path.replace('/delete/', '');
|
||||
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE BINARY id = ?', [planId]).catch(() => {
|
||||
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE id = ?', [planId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
||||
if (!planMatch.length) return genericResponse(STATUS_CODE.NotFound, 'Plan ID does not exist.');
|
||||
if (planMatch[0].ownerId !== id) return genericResponse(STATUS_CODE.Forbidden, "You don't own this plan.");
|
||||
|
||||
await dbClient.execute('UPDATE plans SET deleted = 1, lastUpdated = ? WHERE BINARY id = ?', [new Date(), planId]).catch(() => {
|
||||
await dbClient.execute('UPDATE plans SET deleted = 1, lastUpdated = ? WHERE id = ?', [new Date(), planId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't update DB.");
|
||||
return genericResponse(STATUS_CODE.OK, 'Plan deleted.');
|
||||
} else if (path.startsWith('/perm-delete/')) {
|
||||
const planId = path.replace('/perm-delete/', '');
|
||||
const planMatch = await dbClient.query('SELECT ownerId, deleted FROM plans WHERE BINARY id = ?', [planId]).catch(() => {
|
||||
const planMatch = await dbClient.query('SELECT ownerId, deleted FROM plans WHERE id = ?', [planId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
||||
@@ -304,7 +320,7 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
||||
if (planMatch[0].ownerId !== id) return genericResponse(STATUS_CODE.Forbidden, "You don't own this plan.");
|
||||
if (!planMatch[0].deleted) return genericResponse(STATUS_CODE.Forbidden, 'Plan must be marked as deleted to perm delete.');
|
||||
|
||||
await dbClient.execute('DELETE FROM plans WHERE BINARY id = ? AND deleted = 1', [planId]).catch(() => {
|
||||
await dbClient.execute('DELETE FROM plans WHERE id = ? AND deleted = 1', [planId]).catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't update DB.");
|
||||
|
||||
129
ssr/buildHome.ts
129
ssr/buildHome.ts
@@ -11,8 +11,8 @@ interface Plan {
|
||||
|
||||
const makePlanButtons = (planId: string, deleted: boolean) =>
|
||||
deleted
|
||||
? `<button onclick="doAction('undelete','${planId}')">restore</button><button 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="doAction('undelete','${planId}')">restore</button><button class="btn" onclick="doAction('perm-delete','${planId}')">perm 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) =>
|
||||
`<li>${plan.folder}${plan.folder && '/'}${plan.name} - ${plan.lastUpdated.toLocaleString()} - ${makePlanButtons(plan.id, deleted)}</li>`;
|
||||
@@ -21,18 +21,14 @@ export default async (userId: string, userName: string) => {
|
||||
let failed = false;
|
||||
|
||||
const plans: Plan[] = await dbClient
|
||||
.query('SELECT id, name, folder, lastUpdated FROM plans WHERE BINARY ownerId = ? AND deleted = 0 GROUP BY folder,name,id ORDER BY folder ASC,name ASC', [
|
||||
userId,
|
||||
])
|
||||
.query('SELECT id, name, folder, lastUpdated FROM plans WHERE ownerId = ? AND deleted = 0 GROUP BY folder,name,id ORDER BY folder ASC,name ASC', [userId])
|
||||
.catch((e) => {
|
||||
failed = true;
|
||||
});
|
||||
if (failed) return "Couldn't read DB.";
|
||||
|
||||
const deletedPlans: Plan[] = await dbClient
|
||||
.query('SELECT id, name, folder, lastUpdated FROM plans WHERE BINARY ownerId = ? AND deleted = 1 GROUP BY folder,name,id ORDER BY folder ASC,name ASC', [
|
||||
userId,
|
||||
])
|
||||
.query('SELECT id, name, folder, lastUpdated FROM plans WHERE ownerId = ? AND deleted = 1 GROUP BY folder,name,id ORDER BY folder ASC,name ASC', [userId])
|
||||
.catch(() => {
|
||||
failed = true;
|
||||
});
|
||||
@@ -40,37 +36,123 @@ export default async (userId: string, userName: string) => {
|
||||
|
||||
return `<div>
|
||||
<script>
|
||||
const actionMethod = new Map([['rename','PUT'],['move','PUT'],['undelete','PUT'],['delete','DELETE'],['perm-delete','DELETE']]);
|
||||
const actionMethod = new Map([
|
||||
['rename', 'PUT'],
|
||||
['move', 'PUT'],
|
||||
['undelete', 'PUT'],
|
||||
['delete', 'DELETE'],
|
||||
['perm-delete', 'DELETE'],
|
||||
]);
|
||||
function doAction(action, planId) {
|
||||
let newName = '';
|
||||
if(action==='rename'||action==='move'){newName=prompt(\`Please provide a new \${action==='rename'?'plan':'folder'} name:\`);if(!newName){return;}}
|
||||
if (action === 'rename' || action === 'move') {
|
||||
newName = prompt(\`Please provide a new \${action==='rename'?'plan':'folder'} name:\`);
|
||||
if (!newName) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let userName = localStorage.getItem('name');
|
||||
if(!userName){userName=prompt('Please enter your username:');if(!userName){return;}}
|
||||
if (!userName) {
|
||||
userName = prompt('Please enter your username:');
|
||||
if (!userName) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const userPIN = prompt('Please enter your PIN:');
|
||||
fetch(\`/api/\${action}/\${planId}\`,{method:actionMethod.get(action),body:JSON.stringify({name:userName,pin:userPIN,folder:newName,planName:newName})})
|
||||
.catch((e)=>{e.text().then((text)=>{alert(text);});})
|
||||
fetch(\`/api/\${action}/\${planId}\`, {
|
||||
method: actionMethod.get(action),
|
||||
body: JSON.stringify({ name: userName, pin: userPIN, folder: newName, planName: newName }),
|
||||
})
|
||||
.catch((e) => {
|
||||
e.text().then((text) => {
|
||||
alert(text);
|
||||
});
|
||||
})
|
||||
.then((r) => {
|
||||
if(r.status===200){localStorage.setItem('name',userName);r.text().then((text)=>{alert(text);window.location.reload();});}
|
||||
else{r.text().then((text)=>{alert(text);});}
|
||||
if (r.status === 200) {
|
||||
localStorage.setItem('name', userName);
|
||||
r.text().then((text) => {
|
||||
alert(text);
|
||||
window.location.reload();
|
||||
});
|
||||
} else {
|
||||
r.text().then((text) => {
|
||||
alert(text);
|
||||
});
|
||||
}
|
||||
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) {
|
||||
window.open(\`${config.api.publicDomain}share#\${planId}\`);
|
||||
}
|
||||
function openPlan(planId){window.open(\`${config.api.publicDomain}share#\${planId}\`);}
|
||||
async function sharePlan(planId) {
|
||||
const link = \`${config.api.publicDomain}share#\${planId}\`;
|
||||
try{await navigator.clipboard.writeText(link);alert('Link copied to clipboard');}
|
||||
catch (error){prompt('Failed to copy to clipboard, please select and copy the link below:',link);}
|
||||
try {
|
||||
await navigator.clipboard.writeText(link);
|
||||
alert('Link copied to clipboard');
|
||||
} catch (error) {
|
||||
prompt('Failed to copy to clipboard, please select and copy the link below:', link);
|
||||
}
|
||||
}
|
||||
function deleteAccount() {
|
||||
const userName = prompt('Please enter your username:');
|
||||
if (!userName.trim()) {
|
||||
return;
|
||||
}
|
||||
const userPin = prompt('Please enter your PIN:');
|
||||
if (!userPin.trim()) {
|
||||
return;
|
||||
}
|
||||
if (!confirm('Are you sure you want to delete your account? This is permanent and irreversible, and will delete all plans saved to your account as well.')) {
|
||||
return;
|
||||
}
|
||||
fetch('/api/auth', { method: 'POST', body: JSON.stringify({ name: userName.trim(), pin: userPin.trim() }) })
|
||||
.catch((e) => {
|
||||
e.text().then((text) => {
|
||||
alert(text);
|
||||
});
|
||||
})
|
||||
.then((r) => {
|
||||
if (r.status === 200) {
|
||||
r.json().then((j) => {
|
||||
let deleteCode = '';
|
||||
if (j.hasEmail && j.deleteCodeSet) {
|
||||
deleteCode = prompt("Please enter the Delete Code emailed to you (if you don't see it, please check your spam folder):");
|
||||
}
|
||||
if (j.hasEmail && j.deleteCodeSet && !deleteCode) {
|
||||
alert('Delete code required.');
|
||||
return;
|
||||
}
|
||||
fetch('/api/unenroll', { method: 'DELETE', body: JSON.stringify({ name: userName.trim(), pin: userPin.trim(), deleteCode: deleteCode.trim() }) })
|
||||
.catch((e) => {
|
||||
e.text().then((text) => {
|
||||
alert(text);
|
||||
});
|
||||
})
|
||||
.then((r) => {
|
||||
if (r.status === 200) {
|
||||
alert('Account and plans deleted.');
|
||||
window.location.reload();
|
||||
} else {
|
||||
r.text().then((text) => {
|
||||
alert(text);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
r.text().then((text) => {
|
||||
alert(text);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<p>This is a very basic management page. Please excuse the number of alert/prompts that will come up when you click on things as it was the quickest way to build it out.</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>
|
||||
<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>
|
||||
${plans.map((plan) => makePlanItem(plan, false)).join('')}
|
||||
</ul>
|
||||
@@ -78,5 +160,6 @@ ${plans.map((plan) => makePlanItem(plan, false)).join('')}
|
||||
<ul>
|
||||
${deletedPlans.map((plan) => makePlanItem(plan, true)).join('')}
|
||||
</ul>
|
||||
<button class="btn" onclick="deleteAccount()">Delete my account</button>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
@@ -20,6 +20,15 @@ a {
|
||||
text-decoration:none;
|
||||
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 {
|
||||
margin:0.5rem 0;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
deno run --allow-net --allow-import mod.ts
|
||||
deno run --allow-net --allow-import --allow-env mod.ts
|
||||
19
xivplan-db.rc
Executable file
19
xivplan-db.rc
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
|
||||
# PROVIDE: xivplan_db
|
||||
|
||||
. /etc/rc.subr
|
||||
|
||||
name="xivplan_db"
|
||||
rcvar="xivplan_db_enable"
|
||||
pidfile="/var/webapps/XIVPlan-DB/xivplan_db.pid"
|
||||
|
||||
xivplan_db_root="/var/webapps/XIVPlan-DB"
|
||||
xivplan_db_log="/var/log/xivplan_db.log"
|
||||
|
||||
xivplan_db_chdir="${xivplan_db_root}"
|
||||
command="/usr/sbin/daemon"
|
||||
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
|
||||
run_rc_command "$1"
|
||||
Reference in New Issue
Block a user