96 lines
5.0 KiB
TypeScript
96 lines
5.0 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>`;
|
|
};
|