Initial commit
This commit is contained in:
commit
1e63e008af
48 changed files with 12715 additions and 0 deletions
112
server/api/clients.get.ts
Normal file
112
server/api/clients.get.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
/* global defineEventHandler getQuery, createError */
|
||||
import { QueryObject } from "ufo";
|
||||
|
||||
import { data, database } from "../utils/database";
|
||||
import { isString } from "../utils/isString";
|
||||
import { client } from "~/utils/types/database";
|
||||
|
||||
type queryType = {
|
||||
type: "before" | "after" | "around",
|
||||
id: string
|
||||
} | {
|
||||
type: null
|
||||
};
|
||||
|
||||
function getLocationParameterType(query: QueryObject): queryType {
|
||||
const before = query.before;
|
||||
const after = query.after;
|
||||
const around = query.around;
|
||||
|
||||
let setLocationParametersCount = 0;
|
||||
let rvalue: queryType = { type: null };
|
||||
|
||||
if (isString(before)) {
|
||||
setLocationParametersCount++;
|
||||
rvalue = { type: "before", id: before };
|
||||
}
|
||||
|
||||
if (isString(after)) {
|
||||
setLocationParametersCount++;
|
||||
rvalue = { type: "after", id: after };
|
||||
}
|
||||
|
||||
if (isString(around)) {
|
||||
setLocationParametersCount++;
|
||||
rvalue = { type: "around", id: around };
|
||||
}
|
||||
|
||||
if (setLocationParametersCount > 1) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: "multiple location parameters not allowed",
|
||||
});
|
||||
}
|
||||
|
||||
return rvalue;
|
||||
}
|
||||
|
||||
async function getResults(
|
||||
queryType: queryType,
|
||||
limit = 50,
|
||||
) {
|
||||
switch (queryType.type) {
|
||||
case "before": {
|
||||
const [data] = await database.query(
|
||||
"SELECT *, CONVERT(`id`, CHAR) AS `id` FROM `clients` WHERE `id` < ? ORDER BY `id` DESC LIMIT ?",
|
||||
[queryType.id, limit],
|
||||
) as unknown as data<client>;
|
||||
return data;
|
||||
}
|
||||
case "after": {
|
||||
const [data] = await database.query(
|
||||
"SELECT *, CONVERT(`id`, CHAR) AS `id` FROM `clients` WHERE `id` > ? ORDER BY `id` DESC LIMIT ?",
|
||||
[queryType.id, limit],
|
||||
) as unknown as data<client>;
|
||||
return data;
|
||||
}
|
||||
case "around": {
|
||||
const [data] = await database.query(
|
||||
"(SELECT *, CONVERT(`id`, CHAR) AS `id` FROM `clients` WHERE `id` >= ? ORDER BY `id` ASC LIMIT ?)\n" +
|
||||
"UNION ALL\n" +
|
||||
"(SELECT *, CONVERT(`id`, CHAR) AS `id` FROM `clients` WHERE `id` < ? ORDER BY `id` DESC LIMIT ?)\n" +
|
||||
"ORDER BY `id` DESC",
|
||||
[queryType.id, Math.ceil(limit / 2), queryType.id, Math.floor(limit / 2)],
|
||||
) as unknown as data<client>;
|
||||
return data;
|
||||
}
|
||||
case null: {
|
||||
const [data] = await database.query(
|
||||
"SELECT *, CONVERT(`id`, CHAR) AS `id` FROM `clients` ORDER BY `id` DESC LIMIT ?",
|
||||
[limit],
|
||||
) as unknown as data<client>;
|
||||
return data;
|
||||
}
|
||||
default:
|
||||
throw createError("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const query = getQuery(e);
|
||||
|
||||
let limit = 50;
|
||||
if (query.limit) limit = Number(query.limit);
|
||||
if (limit > 200) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: "Cannot retrieve more than 200 records",
|
||||
});
|
||||
}
|
||||
if (limit <= 0) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: "Tried to retireve 0 or less records",
|
||||
});
|
||||
}
|
||||
|
||||
const queryData = getLocationParameterType(query);
|
||||
|
||||
const result = await getResults(queryData, limit);
|
||||
|
||||
return result;
|
||||
});
|
67
server/api/clients.post.ts
Normal file
67
server/api/clients.post.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
/* global defineEventHandler, createError, readBody */
|
||||
|
||||
import { database } from "../utils/database";
|
||||
import Snowflake from "../utils/snowflake";
|
||||
import { client } from "~/utils/types/database";
|
||||
|
||||
const clientKeys = [
|
||||
"name",
|
||||
"address",
|
||||
"phone",
|
||||
"email",
|
||||
];
|
||||
|
||||
export function checkIsClient(
|
||||
value: any,
|
||||
required = true,
|
||||
): value is Partial<Omit<client, "id">> {
|
||||
const errors = new Map<string, string>();
|
||||
|
||||
if (typeof value !== "object") {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: "Invalid body",
|
||||
});
|
||||
}
|
||||
|
||||
if (!(typeof value.name === "string" || value.name === null || (!required && value.name === undefined))) errors.set("name", "is not string or null");
|
||||
if (!(typeof value.address === "string" || value.address === null || (!required && value.address === undefined))) errors.set("address", "is not string or null");
|
||||
if (!(typeof value.phone === "string" || value.phone === null || (!required && value.phone === undefined))) errors.set("phone", "is not string or null");
|
||||
if (!(typeof value.email === "string" || value.email === null || (!required && value.email === undefined))) errors.set("email", "is not string or null");
|
||||
|
||||
for (const i in value)
|
||||
if (!clientKeys.includes(i)) errors.set(i, `excessive property`);
|
||||
|
||||
if (errors.size !== 0) {
|
||||
let message = "Invalid Parameters: ";
|
||||
for (const i in errors)
|
||||
message += i + ", ";
|
||||
message = message.slice(0, -2);
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message,
|
||||
data: {
|
||||
errors: Object.fromEntries(errors),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const body = await readBody(e);
|
||||
const id = new Snowflake().toString();
|
||||
|
||||
if (!checkIsClient(body)) return; // checkIsClient already throws an detailed error
|
||||
|
||||
await database.query(
|
||||
"INSERT INTO `clients` VALUES (?, ?, ?, ?, ?)",
|
||||
[id, body.name, body.address, body.phone, body.email],
|
||||
);
|
||||
|
||||
// FIXME: data may be turncated in the database
|
||||
// either throw an error when data is too large or
|
||||
// reply with turncated data
|
||||
return { id, ...body };
|
||||
});
|
17
server/api/clients/[id].delete.ts
Normal file
17
server/api/clients/[id].delete.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/* global defineEventHandler, createError */
|
||||
import { ResultSetHeader } from "mysql2";
|
||||
|
||||
import { database } from "~/server/utils/database";
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = e.context.params?.id as string;
|
||||
|
||||
const [result] = await database.query(
|
||||
"DELETE FROM `clients` WHERE `id` = ?",
|
||||
[id],
|
||||
) as unknown as [ResultSetHeader];
|
||||
|
||||
if (result.affectedRows === 0) throw createError({ statusCode: 404 });
|
||||
|
||||
return null;
|
||||
});
|
20
server/api/clients/[id].get.ts
Normal file
20
server/api/clients/[id].get.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* global defineEventHandler, createError */
|
||||
|
||||
import { database, data } from "~/server/utils/database";
|
||||
import { client } from "~/utils/types/database";
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const id = e.context.params?.id;
|
||||
const [data] = await database.query(
|
||||
"SELECT *, CONVERT(`id`, CHAR) AS `id` FROM `clients` WHERE `id` = ?",
|
||||
[id],
|
||||
) as unknown as data<client>;
|
||||
|
||||
if (!data[0]) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
|
||||
return data[0];
|
||||
});
|
36
server/api/clients/[id].patch.ts
Normal file
36
server/api/clients/[id].patch.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/* global defineEventHandler, readBody, createError */
|
||||
|
||||
import { ResultSetHeader } from "mysql2";
|
||||
|
||||
import { checkIsClient } from "../clients.post";
|
||||
import { client } from "~/utils/types/database";
|
||||
import { database, data } from "~/server/utils/database";
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const body = await readBody(e);
|
||||
const id = e.context.params?.id as string;
|
||||
|
||||
if (!checkIsClient(body, false)) return; // checkIsClient already throws an detailed error
|
||||
|
||||
for (const [k, v] of Object.entries(body)) {
|
||||
const [res] = await database.query(
|
||||
// I believe it is safe to put key in the template
|
||||
// because it is limited to 4 values here
|
||||
`UPDATE \`clients\` SET \`${k}\` = ? WHERE \`id\` = ?`,
|
||||
[v, id],
|
||||
) as unknown as [ResultSetHeader];
|
||||
|
||||
if (res.affectedRows !== 1) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const [data] = await database.query(
|
||||
"SELECT *, CONVERT(`id`, CHAR) AS `id` FROM `clients` WHERE `id` = ?",
|
||||
[id],
|
||||
) as unknown as data<client>;
|
||||
|
||||
return data[0];
|
||||
});
|
12
server/api/clients/count.get.ts
Normal file
12
server/api/clients/count.get.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/* global defineEventHandler, createError */
|
||||
|
||||
import { database, data } from "~/server/utils/database";
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const [[data]] = await database.query(
|
||||
"SELECT COUNT(*) as `count` FROM `clients`",
|
||||
) as unknown as data<{count: number}>;
|
||||
|
||||
if (!data) throw createError("Database returned no rows");
|
||||
return data;
|
||||
});
|
9
server/api/dbtest.get.ts
Normal file
9
server/api/dbtest.get.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/* global defineEventHandler */
|
||||
|
||||
import { database } from "../utils/database";
|
||||
|
||||
export default defineEventHandler(async () => {
|
||||
const [owo] = await database.execute("SELECT * FROM `sch_baza_smartfony`.`lombardy`");
|
||||
|
||||
return owo as {id: number, nazwa: string, adres: string, kontakt: string}[];
|
||||
});
|
26
server/api/dbtest.post.ts
Normal file
26
server/api/dbtest.post.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/* global defineEventHandler, readBody */
|
||||
|
||||
import { RowDataPacket } from "mysql2";
|
||||
|
||||
import { database } from "../utils/database";
|
||||
import { isString } from "../utils/isString";
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const data = await readBody(e);
|
||||
|
||||
const nazwa = data.nazwa;
|
||||
const adres = data.adres;
|
||||
const kontakt = data.kontakt;
|
||||
|
||||
if (!isString(nazwa)) throw new Error("nazwa is not string");
|
||||
if (!isString(adres)) throw new Error("adres is not string");
|
||||
if (!isString(kontakt)) throw new Error("kontakt is not string");
|
||||
|
||||
const [inserted] = await database.query("INSERT INTO `sch_baza_smartfony`.`lombardy` (`nazwa`, `adres`, `kontakt`) VALUES (?, ?, ?);", [nazwa, adres, kontakt]) as RowDataPacket[];
|
||||
return {
|
||||
id: inserted.insertId as number,
|
||||
nazwa,
|
||||
adres,
|
||||
kontakt,
|
||||
};
|
||||
});
|
9
server/api/dbtest/[id].delete.ts
Normal file
9
server/api/dbtest/[id].delete.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/* global defineEventHandler */
|
||||
|
||||
import { database } from "~/server/utils/database";
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
if (!e.context.params?.id) return Error("id is not provided");
|
||||
const rowID = e.context.params.id;
|
||||
await database.execute("DELETE FROM `sch_baza_smartfony`.`lombardy` WHERE `id` = ?", [rowID]);
|
||||
});
|
6
server/api/echo.post.ts
Normal file
6
server/api/echo.post.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
/* global defineEventHandler */
|
||||
|
||||
export default defineEventHandler((event) => {
|
||||
const message = event.node.req.read();
|
||||
return message;
|
||||
});
|
5
server/api/hi.ts
Normal file
5
server/api/hi.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
/* global defineEventHandler */
|
||||
|
||||
export default defineEventHandler(() => {
|
||||
return "Hi mom!";
|
||||
});
|
40
server/api/login.post.ts
Normal file
40
server/api/login.post.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/* global defineEventHandler, getCookie, setCookie, readBody, createError */
|
||||
import crypto from "crypto";
|
||||
|
||||
import { database, data } from "../utils/database";
|
||||
import { isString } from "../utils/isString";
|
||||
import Snowflake from "../utils/snowflake";
|
||||
import { cookieSettings } from "../utils/rootUtils";
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
if (getCookie(e, "token"))
|
||||
throw createError({ statusCode: 501, message: "Case not implemented: logging in while cookie is set" });
|
||||
await new Promise(resolve => setTimeout(resolve, 420));
|
||||
const data = await readBody(e);
|
||||
|
||||
const login = data.login;
|
||||
const password = data.password;
|
||||
|
||||
if (!isString(login)) throw createError({ statusCode: 400, message: "Login is not string." });
|
||||
if (!isString(password)) throw createError({ statusCode: 400, message: "Password is not string." });
|
||||
|
||||
const hashedPassword = crypto.createHmac("sha512", "42")
|
||||
.update(password)
|
||||
.digest("hex");
|
||||
|
||||
const [account] = await database.query(
|
||||
"SELECT CONVERT(`id`, CHAR(32)) AS `id` from `users` WHERE `username` = ? AND LOWER(HEX(`password`)) = ? LIMIT 1",
|
||||
[login, hashedPassword],
|
||||
)as unknown as data<{id: string}>;
|
||||
|
||||
if (account.length === 0) throw createError({ statusCode: 400, message: "Invalid username or password." });
|
||||
|
||||
const sessionId = new Snowflake().toString();
|
||||
|
||||
await database.query(
|
||||
"INSERT INTO `sessions` (`id`, `user`) VALUES ( ? , ? )",
|
||||
[sessionId, account[0].id],
|
||||
);
|
||||
setCookie(e, "token", sessionId, cookieSettings);
|
||||
return { message: "Login successful", token: sessionId };
|
||||
});
|
29
server/api/logout.ts
Normal file
29
server/api/logout.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/* global defineEventHandler, createError, getCookie, deleteCookie */
|
||||
|
||||
import { isAuthorised } from "../middleware/auth";
|
||||
import { database } from "../utils/database";
|
||||
import { cookieSettings } from "../utils/rootUtils";
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const token = getCookie(e, "token");
|
||||
if (token === undefined) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
data: "You can't log out if you're already logged out (no session cookie)",
|
||||
});
|
||||
}
|
||||
|
||||
deleteCookie(e, "token", cookieSettings);
|
||||
if (!await isAuthorised(token)) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
message: "You can't log out if you're already logged out (session expired or never existed)",
|
||||
});
|
||||
}
|
||||
|
||||
database.query(
|
||||
"DELETE FROM `sessions` WHERE `id` = ?",
|
||||
[token],
|
||||
);
|
||||
return { message: "Logged out" };
|
||||
});
|
14
server/api/users/me.get.ts
Normal file
14
server/api/users/me.get.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/* global defineEventHandler, getCookie */
|
||||
|
||||
import { database, data } from "~/server/utils/database";
|
||||
import { user } from "~/utils/types/database";
|
||||
|
||||
export default defineEventHandler(async (e) => {
|
||||
const token = getCookie(e, "token");
|
||||
const [[userData]] = await database.query(
|
||||
"SELECT CONVERT(`users`.`id`, CHAR(32)) as `id`, `users`.`username` as `username`, `users`.`email` as `email`, `users`.`display_name` as `display_name` FROM `sessions` LEFT JOIN `users` ON `sessions`.`user` = `users`.`id` WHERE `sessions`.`id` = ?",
|
||||
[token],
|
||||
) as unknown as data<user>;
|
||||
|
||||
return userData;
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue