update cuz presentation

This commit is contained in:
Wroclaw 2023-05-24 09:40:45 +02:00
parent 7a9e451739
commit 4e67cc4e19
29 changed files with 1065 additions and 88 deletions

View file

@ -0,0 +1,9 @@
/* global defineEventHandler */
import { baaWrapper } from "~/server/api/orders.get";
export default defineEventHandler(async (e) => {
const baa = await baaWrapper.RESTget(e, 50, 200, "`client` = ?", [e.context.params?.id]);
console.log(baa);
return baa;
});

View file

@ -3,8 +3,8 @@ 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";
import Snowflake from "~/utils/snowflake";
export default defineEventHandler(async (e) => {
if (getCookie(e, "token"))

37
server/api/orders.get.ts Normal file
View file

@ -0,0 +1,37 @@
/* global defineEventHandler */
import BaaPagination from "../utils/baaPagination";
import { data, database } from "../utils/database";
import { client, orderSummary } from "~/utils/types/database";
export const baaWrapper = new BaaPagination<orderSummary, "id">(
"orderSummaries",
"id",
"*, CONVERT(`client`, CHAR) AS `client`, CONVERT(`user`, CHAR) as `user`",
);
export default defineEventHandler(async (e) => {
const orders = await baaWrapper.RESTget(e, 50, 200);
const uniqueClients: Array<string> = [];
for (const i of orders) {
if (!uniqueClients.includes(i.client))
uniqueClients.push(database.escape(i.client));
}
const [clients] = await database.query(
["SELECT",
"*,",
"CONVERT(`id`, CHAR) AS `id`",
"FROM `clients`",
"WHERE `id` IN",
`(${uniqueClients.join(', ')})`,
].join(" "),
) as data<client>;
const rvalue: Array<Omit<typeof orders, "client"> | { client?: client }> = [];
for (const i of orders)
rvalue.push({ ...i, client: clients.find(e => i.client === e.id) });
return rvalue;
});

172
server/api/orders.post.ts Normal file
View file

@ -0,0 +1,172 @@
/* global defineEventHandler, createError, readBody, setResponseStatus */
import { createValidationError, handleRecursedValidationError } from "../utils/validation";
import { database as db } from "../utils/database";
import getRequestingUser from "../utils/getRequestingUser";
import { getOrder } from "./orders/[id].get";
import Snowflake from "~/utils/snowflake";
type importedProduct = {
name: string | null,
link: string,
price_imported: number,
price: number,
}
type work = {
offer: string,
price: number,
notes: string | null,
is_fulfilled: boolean | 0 | 1,
}
type order = {
client: string,
// user: string,
is_draft: boolean | 0 | 1,
imported_products: Array<importedProduct>,
work: Array<work>,
};
export function checkIsWork<Patch extends boolean = boolean>(
value: any,
patch: Patch,
): value is Patch extends true ? Partial<work> : work {
const errors = new Map<string, string>();
if (typeof value !== "object") {
throw createError({
message: "Invalid body",
statusCode: 400,
});
}
if (!(typeof value.offer === "string" || (patch && value.offer === undefined))) errors.set("offer", "is not string");
if (!(typeof value.price === "number" || (patch && value.price === undefined))) errors.set("price", "is not price");
if (!(typeof value.notes === "string" || value.notes === null || (patch && value.notes === undefined))) errors.set("notes", "is not string or null");
if (!(typeof value.is_fulfilled === "boolean" || value.is_fulfilled === 0 || value.is_fulfilled === 1 || (patch && value.is_fulfilled === undefined))) errors.set("is_fulfilled", "is not boolean");
if (errors.size !== 0) throw createValidationError(errors);
return true;
}
export function checkIsImportedProduct<Patch extends boolean = boolean>(
value: any,
patch: Patch,
): value is Patch extends true ? Partial<importedProduct> : importedProduct {
const errors = new Map<string, string>();
if (typeof value !== "object") {
throw createError({
message: "Invalid body",
statusCode: 400,
});
}
if (!(typeof value.name === "string" || value.name === null || (patch && value.name === undefined))) errors.set("name", "is not string or null");
if (!(typeof value.link === "string" || (patch && value.name === undefined))) errors.set("link", "is not string");
if (!(typeof value.price_imported === "number" || (patch && value.name === undefined))) errors.set("price_imported", "is not number");
if (!(typeof value.price || (patch && value.price === undefined))) errors.set("price", "is not number");
if (errors.size !== 0) throw createValidationError(errors);
return true;
}
export function checkIsOrder<Patch extends boolean = boolean>(
value: any,
patch: Patch,
): value is Patch extends true ? Partial<Pick<order, "client" | "is_draft">> : order {
const errors = new Map<string, string>();
if (typeof value !== "object") {
throw createError({
message: "Invalid body",
statusCode: 400,
});
}
if (!(typeof value.client === "string" || (patch && value.client === undefined))) errors.set("client", "is not string");
if (!(typeof value.is_draft === "boolean" || value.is_draft === 0 || value.is_draft === 1 || (patch && value.is_draft === undefined))) errors.set("is_draft", "is not boolean");
if (!(value.imported_products instanceof Array)) errors.set("imported_products", "is not array");
else if (patch && value.imported_products !== undefined) errors.set("imported_products", "cannot patch from order");
if (!(value.work instanceof Array)) errors.set("work", "is not array");
else if (patch && value.work !== undefined) errors.set("work", "cannot patch from order");
if (!patch) {
const importedProducts = value.imported_products;
if (importedProducts instanceof Array) {
for (const i in importedProducts) {
try {
checkIsImportedProduct(importedProducts[i], patch);
} catch (e) {
handleRecursedValidationError(e, errors, `imported_products[${i}]`);
}
}
}
const work = value.work;
if (work instanceof Array) {
for (const i in work) {
try {
checkIsWork(work[i], patch);
} catch (e) {
handleRecursedValidationError(e, errors, `work[${i}]`);
}
}
}
}
if (errors.size !== 0) throw createValidationError(errors);
return true;
}
export default defineEventHandler(async (e) => {
const body = await readBody(e);
const id = new Snowflake().toString();
const user = await getRequestingUser(e);
if (!checkIsOrder(body, false)) throw createError({ message: "Invalid body", statusCode: 400 });
const database = await db.new();
await database.beginTransaction();
await database.query(
["INSERT INTO",
"`orders`",
"VALUES",
"(?, ?, ?, ?)",
].join(" "),
[id, body.client, user.id, body.is_draft],
);
const promises: Array<Promise<any>> = [];
for (const i of body.imported_products) {
promises.push(database.query(
["INSERT INTO",
"`imported_products`",
"VALUES",
"(?, ?, ?, ?, ?, ?)",
].join(" "),
[new Snowflake().toString(), id, i.name, i.link, i.price_imported, i.price],
));
}
for (const i of body.work) {
promises.push(database.query(
["INSERT INTO",
"`work`",
"VALUES",
"(?, ?, ?, ?, ?, ?)",
].join(" "),
[new Snowflake().toString(), id, i.offer, i.price, i.notes, i.is_fulfilled],
));
}
await Promise.all(promises);
await database.commit();
setResponseStatus(e, 201);
return getOrder(id);
});

View 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;
const [result] = await database.query(
"DELETE FROM `orders` WHERE `id` = ?",
[id],
) as unknown as [ResultSetHeader];
if (result.affectedRows === 0) throw createError({ statusCode: 404 });
return null;
});

