Files
XIVPlan-DB/ssr/buildHome.ts
2026-04-23 19:09:30 -04:00

179 lines
5.9 KiB
TypeScript

import config from '~config';
import dbClient from 'db/client.ts';
interface Plan {
id: string;
name: string;
folder: string;
lastUpdated: Date;
}
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>`;
const makePlanItem = (plan: Plan, deleted: boolean) =>
`<li>${plan.folder}${plan.folder && '/'}${plan.name} - ${plan.lastUpdated.toLocaleString()} - ${makePlanButtons(plan.id, deleted)}</li>`;
export default async (userId: string, userName: string) => {
let failed = false;
const plans: Plan[] = await dbClient
.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 ownerId = ? AND deleted = 1 GROUP BY folder,name,id ORDER BY folder ASC,name ASC', [userId])
.catch(() => {
failed = true;
});
if (failed) return "Couldn't read DB.";
return `<div>
<script>
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;
}
}
let userName = localStorage.getItem('name');
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);
});
})
.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);
});
}
});
}
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}\`);
}
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);
}
}
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>
<ul>
${plans.map((plan) => makePlanItem(plan, false)).join('')}
</ul>
<h3>${userName}'s Deleted Plans:</h3>
<ul>
${deletedPlans.map((plan) => makePlanItem(plan, true)).join('')}
</ul>
<button onclick="deleteAccount()">Delete my account</button>
</div>`;
};