|
|
|
|
@@ -1,3 +1,4 @@
|
|
|
|
|
import { hash, verify } from '@bcrypt';
|
|
|
|
|
import { customAlphabet } from '@nanoid';
|
|
|
|
|
import { STATUS_CODE, STATUS_TEXT, StatusCode } from '@std/http/status';
|
|
|
|
|
|
|
|
|
|
@@ -42,7 +43,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 id = ?', [userId]).catch(() => {
|
|
|
|
|
const userMatch = await dbClient.query('SELECT name FROM users WHERE BINARY id = ?', [userId]).catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
if (failed) return buildPage("Couldn't read DB. Please try again.");
|
|
|
|
|
@@ -52,7 +53,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 id = ? AND deleted = 0', [planId]).catch(() => {
|
|
|
|
|
const plans = await dbClient.query('SELECT name, folder, data FROM plans WHERE BINARY id = ? AND deleted = 0', [planId]).catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
|
|
|
|
@@ -61,14 +62,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 id = ?', [userId]).catch(() => {
|
|
|
|
|
const userMatch = await dbClient.query('SELECT id FROM users WHERE BINARY 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 ownerId = ? AND deleted = 0 ORDER BY folder ASC,name ASC', [userId])
|
|
|
|
|
.query('SELECT id, name, folder, lastUpdated FROM plans WHERE BINARY ownerId = ? AND deleted = 0 ORDER BY folder ASC,name ASC', [userId])
|
|
|
|
|
.catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
@@ -81,7 +82,7 @@ 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 id = ?', [userId]).catch(() => {
|
|
|
|
|
const userMatch = await dbClient.query('SELECT id FROM users WHERE BINARY id = ?', [userId]).catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
|
|
|
|
@@ -93,7 +94,7 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
|
|
|
|
} else if (req.method === 'POST' && path === '/enroll') {
|
|
|
|
|
const body = await req.json();
|
|
|
|
|
|
|
|
|
|
const userNameMatches = await dbClient.query('SELECT name FROM users WHERE name = ?', [body.name]).catch(() => {
|
|
|
|
|
const userNameMatches = await dbClient.query('SELECT name FROM users WHERE BINARY name = ?', [body.name]).catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
|
|
|
|
@@ -101,12 +102,13 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
|
|
|
|
if (userNameMatches.length === 0) {
|
|
|
|
|
if (body.name.length < 4 || body.name.length > 20)
|
|
|
|
|
return genericResponse(STATUS_CODE.BadRequest, `Name too ${body.name.length < 4 ? 'short' : 'long'}.`);
|
|
|
|
|
if (body.pin.length < 4 || body.pin.length > 16) return genericResponse(STATUS_CODE.BadRequest, `PIN too ${body.pin.length < 4 ? 'short' : 'long'}.`);
|
|
|
|
|
if (body.pin.length < 4) return genericResponse(STATUS_CODE.BadRequest, `PIN too ${body.pin.length < 4 ? 'short' : 'long'}.`);
|
|
|
|
|
if (body.email.length > 255) return genericResponse(STATUS_CODE.BadRequest, 'Email too long.');
|
|
|
|
|
|
|
|
|
|
const id = nanoid();
|
|
|
|
|
const pinHash = await hash(body.pin);
|
|
|
|
|
|
|
|
|
|
await dbClient.execute('INSERT INTO users(id,name,pin,email) values(?,?,?,?)', [id, body.name, body.pin, body.email]).catch(() => {
|
|
|
|
|
await dbClient.execute('INSERT INTO users(id,name,hash,email) values(?,?,?,?)', [id, body.name, pinHash, body.email]).catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@@ -121,11 +123,12 @@ Deno.serve({ port: config.api.port }, async (req) => {
|
|
|
|
|
} else {
|
|
|
|
|
const body = await req.json();
|
|
|
|
|
|
|
|
|
|
const loginMatch = await dbClient.query('SELECT id, email, deleteCode FROM users WHERE name = ? AND pin = ?', [body.name, body.pin]).catch(() => {
|
|
|
|
|
const loginMatch = await dbClient.query('SELECT id, hash, email, deleteCode FROM users WHERE BINARY 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.');
|
|
|
|
|
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.');
|
|
|
|
|
const id = loginMatch[0].id;
|
|
|
|
|
const email = loginMatch[0].email;
|
|
|
|
|
const hasEmail = email.length > 0;
|
|
|
|
|
@@ -152,28 +155,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 id = ?', [planId]).catch(() => {
|
|
|
|
|
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE BINARY 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 id = ?', [new Date(), planId]).catch(() => {
|
|
|
|
|
await dbClient.execute('UPDATE plans SET deleted = 0, lastUpdated = ? WHERE BINARY 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 id = ?', [planId]).catch(() => {
|
|
|
|
|
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE BINARY 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 id = ?', [body.data, new Date(), planId]).catch(() => {
|
|
|
|
|
await dbClient.execute('UPDATE plans SET data = ?, lastUpdated = ? WHERE BINARY id = ?', [body.data, new Date(), planId]).catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't update DB.");
|
|
|
|
|
@@ -182,14 +185,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 id = ?', [planId]).catch(() => {
|
|
|
|
|
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE BINARY 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 id = ?', [body.planName, new Date(), planId]).catch(() => {
|
|
|
|
|
await dbClient.execute('UPDATE plans SET name = ?, lastUpdated = ? WHERE BINARY id = ?', [body.planName, new Date(), planId]).catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't update DB.");
|
|
|
|
|
@@ -198,14 +201,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 id = ?', [planId]).catch(() => {
|
|
|
|
|
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE BINARY 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 id = ?', [body.folder, new Date(), planId]).catch(() => {
|
|
|
|
|
await dbClient.execute('UPDATE plans SET folder = ?, lastUpdated = ? WHERE BINARY id = ?', [body.folder, new Date(), planId]).catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't update DB.");
|
|
|
|
|
@@ -215,12 +218,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 ownerId = ?', [id]).catch(() => {
|
|
|
|
|
await dbClient.execute('DELETE FROM plans WHERE BINARY ownerId = ?', [id]).catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't delete plans.");
|
|
|
|
|
|
|
|
|
|
await dbClient.execute('DELETE FROM users WHERE id = ?', [id]).catch(() => {
|
|
|
|
|
await dbClient.execute('DELETE FROM users WHERE BINARY id = ?', [id]).catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't delete user.");
|
|
|
|
|
@@ -228,7 +231,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 id = ?', [newDeleteCode, id]).catch(() => {
|
|
|
|
|
await dbClient.execute('UPDATE users SET deleteCode = ? WHERE BINARY id = ?', [newDeleteCode, id]).catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't set deleteCode.");
|
|
|
|
|
@@ -279,21 +282,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 id = ?', [planId]).catch(() => {
|
|
|
|
|
const planMatch = await dbClient.query('SELECT ownerId FROM plans WHERE BINARY 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 id = ?', [new Date(), planId]).catch(() => {
|
|
|
|
|
await dbClient.execute('UPDATE plans SET deleted = 1, lastUpdated = ? WHERE BINARY 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 id = ?', [planId]).catch(() => {
|
|
|
|
|
const planMatch = await dbClient.query('SELECT ownerId, deleted FROM plans WHERE BINARY id = ?', [planId]).catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't read DB.");
|
|
|
|
|
@@ -301,7 +304,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 id = ? AND deleted = 1', [planId]).catch(() => {
|
|
|
|
|
await dbClient.execute('DELETE FROM plans WHERE BINARY id = ? AND deleted = 1', [planId]).catch(() => {
|
|
|
|
|
failed = true;
|
|
|
|
|
});
|
|
|
|
|
if (failed) return genericResponse(STATUS_CODE.InternalServerError, "Couldn't update DB.");
|
|
|
|
|
|