View file

@ -0,0 +1,108 @@
/* global defineEventHandler, createError */
import { offer as offerType, order } from "~/utils/types/database";
import { database, data } from "~/server/utils/database";
export async function orderExists(id: string) {
const [[exists]] = await database.query(
"SELECT EXISTS(*) AS `exists` FROM `orders` WHERE `id` = ?",
[id],
) as data<{exists: 0 | 1}>;
return exists.exists === 1;
}
export async function getImportedProducts(id: string) {
const [importedProducts] = await database.query(
["SELECT",
"CONVERT(`id`, CHAR) AS `id`,",
"`name`,",
"`link`,",
"`price`,",
"`price_imported`",
"FROM `imported_products`",
"WHERE `order` = ?",
].join(" "),
[id],
) as data<{
id: string,
name: string | null,
link: string,
price: string,
price_imported: string
}>;
return importedProducts;
}
export async function getWork(id: string) {
const [work] = await database.query(
["SELECT",
"CONVERT(`id`, CHAR) AS `id`,",
"CONVERT(`offer`, CHAR) AS `offer`,",
"`price`,",
"`notes`,",
"`is_fulfilled`",
"FROM `work`",
"WHERE `order` = ?",
].join(" "),
[id],
) as data<{
id: string,
offer: offerType,
price: number,
notes: string | null,
is_fulfilled: 0 | 1,
}>;
const [offer] = await database.query(
["SELECT",
"CONVERT(`offer`.`id`, CHAR) AS `id`,",
"`offer`.`name`,",
"`offer`.`description`,",
"`offer`.`recommended_price`",
"FROM",
"`work`",
"LEFT JOIN `offer` ON `work`.`offer` = `offer`.`id`",
"WHERE `work`.`order` = ?",
].join(" "),
[id],
) as data<offerType>;
// @ts-ignore i.offer is string, but it needs to be an offer object
for (const i of work) i.offer = offer.find(e => e.id === i.offer) as offerType;
return work;
}
export async function getOrder(id: string): Promise<order> {
const [[order]] = await database.query(
["SELECT",
"CONVERT(`id`, CHAR) AS `id`,",
"CONVERT(`client`, CHAR) AS `client`,",
"CONVERT(`user`, CHAR) AS `user`, ",
"`is_draft`,",
"`value`",
"FROM `orderSummaries`",
"WHERE `id` = ?",
].join(" "),
[id],
) as data<{
id: string,
client: string,
user: string,
is_draft: 0 | 1,
value: number,
}>;
if (!order) throw createError({ statusCode: 404 });
const importedProducts = await getImportedProducts(id);
const work = await getWork(id);
return { ...order, imported_products: importedProducts, work };
}
export default defineEventHandler((e) => {
const key = e.context.params?.id;
return getOrder(key as string);
});

