diff --git a/components/alerts.vue b/components/alerts.vue new file mode 100644 index 0000000..1301eab --- /dev/null +++ b/components/alerts.vue @@ -0,0 +1,63 @@ + + + + + + + + + diff --git a/components/entryEditor.vue b/components/entryEditor.vue new file mode 100644 index 0000000..49a0651 --- /dev/null +++ b/components/entryEditor.vue @@ -0,0 +1,51 @@ + + + + updateModel(i.key, v)" + /> + diff --git a/components/navigation/navigation.vue b/components/navigation/navigation.vue index 02ee1f2..c72c67d 100644 --- a/components/navigation/navigation.vue +++ b/components/navigation/navigation.vue @@ -13,18 +13,20 @@ const navOpen = ref(!mobile.value); Database Project - + - + + + diff --git a/components/orderView.vue b/components/orderView.vue new file mode 100644 index 0000000..aa540b7 --- /dev/null +++ b/components/orderView.vue @@ -0,0 +1,56 @@ + + + + + + + Works + + + + fulfilled + offer + price + + + + + {{ i.is_fulfilled }} + + {{ i.offer.name }} + + {{ i.price }} PLN + + + + + + Imported products + + + + Name + link + price + + + + + {{ i.name }} + {{ i.link }} + {{ i.price }} PLN + + + + + + diff --git a/components/pagedList.vue b/components/pagedList.vue index 952a2c7..24dc5ed 100644 --- a/components/pagedList.vue +++ b/components/pagedList.vue @@ -2,6 +2,7 @@ const props = defineProps<{ records: Array, recordKey: string, + recordValue?: string, variant?: "default" | "inset" | "accordion" | "popout", modelValue?: any, }>(); @@ -22,6 +23,7 @@ defineEmits<{ v-for="record in records" :key="record[recordKey]" :variant="props.variant ?? 'default'" + :value="recordValue !== undefined ? record[recordValue] : undefined" > diff --git a/components/pagedTable.vue b/components/pagedTable.vue index b0045f1..5c9df94 100644 --- a/components/pagedTable.vue +++ b/components/pagedTable.vue @@ -53,4 +53,8 @@ defineEmits<{ flex-direction: row; flex-wrap: nowrap; } + + tr:hover { + background-color: rgba(var(--v-theme-on-background), calc(var(--v-hover-opacity) * var(--v-theme-overlay-multiplier))); + } diff --git a/pages/client/[id].vue b/pages/client/[id].vue index 380683a..13f2504 100644 --- a/pages/client/[id].vue +++ b/pages/client/[id].vue @@ -1,9 +1,14 @@ + + + + Edit client + + + { formData[k] = v; }" + /> + + + + Submit + + + + - + - - - {{ client.email }} - - + + + {{ client.email }} + + + + + edit + + + Created {{ getCreationDate() }} - - {{ i }} - - {{ i }} + + {{ new Date(Number(new Snowflake(BigInt(((i.record) as orderSummary).id)).timestamp)).toLocaleDateString() }} + {{ ((i.record) as orderSummary).value }} PLN + + {{ ((i.record) as orderSummary).imported_products_count }} + products, + {{ ((i.record) as orderSummary).work_count }} + works + + + + + + diff --git a/pages/clients.vue b/pages/clients.vue index adf945c..365602e 100644 --- a/pages/clients.vue +++ b/pages/clients.vue @@ -1,12 +1,19 @@ - + - - owowowowowowowowo - - + + + Create client + + + { formData[k] = v; }" + /> + + + + Submit + + + + @@ -66,6 +146,11 @@ async function loadBefore() { There are {{ count?.count }} clients in the database. + + Create + diff --git a/pages/index.vue b/pages/index.vue index 3939812..4fba044 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -3,7 +3,8 @@ import Test from '~/components/test.vue'; - Hi mom! + Hi mom! + diff --git a/pages/login.vue b/pages/login.vue index df89aba..5761adf 100644 --- a/pages/login.vue +++ b/pages/login.vue @@ -124,6 +124,13 @@ updateUserInfo(); > Logout + + go to clients + diff --git a/server/api/clients/[id]/orders.get.ts b/server/api/clients/[id]/orders.get.ts new file mode 100644 index 0000000..bd9903b --- /dev/null +++ b/server/api/clients/[id]/orders.get.ts @@ -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; +}); diff --git a/server/api/login.post.ts b/server/api/login.post.ts index e993cab..b011aaf 100644 --- a/server/api/login.post.ts +++ b/server/api/login.post.ts @@ -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")) diff --git a/server/api/orders.get.ts b/server/api/orders.get.ts new file mode 100644 index 0000000..34f7816 --- /dev/null +++ b/server/api/orders.get.ts @@ -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( + "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 = []; + 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; + + const rvalue: Array | { client?: client }> = []; + + for (const i of orders) + rvalue.push({ ...i, client: clients.find(e => i.client === e.id) }); + return rvalue; +}); diff --git a/server/api/orders.post.ts b/server/api/orders.post.ts new file mode 100644 index 0000000..ed31ada --- /dev/null +++ b/server/api/orders.post.ts @@ -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, + work: Array, +}; + +export function checkIsWork( + value: any, + patch: Patch, +): value is Patch extends true ? Partial : work { + const errors = new Map(); + + 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( + value: any, + patch: Patch, +): value is Patch extends true ? Partial : importedProduct { + const errors = new Map(); + + 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( + value: any, + patch: Patch, +): value is Patch extends true ? Partial> : order { + const errors = new Map(); + + 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> = []; + 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); +}); diff --git a/server/api/orders/[id].delete.ts b/server/api/orders/[id].delete.ts new file mode 100644 index 0000000..0f660d0 --- /dev/null +++ b/server/api/orders/[id].delete.ts @@ -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; +}); diff --git a/server/api/orders/[id].get.ts b/server/api/orders/[id].get.ts new file mode 100644 index 0000000..9832f2f --- /dev/null +++ b/server/api/orders/[id].get.ts @@ -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; + + // @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 { + 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); +}); diff --git a/server/api/orders/[id].patch.ts b/server/api/orders/[id].patch.ts new file mode 100644 index 0000000..87aa718 --- /dev/null +++ b/server/api/orders/[id].patch.ts @@ -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]); +}); diff --git a/server/api/orders/[id]/imported_products.get.ts b/server/api/orders/[id]/imported_products.get.ts new file mode 100644 index 0000000..ad7d9e0 --- /dev/null +++ b/server/api/orders/[id]/imported_products.get.ts @@ -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; +}); diff --git a/server/api/orders/[id]/imported_products.post.ts b/server/api/orders/[id]/imported_products.post.ts new file mode 100644 index 0000000..f441432 --- /dev/null +++ b/server/api/orders/[id]/imported_products.post.ts @@ -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); +}); diff --git a/server/api/orders/[id]/work.get.ts b/server/api/orders/[id]/work.get.ts new file mode 100644 index 0000000..5399cb8 --- /dev/null +++ b/server/api/orders/[id]/work.get.ts @@ -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; +}); diff --git a/server/api/orders/[id]/work.post.ts b/server/api/orders/[id]/work.post.ts new file mode 100644 index 0000000..98e04bd --- /dev/null +++ b/server/api/orders/[id]/work.post.ts @@ -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); +}); diff --git a/server/api/orders/[id]/work/[idWork].delete.ts b/server/api/orders/[id]/work/[idWork].delete.ts new file mode 100644 index 0000000..d95f1c3 --- /dev/null +++ b/server/api/orders/[id]/work/[idWork].delete.ts @@ -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; +}); diff --git a/server/api/orders/[id]/work/[idWork].get.ts b/server/api/orders/[id]/work/[idWork].get.ts new file mode 100644 index 0000000..5a8c06c --- /dev/null +++ b/server/api/orders/[id]/work/[idWork].get.ts @@ -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); +}); diff --git a/server/utils/baaPagination.ts b/server/utils/baaPagination.ts index 4155145..96e4853 100644 --- a/server/utils/baaPagination.ts +++ b/server/utils/baaPagination.ts @@ -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 = { diff --git a/server/utils/database.ts b/server/utils/database.ts index 7510f9e..d956d52 100644 --- a/server/utils/database.ts +++ b/server/utils/database.ts @@ -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 }; +database.new = () => { return mysql.createConnection(connectionOptions); }; export type data = [T[], mysql.FieldPacket[]]; diff --git a/server/utils/getRequestingUser.ts b/server/utils/getRequestingUser.ts new file mode 100644 index 0000000..45a0037 --- /dev/null +++ b/server/utils/getRequestingUser.ts @@ -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; + + if (!user) throw createError("User not found"); + return user; +} diff --git a/server/utils/validation.ts b/server/utils/validation.ts new file mode 100644 index 0000000..300a55d --- /dev/null +++ b/server/utils/validation.ts @@ -0,0 +1,31 @@ +/* global createError */ + +export function createValidationError(errors: Map) { + 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, 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; + } +} diff --git a/server/utils/snowflake.ts b/utils/snowflake.ts similarity index 86% rename from server/utils/snowflake.ts rename to utils/snowflake.ts index 829884c..b91bddc 100644 --- a/server/utils/snowflake.ts +++ b/utils/snowflake.ts @@ -31,7 +31,11 @@ export default class Snowflake { this.state = state + (value << 0n); } - constructor() { + constructor(value?: bigint) { + if (value) { + this.state = BigInt.asUintN(64, value); + return; + } this.set_timestamp(Date.now()); this.set_processid(1); this.set_increment(Snowflake.increment()); @@ -40,4 +44,8 @@ export default class Snowflake { public toString() { return this.state.toString(); } + + public get timestamp() { + return BigInt.asUintN(64 - 22, this.state >> 22n); + } } diff --git a/utils/types/database.ts b/utils/types/database.ts index 5978975..28a4ddc 100644 --- a/utils/types/database.ts +++ b/utils/types/database.ts @@ -1,68 +1,99 @@ export interface client { - id: string, - name: string | null, - address: string | null, - phone: string | null, - email: string | null, + id: string; + name: string | null; + address: string | null; + phone: string | null; + email: string | null; } export interface user { - id: string, - username: string, - email: string, - display_name?: string, + id: string; + username: string; + email: string; + display_name?: string; } export interface session { - id: string, - user: string, - expiry_date: string, + id: string; + user: string; + expiry_date: string; } export interface imported_product { - id: string, + id: string; // order: string, - name?: string, - link: string, - price_imported: string, - price: string, + name?: string; + link: string; + price_imported: string; + price: string; } export interface offer { - id: string, - name: string, - description?: string, - recommended_price?: string, + id: string; + name: string; + description?: string; + recommended_price?: string; } export interface work { - id: string, + id: string; // order: string, - offer: string|offer, - price: string, - notes: string, - is_fulfilled: boolean, + offer: string | offer; + price: string; + notes: string; + is_fulfilled: boolean; } export interface order { - id: string, - client: client|string, - user: user|string, - is_draft: boolean, - imported_products: imported_product[], - work: work[], + imported_products: Array<{ + id: string; + name: string | null; + link: string; + price: string; + price_imported: string; + }>; + work: { + id: string; + offer: offer; + price: number; + notes: string | null; + is_fulfilled: 0 | 1; + }[]; + id: string; + client: string; + user: string; + is_draft: 0 | 1; + value: number; +} + +export interface orderSummary { + id: string; + client: string; + user: string; + is_draft: 0 | 1; + value: string; + imported_products_count: number; + work_count: number; } export interface work_template { - id: string, + id: string; // order_template: string, - offer: string|offer, - price: string, - notes?: string, + offer: string | offer; + price: string; + notes?: string; } export interface order_template { - id: string, - name: string, - description?: string, + id: string; + name: string; + description?: string; } + +// 1 is true, 0 is false +export type Dboolean = + | boolean + | 0 // false + | 1; // true + +export type Dnumber = number | `${number}`;