View file

@ -0,0 +1,17 @@
/* global defineEventHandler, readBody, createError */
import { checkIsOrder } from "../orders.post";
import { database as db } from "~/server/utils/database";
export default defineEventHandler(async (e) => {
const body = await readBody(e);
const id = e.context.params?.id;
if (!checkIsOrder(e, true)) throw createError({ message: "Invalid body", statusCode: 400 });
const database = await db.new();
await database.beginTransaction();
for (const [k, v] of Object.entries(body))
database.query(`UPDATE TABLE \`orders\` SET \`${k}\` = ? WHERE \`id\` = ?`, [v, id]);
});

View file

@ -0,0 +1,12 @@
/* global defineEventHandler, createError */
import { orderExists, getImportedProducts } from "../[id].get";
export default defineEventHandler(async (e) => {
const id = e.context.params?.id as string;
if (!orderExists(id)) throw createError({ statusCode: 404 });
const importedProducts = await getImportedProducts(id);
return importedProducts;
});

View file

@ -0,0 +1,27 @@
/* global defineEventHandler, readBody, createError, setResponseStatus */
import { checkIsImportedProduct } from "../../orders.post";
import { getImportedProducts, orderExists } from "../[id].get";
import Snowflake from "~/utils/snowflake";
import { database } from "~/server/utils/database";
export default defineEventHandler(async (e) => {
const body = await readBody(e);
const idOrder = e.context.params?.id as string;
const idImportedProducts = new Snowflake().toString();
if (!orderExists(idOrder)) throw createError({ statusCode: 404 });
if (!checkIsImportedProduct(body, false)) throw createError({ message: "Invalid body", statusCode: 400 });
await database.query(
["INSERT INTO",
"`imported_products`",
"VALUES",
"(?, ?, ?, ?, ?, ?)",
].join(" "),
[idImportedProducts, idOrder, body.name, body.link, body.price_imported, body.price],
);
setResponseStatus(e, 201);
return getImportedProducts(idOrder);
});

View file

@ -0,0 +1,12 @@
/* global defineEventHandler, createError */
import { orderExists, getWork } from "../[id].get";
export default defineEventHandler(async (e) => {
const id = e.context.params?.id as string;
if (!orderExists(id)) throw createError({ statusCode: 404 });
const work = await getWork(id);
return work;
});

View file

@ -0,0 +1,28 @@
/* global defineEventHandler, readBody, createError, setResponseStatus */
import { checkIsWork } from "../../orders.post";
import { getWork, orderExists } from "../[id].get";
import Snowflake from "~/utils/snowflake";
import { database } from "~/server/utils/database";
export default defineEventHandler(async (e) => {
const body = await readBody(e);
const idOrder = e.context.params?.id as string;
const idWork = new Snowflake().toString();
if (!orderExists(idOrder)) throw createError({ statusCode: 404 });
if (!checkIsWork(body, false)) throw createError({ message: "Invalid body", statusCode: 400 });
await database.query(
["INSERT INTO",
"`work`",
"VALUES",
"(?, ?, ?, ?, ?, ?)",
].join(" "),
[idWork, idOrder, body.offer, body.price, body.notes, body.is_fulfilled],
);
setResponseStatus(e, 201);
return getWork(idWork);
});

View file

@ -0,0 +1,20 @@
/* global defineEventHandler, createError */
import { ResultSetHeader } from "mysql2";
import { orderExists } from "../../[id].get";
import { database } from "~/server/utils/database";
export default defineEventHandler(async (e) => {
const idOrder = e.context.params?.id as string;
const idWork = e.context.params?.idWork as string;
if (!orderExists(idOrder)) throw createError({ statusCode: 404 });
const [response] = await database.query(
"DELETE FROM `work` WHERE `id` = ?",
[idWork],
) as unknown as [ResultSetHeader];
if (response.affectedRows === 0) throw createError({ statusCode: 404 });
return null;
});

View file

@ -0,0 +1,11 @@
/* global defineEventHandler, createError */
import { orderExists, getWork } from "../../[id].get";
export default defineEventHandler((e) => {
const idOrder = e.context.params?.id as string;
const idWork = e.context.params?.idWork as string;
if (!orderExists(idOrder)) throw createError({ statusCode: 404 });
return getWork(idWork);
});

View file

@ -5,7 +5,7 @@ import { ResultSetHeader } from "mysql2/promise";
import { data, database } from "./database";
import { isString } from "./isString";
import Snowflake from "./snowflake";
import Snowflake from "~/utils/snowflake";
import { client } from "~/utils/types/database";
type queryType = {

View file

@ -1,11 +1,17 @@
import mysql from "mysql2/promise";
import mysql, { Connection } from "mysql2/promise";
export const database = await mysql.createConnection({
const connectionOptions: mysql.ConnectionOptions = {
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_SCHEMA,
});
decimalNumbers: true,
supportBigNumbers: true,
};
export const database =
await mysql.createConnection(connectionOptions) as Connection & { new: () => Promise<Connection> };
database.new = () => { return mysql.createConnection(connectionOptions); };
export type data<T> = [T[], mysql.FieldPacket[]];

View file

@ -0,0 +1,25 @@
/* global getCookie, createError */
import { H3Event } from "h3";
import { database, data } from "./database";
import { user } from "~/utils/types/database";
export default async function getRequestingUser(e: H3Event) {
const cookie = getCookie(e, "token");
const [[user]] = await database.query(
["SELECT",
"CONVERT(`users`.`id`, CHAR) as `id`,",
"`users`.`username`,",
"`users`.`email`,",
"`users`.`display_name`",
"FROM",
"`sessions`",
"LEFT JOIN `users` ON `sessions`.`user` = `users`.`id`",
"WHERE `sessions`.`id` = ?",
].join(" "),
[cookie],
) as data<user>;
if (!user) throw createError("User not found");
return user;
}

View file

@ -1,43 +0,0 @@
export default class Snowflake {
static current_increment = 0n;
public static increment() {
this.current_increment = BigInt.asUintN(12, this.current_increment + 1n);
return this.current_increment;
}
state = 0n;
public set_timestamp(value: number | bigint) {
value = BigInt.asUintN(64 - 22, BigInt(value));
const state = BigInt.asUintN(22, this.state);
this.state = state + (value << 22n);
}
public set_machineid(value: number | bigint) {
value = BigInt.asUintN(12 - 17, BigInt(value));
const state = BigInt.asUintN(17, this.state) + (this.state >> 22n) << 22n;
this.state = state + (value << 12n);
}
public set_processid(value: number | bigint) {
value = BigInt.asUintN(17 - 12, BigInt(value));
const state = BigInt.asUintN(12, this.state) + (this.state >> 17n) << 17n;
this.state = state + (value << 12n);
}
public set_increment(value: number | bigint) {
value = BigInt.asUintN(12 - 0, BigInt(value));
const state = (this.state >> 12n) << 12n;
this.state = state + (value << 0n);
}
constructor() {
this.set_timestamp(Date.now());
this.set_processid(1);
this.set_increment(Snowflake.increment());
}
public toString() {
return this.state.toString();
}
}

View file

@ -0,0 +1,31 @@
/* global createError */
export function createValidationError(errors: Map<string, string>) {
let message = "Invalid parameters: ";
for (const i in errors)
message += i + ", ";
message = message.slice(0, -2);
return createError({
statusCode: 400,
message,
data: {
errors: Object.fromEntries(errors),
},
});
}
export function handleRecursedValidationError(e: unknown, errors: Map<string, string>, element: string) {
if (typeof e !== "object") throw e;
if (!e) throw e;
if (!(e as any).data || !(e as any).message) throw e;
const upstreamErrors = (e as any).data.errors as any;
const upstreamMessage = (e as any).message;
if (upstreamErrors) {
for (const j in upstreamErrors)
errors.set(`${element}.${j}`, String(upstreamErrors[j]));
} else if (upstreamMessage) {
errors.set(`${element}`, String(upstreamMessage));
} else {
throw e;
}
}