Compare commits

..

No commits in common. "d5d2f2e889759fa964d3b6151f021a164c9ec91b" and "267a83d4849a4659496c30fa762aac4b9b45e4fe" have entirely different histories.

51 changed files with 4123 additions and 6151 deletions

View file

@ -1,32 +0,0 @@
name: Build dev
on:
push:
jobs:
build:
runs-on: docker
steps:
# Setup
- uses: https://code.forgejo.org/actions/setup-node@v3
with:
node-version: 18
- uses: https://code.forgejo.org/actions/checkout@v3
- name: Install dependencies
run: npm install -D
# Compile
- name: Build project
if: success()
run: npx nuxi build
# Upload
- name: Package to zip
if: success()
run: tar --create --file build-${{ github.run_number }}.tar .output
- name: Upload build
if: success()
uses: https://code.forgejo.org/actions/upload-artifact@v3
with:
name: Build-${{ github.run_number }}
path: build-${{ github.run_number }}.tar

View file

@ -14,6 +14,10 @@ Project uses Oracle MySQL as a database store. It uses environment variables for
| Environment variable | Description |
|----------------------|-------------------|
| `DB_URL` | Database url, see [this](https://www.prisma.io/docs/concepts/database-connectors/mysql#connection-url) |
| `DB_HOST` | Database host |
| `DB_PORT` | Database port |
| `DB_USER` | Database user |
| `DB_PASSWORD` | Database password |
| `DB_SCHEMA` | Database schema |
After setting variables, you can run the project using `npx nuxi dev` or `npx nuxi preview`.

View file

@ -1,10 +1,8 @@
<script setup lang="ts">
import type { useFetch } from '#imports';
type Order = Awaited<ReturnType<typeof useFetch<void, any, "/api/orders/:id", "get">>>["data"]["value"];
import { order as orderType } from '~/utils/types/database';
const props = defineProps<{
order?: Order | undefined
order?: orderType
}>();
</script>
@ -26,7 +24,7 @@ const props = defineProps<{
</thead>
<tbody>
<tr v-for="i in props.order.work" :key="i.id">
<td>{{ i.fulfilled }}</td>
<td>{{ i.is_fulfilled }}</td>
<td>
{{ i.offer.name }}
</td>

View file

@ -1,19 +1,10 @@
import { defineNuxtConfig } from "nuxt/config";
import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify';
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
pages: true,
build: {
transpile: ["vuetify"],
},
modules: [
(_options, nuxt) => {
nuxt.hooks.hook('vite:extendConfig', (config) => {
config.plugins?.push(vuetify({ autoImport: true }));
});
},
],
css: [
"vuetify/lib/styles/main.sass",
"@mdi/font/css/materialdesignicons.min.css",
@ -27,11 +18,7 @@ export default defineNuxtConfig({
},
},
vite: {
vue: {
template: {
transformAssetUrls,
},
},
// devBundler: "legacy"
},
imports: {
autoImport: false,

8221
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -13,17 +13,15 @@
"devDependencies": {
"@mdi/font": "^7.2.96",
"@nuxtjs/eslint-config-typescript": "^12.0.0",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"eslint": "^8.39.0",
"nuxt": "3.8.1",
"prisma": "5.5.2",
"nuxt": "3.4.0",
"sass": "^1.62.0",
"vite-plugin-vuetify": "^1.0.2",
"vuetify": "^3.1.15"
},
"dependencies": {
"@prisma/client": "5.5.2",
"mysql2": "^3.2.3"
}
}

View file

@ -1,33 +1,29 @@
<script setup lang="ts">
/* global $fetch */
import { useRoute, useFetch, createError } from "nuxt/app";
import { ref, type Ref } from "vue";
import { ref, Ref } from "vue";
import { VBtn, VForm } from "vuetify/components";
import PagedList from "~/components/pagedList.vue";
import Snowflake from "~/utils/snowflake";
import { client as clientType, order, orderSummary } from "~/utils/types/database";
import OrderView from "~/components/orderView.vue";
import EntryEditor, { type fieldDefinition } from "~/components/entryEditor.vue";
import EntryEditor, { fieldDefinition } from "~/components/entryEditor.vue";
const route = useRoute();
const id = route.params.id;
const clientRequest = await useFetch(`/api/clients/${id}` as "/api/clients/:id");
const clientRequest = await useFetch(`/api/clients/${id}`);
if (clientRequest.error.value) throw createError(clientRequest.error.value?.data ?? "");
type Client = NonNullable<typeof clientRequest.data.value>;
const client = clientRequest.data as Ref<Client>;
const client = clientRequest.data as Ref<clientType>;
const clientOrdersRequest = await useFetch(`/api/clients/${id}/orders` as "/api/clients/:id/orders");
const clientOrdersRequest = await useFetch(`/api/clients/${id}/orders`);
if (clientOrdersRequest.error.value) throw createError(clientOrdersRequest.error.value?.data ?? "");
type OrderSummary = NonNullable<typeof clientOrdersRequest.data.value>;
const clientOrders = clientOrdersRequest.data as Ref<OrderSummary>;
const clientOrders = clientOrdersRequest.data as Ref<Array<orderSummary>>;
type Order = Awaited<ReturnType<typeof useFetch<void, any, "/api/orders/:id", "get">>>["data"]["value"];
// cache
const orders = ref<Map<string, {
loading: boolean,
value?: Order
value?: order
}>>(new Map());
for (const i of clientOrders.value)
@ -40,7 +36,7 @@ async function loadOrder(id: string) {
// @ts-expect-error
entry.value = await $fetch(`/api/orders/${id}` as "/api/order/:id", {
method: "GET",
}) as Order;
});
entry.loading = false;
}
@ -188,24 +184,24 @@ function getCreationDate() {
>
<template #title="i">
<VRow>
<VCol>{{ new Date(Number(new Snowflake(BigInt((i.record.id))).timestamp)).toLocaleDateString() }}</VCol>
<VCol>{{ i.record.value }} PLN</VCol>
<VCol>{{ new Date(Number(new Snowflake(BigInt(((i.record) as orderSummary).id)).timestamp)).toLocaleDateString() }}</VCol>
<VCol>{{ ((i.record) as orderSummary).value }} PLN</VCol>
<VCol>
{{ i.record.imported_products_count }}
{{ ((i.record) as orderSummary).imported_products_count }}
products,
{{ i.record.work_count }}
{{ ((i.record) as orderSummary).work_count }}
works
</VCol>
</VRow>
</template>
<template #text="i">
<VProgressLinear
:height="orders.get(i.record.id)?.loading ?? true ? undefined : 0"
:height="orders.get((i.record as orderSummary).id)?.loading ?? true ? undefined : 0"
absolute
:progress="orders.get(i.record.id)?.loading ?? true"
:indeterminate="orders.get(i.record.id)?.loading ?? true"
:progress="orders.get((i.record as orderSummary).id)?.loading ?? true"
:indeterminate="orders.get((i.record as orderSummary).id)?.loading ?? true"
/>
<OrderView :order="orders.get(i.record.id)?.value" />
<OrderView :order="(orders.get((i.record as orderSummary).id)?.value as order | undefined)" />
</template>
</PagedList>
</VCol>

View file

@ -1,14 +1,14 @@
<script setup lang="ts">
/* global $fetch */
import { type NuxtError } from "nuxt/app";
import { ref, type Ref, reactive } from "vue";
import { useFetch, createError, navigateTo, NuxtError, useRoute } from "nuxt/app";
import { ref, Ref, reactive } from "vue";
import { VBtn } from "vuetify/components";
import { definePageMeta } from "~/.nuxt/imports";
import { client as clientType } from "~/utils/types/database";
import pagedTable from "~/components/pagedTable.vue";
import Alerts, { type AlertData } from "~/components/alerts.vue";
import { type fieldDefinition } from "~/components/entryEditor.vue";
import { useFetch, createError, navigateTo, useRoute, definePageMeta } from "#imports";
import Alerts, { AlertData } from "~/components/alerts.vue";
import { fieldDefinition } from "~/components/entryEditor.vue";
definePageMeta({ middleware: ["auth"] });
const route = useRoute();
@ -32,7 +32,7 @@ async function rowClicked(client: string, edit = false) {
async function rowDelete(client: string) {
try {
await $fetch(`/api/clients/${client}` as "api/clients/:id", {
await $fetch<clientType>(`/api/clients/${client}`, {
method: "DELETE",
});
clients.value = clients.value.filter(e => e.id !== client);

View file

@ -1,11 +1,11 @@
<script setup lang="ts">
/* global $fetch */
import { ref } from 'vue';
import { type NuxtError } from 'nuxt/app';
import { navigateTo, useFetch, definePageMeta } from '#imports';
import { NuxtError, navigateTo, useFetch } from 'nuxt/app';
import { definePageMeta } from '~/.nuxt/imports';
import EntryEditor, { type fieldDefinition } from '~/components/entryEditor.vue';
import Alerts, { type AlertData } from '~/components/alerts.vue';
import EntryEditor, { fieldDefinition } from '~/components/entryEditor.vue';
import Alerts, { AlertData } from '~/components/alerts.vue';
const editorFields: Array<fieldDefinition> = [
{ key: "username", type: "text", label: "Username", optional: false },

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { navigateTo } from '#imports';
import { navigateTo } from 'nuxt/app';
navigateTo("/clients");
</script>

View file

@ -2,8 +2,9 @@
/* global $fetch */
import { ref, watch } from "vue";
import { VForm } from "vuetify/components";
import { navigateTo, useCookie, useFetch, useRoute } from "nuxt/app";
import { cookieSettings } from "~/utils/cookieSettings";
import { definePageMeta, navigateTo, useCookie, useFetch, useRoute } from "#imports";
import { definePageMeta } from "~/.nuxt/imports";
const route = useRoute();

View file

@ -6,6 +6,8 @@ import { defineNuxtPlugin } from '#app';
export default defineNuxtPlugin((nuxtApp) => {
const vuetify = createVuetify({
ssr: true,
components,
directives,
theme: {
},
});

View file

@ -1,116 +0,0 @@
datasource db {
provider = "mysql"
url = env("DB_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id BigInt @id @unique @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt
username String @unique @db.VarChar(30)
email String @unique @db.VarChar(128)
password Bytes @db.Binary(64)
display_name String? @db.VarChar(30)
managedOrders Order[]
sessions Session[]
@@map("users")
}
model Session {
id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt
userId BigInt @map("user") @db.UnsignedBigInt
expiry_date DateTime? @default(dbgenerated("(now() + interval 30 day)")) @db.Timestamp(0)
user User @relation(fields: [userId], references: [id])
@@index([userId], map: "user_idx")
@@map("sessions")
}
model Client {
id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt
name String? @db.VarChar(128)
address String? @db.VarChar(128)
phone String? @db.VarChar(16)
email String? @db.VarChar(128)
orders Order[]
@@map("clients")
}
model Order {
id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt
clientId BigInt @db.UnsignedBigInt @map("client")
userId BigInt @db.UnsignedBigInt @map("user")
draft Boolean @default(true) @map("is_draft") @db.TinyInt
imported_products ImportedProduct[]
client Client @relation(fields: [clientId], references: [id])
user User @relation(fields: [userId], references: [id])
work Work[]
@@index([clientId])
@@index([userId])
@@map("orders")
}
model ImportedProduct {
id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt
orderId BigInt @db.UnsignedBigInt @map("order")
name String? @db.VarChar(128)
link String @db.VarChar(1024)
price_imported Decimal @default(0.00) @db.Decimal(10, 2)
price Decimal @default(0.00) @db.Decimal(10, 2)
order Order @relation(fields: [orderId], references: [id])
@@index([orderId])
@@map("imported_products")
}
model Offer {
id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt
name String @db.VarChar(45)
description String? @db.Text
recommended_price Decimal? @db.Decimal(10, 2)
work Work[]
@@map("offer")
}
model OrderTemplate {
id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt
name String @db.VarChar(45)
description String? @db.Text
work_templates WorkTemplate[]
@@map("order_templates")
}
model Work {
id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt
orderId BigInt @map("order") @db.UnsignedBigInt
offerId BigInt @map("offer") @db.UnsignedBigInt
price Decimal @db.Decimal(10, 2)
notes String? @db.Text
fulfilled Boolean @default(false) @map("is_fulfilled") @db.TinyInt
order Order @relation(fields: [orderId], references: [id])
offer Offer @relation(fields: [offerId], references: [id])
@@index([offerId], map: "offer_idx")
@@index([orderId], map: "order_idx")
@@map("work")
}
model WorkTemplate {
id BigInt @id @default(dbgenerated("(((unix_timestamp() * 1000) * pow(2,22)) + floor((rand() * pow(2,12))))")) @db.UnsignedBigInt
orderTemplateId BigInt @map("order_template") @db.UnsignedBigInt
offerId BigInt @map("offer") @db.UnsignedBigInt
price Decimal @default(0.00) @db.Decimal(10, 2)
notes String? @db.Text
orderTemplate OrderTemplate @relation(fields: [orderTemplateId], references: [id])
@@index([offerId])
@@index([orderTemplateId])
@@map("work_templates")
}

Binary file not shown.

167
schemaModel.sql Normal file
View file

@ -0,0 +1,167 @@
-- Server version 8.0.32
--
-- Table structure for table `users`
--
CREATE TABLE `users` (
`id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))),
`username` varchar(30) NOT NULL,
`email` varchar(128) NOT NULL,
`password` binary(64) NOT NULL,
`display_name` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idusers_UNIQUE` (`id`),
UNIQUE KEY `username_UNIQUE` (`username`),
UNIQUE KEY `email_UNIQUE` (`email`)
);
--
-- Table structure for table `clients`
--
CREATE TABLE `clients` (
`id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))),
`name` varchar(128) DEFAULT NULL,
`address` varchar(128) DEFAULT NULL,
`phone` varchar(16) DEFAULT NULL,
`email` varchar(128) DEFAULT NULL,
PRIMARY KEY (`id`)
);
--
-- Table structure for table `orders`
--
CREATE TABLE `orders` (
`id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))),
`client` bigint unsigned NOT NULL,
`user` bigint unsigned NOT NULL,
`is_draft` tinyint NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
KEY `user_idx` (`user`),
KEY `client_idx` (`client`),
CONSTRAINT `client` FOREIGN KEY (`client`) REFERENCES `clients` (`id`),
CONSTRAINT `user` FOREIGN KEY (`user`) REFERENCES `users` (`id`)
);
--
-- Table structure for table `imported_products`
--
CREATE TABLE `imported_products` (
`id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))),
`order` bigint unsigned NOT NULL,
`name` varchar(128) DEFAULT NULL,
`link` varchar(1024) NOT NULL,
`price_imported` decimal(10,2) NOT NULL DEFAULT '0.00',
`price` decimal(10,2) NOT NULL DEFAULT '0.00',
PRIMARY KEY (`id`),
KEY `order_idx` (`order`),
CONSTRAINT `order2` FOREIGN KEY (`order`) REFERENCES `orders` (`id`)
);
--
-- Table structure for table `offer`
--
CREATE TABLE `offer` (
`id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))),
`name` varchar(45) NOT NULL,
`description` text,
`recommended_price` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`id`)
);
--
-- Table structure for table `order_templates`
--
CREATE TABLE `order_templates` (
`id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))),
`name` varchar(45) NOT NULL,
`description` text,
PRIMARY KEY (`id`)
);
--
-- Table structure for table `sessions`
--
CREATE TABLE `sessions` (
`id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))),
`user` bigint unsigned NOT NULL,
`expiry_date` timestamp NULL DEFAULT ((now() + interval 30 day)),
PRIMARY KEY (`id`),
KEY `user_idx` (`user`),
CONSTRAINT `user_session` FOREIGN KEY (`user`) REFERENCES `users` (`id`)
);
--
-- Table structure for table `work`
--
CREATE TABLE `work` (
`id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))),
`order` bigint unsigned NOT NULL,
`offer` bigint unsigned NOT NULL,
`price` decimal(10,2) NOT NULL,
`notes` text,
`is_fulfilled` tinyint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `order_idx` (`order`),
KEY `offer_idx` (`offer`),
CONSTRAINT `offer` FOREIGN KEY (`offer`) REFERENCES `offer` (`id`),
CONSTRAINT `order` FOREIGN KEY (`order`) REFERENCES `orders` (`id`)
);
--
-- Table structure for table `work_templates`
--
CREATE TABLE `work_templates` (
`id` bigint unsigned NOT NULL DEFAULT (((unix_timestamp() * 1000 * pow(2,22)) + floor((rand() * pow(2,12))))),
`order_template` bigint unsigned NOT NULL,
`offer` bigint unsigned NOT NULL,
`price` decimal(10,2) NOT NULL DEFAULT '0.00',
`notes` text,
PRIMARY KEY (`id`),
KEY `order_template_idx` (`order_template`),
KEY `offer_idx` (`offer`),
CONSTRAINT `offer2` FOREIGN KEY (`offer`) REFERENCES `offer` (`id`),
CONSTRAINT `order_template` FOREIGN KEY (`order_template`) REFERENCES `order_templates` (`id`)
);
--
-- Final view structure for view `orderSummaries`
--
CREATE VIEW `orderSummaries` AS
SELECT
`id`,
`client`,
`user`,
`is_draft`,
(COALESCE(`imported_products`.`price`, 0) + COALESCE(`work`.`price`, 0)) AS `value`,
COALESCE(`imported_products`.`count`, 0) as `imported_products_count`,
COALESCE(`work`.`count`, 0) as `work_count`
FROM
`orders`
LEFT JOIN
(
SELECT
`order`,
SUM(`price`) as `price`,
COUNT(*) AS `count`
FROM `imported_products`
GROUP BY `order`
) as `imported_products` ON `orders`.`id` = `imported_products`.`order`
LEFT JOIN
(
SELECT
`order`,
SUM(`price`) AS `price`,
COUNT(*) AS `count`
FROM `work`
GROUP BY `work`.`order`
) AS `work` ON `work`.`order` = `orders`.`id`;

View file

@ -1,10 +1,10 @@
import { defineEventHandler } from "h3";
/* global defineEventHandler getQuery, createError */
import getPaginatedParameters from "../utils/baaPageParsing";
import { database } from "../utils/database";
import { prismaToWeb } from "~/server/utils/prismaToWeb";
import BaaPagination from "~/server/utils/baaPagination";
import { client } from "~/utils/types/database";
export const baaWrapper = new BaaPagination<client, "id">("clients", "id");
export default defineEventHandler((e) => {
const pageParameters = getPaginatedParameters(e, 50, 200);
return database.client.findPaginated(pageParameters, {}).then(prismaToWeb);
return baaWrapper.RESTget(e);
});

View file

@ -1,12 +1,7 @@
import { defineEventHandler, readBody, setResponseStatus } from "h3";
import { type Client } from "@prisma/client";
/* global defineEventHandler, createError, readBody */
import getRequestingUser from "../utils/getRequestingUser";
import { database } from "../utils/database";
import { prismaToWeb } from "~/server/utils/prismaToWeb";
import Snowflake from "~/utils/snowflake";
import { createError } from "#imports";
import { baaWrapper } from "./clients.get";
import { client } from "~/utils/types/database";
const clientKeys: Array<string> = [
"name",
@ -15,10 +10,10 @@ const clientKeys: Array<string> = [
"email",
];
export function checkIsClient<Patch extends boolean = boolean>(
export function checkIsClient(
value: any,
patch: Patch,
): value is Patch extends true ? Partial<Omit<Client, "id">> : Omit<Client, "id"> {
required = false,
): value is Partial<Omit<client, "id">> {
const errors = new Map<string, string>();
if (typeof value !== "object") {
@ -28,12 +23,12 @@ export function checkIsClient<Patch extends boolean = boolean>(
});
}
if (!(typeof value.name === "string" || value.name === null || (!patch && value.name === undefined))) errors.set("name", "is not string or null");
if (!(typeof value.address === "string" || value.address === null || (!patch && value.address === undefined))) errors.set("address", "is not string or null");
if (!(typeof value.phone === "string" || value.phone === null || (!patch && value.phone === undefined))) errors.set("phone", "is not string or null");
if (!(typeof value.email === "string" || value.email === null || (!patch && value.email === undefined))) errors.set("email", "is not string or null");
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 as Partial<Omit<Client, "id">>)
for (const i in value as Partial<Omit<client, "id">>)
if (!clientKeys.includes(i)) errors.set(i, `excessive property`);
if (errors.size !== 0) {
@ -53,20 +48,6 @@ export function checkIsClient<Patch extends boolean = boolean>(
return true;
}
export default defineEventHandler(async (e) => {
const body = await readBody(e);
const id = new Snowflake().state;
const user = await getRequestingUser(e);
if (!checkIsClient(body, false)) throw createError({ message: "Invalid body", statusCode: 400 });
const rvalue = await database.client.create({
data: {
...body,
id,
},
});
setResponseStatus(e, 201);
return prismaToWeb(rvalue);
export default defineEventHandler((e) => {
return baaWrapper.RESTpost(e, clientKeys as Array<keyof Omit<client, "id">>, (o): o is Omit<client, "id"> => checkIsClient(o, true));
});

View file

@ -1,22 +1,7 @@
import { defineEventHandler } from "h3";
/* global defineEventHandler */
import { database } from "~/server/utils/database";
import { baaWrapper } from "../clients.get";
import { createError } from "#imports";
export default defineEventHandler(async (e) => {
const id = e.context.params?.id as string;
try {
await database.client.delete({
where: {
id: BigInt(id),
},
});
} catch (e) {
// FIXME: should be 500 on errors other than "RecordNotFound"
throw createError({ statusCode: 404 });
}
return null;
export default defineEventHandler((e) => {
return baaWrapper.RESTdeleteRecord(e);
});

View file

@ -1,18 +1,7 @@
import { defineEventHandler } from "h3";
/* global defineEventHandler */
import { database } from "~/server/utils/database";
import { prismaToWeb } from "~/server/utils/prismaToWeb";
import { baaWrapper } from "../clients.get";
import { createError } from "#imports";
export default defineEventHandler(async (e) => {
const key = e.context.params?.id as string;
const rvalue = await database.client.findUnique({
where: {
id: BigInt(key),
},
});
if (!rvalue) throw createError({ statusCode: 404 });
return prismaToWeb(rvalue);
export default defineEventHandler((e) => {
return baaWrapper.RESTgetRecord(e);
});

View file

@ -1,23 +1,8 @@
import { defineEventHandler, readBody } from "h3";
/* global defineEventHandler */
import { checkIsClient } from "../clients.post";
import { database } from "~/server/utils/database";
import { prismaToWeb } from "~/server/utils/prismaToWeb";
import { baaWrapper } from "../clients.get";
import { createError } from "#imports";
export default defineEventHandler(async (e) => {
const body = await readBody(e);
const id = e.context.params?.id as string;
if (!checkIsClient(body, true)) throw createError({ message: "Invalid body", statusCode: 400 });
const rvalue = await database.client.update({
where: {
id: BigInt(id),
},
data: body,
});
return prismaToWeb(rvalue);
export default defineEventHandler((e) => {
return baaWrapper.RESTpatchRecord(e, checkIsClient);
});

View file

@ -1,16 +1,9 @@
import { defineEventHandler } from "h3";
/* global defineEventHandler */
import { getOrders } from "~/server/api/orders.get";
import { prismaToWeb } from "~/server/utils/prismaToWeb";
import getPaginatedParameters from "~/server/utils/baaPageParsing";
import { baaWrapper } from "~/server/api/orders.get";
export default defineEventHandler((e) => {
const pageParameters = getPaginatedParameters(e, 50, 200);
const clientId = e.context.params?.id as string;
return getOrders(
pageParameters,
{
clientId: BigInt(clientId),
},
).then(prismaToWeb);
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

@ -1,9 +1,7 @@
import { defineEventHandler } from "h3";
/* global defineEventHandler */
import { database } from "~/server/utils/database";
import { baaWrapper } from "../clients.get";
export default defineEventHandler(async (e) => {
return {
count: await database.client.count({}),
};
export default defineEventHandler((e) => {
return baaWrapper.RESTrecordCount(e);
});

View 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]);
});

View file

@ -1,4 +1,4 @@
import { defineEventHandler } from "h3";
/* global defineEventHandler */
export default defineEventHandler((event) => {
const message = event.node.req.read();

View file

@ -1,14 +1,13 @@
import { defineEventHandler } from "h3";
import { database } from "../utils/database";
/* global defineEventHandler */
import { data, database } from "../utils/database";
export async function isFirstRun() {
try {
const numberOfUsers = await database.user.count();
return numberOfUsers === 0;
} catch {
// We could fall here if the database is not initialized
return true;
}
const [tables] = await database.query({ sql: "SHOW TABLES", rowsAsArray: true }, []) as data<[string]>;
if (tables.length === 0) return true;
if (!tables.find(a => a[0] === "users")) return true;
const [[users]] = await database.query("SELECT COUNT(*) as `count` FROM `users`") as data<{count: number}>;
if (users.count === 0) return true;
return false;
}
export default defineEventHandler((e) => {

View file

@ -1,17 +1,16 @@
import { execSync } from "node:child_process";
import { defineEventHandler, setResponseStatus, readBody } from "h3";
/* global defineEventHandler, setResponseStatus, readBody, createError */
import { database } from "../utils/database";
import fs from "node:fs/promises";
import { database as db } from "../utils/database";
import { isFirstRun } from "./firstRun.get";
import { getPasswordHash } from "./login.post";
import Snowflake from "~/utils/snowflake";
import { createError } from "#imports";
export default defineEventHandler(async (e) => {
if (!isFirstRun()) {
setResponseStatus(e, 404);
return null;
return "";
}
const body = await readBody(e);
@ -23,14 +22,12 @@ export default defineEventHandler(async (e) => {
const email = body.email;
if (typeof email !== "string") throw createError({ message: "email is not string", statusCode: 400 });
execSync("npx prisma db push --force-reset");
database.user.create({
data: {
id: new Snowflake().state,
username,
email,
password: getPasswordHash(password),
},
});
return null;
const sql = await fs.readFile("./schemaModel.sql", "utf-8");
const database = await db.new({ multipleStatements: true });
await database.query(sql);
await database.execute(
"INSERT INTO `users` (`id`, `username`, `password`, `email`) VALUES (?, ?, ?, ?)",
[new Snowflake().toString(), username, getPasswordHash(password), email]);
return "";
});

View file

@ -1,4 +1,4 @@
import { defineEventHandler } from "h3";
/* global defineEventHandler */
export default defineEventHandler(() => {
return "Hi mom!";

View file

@ -1,13 +1,11 @@
/* global defineEventHandler, getCookie, setCookie, readBody, createError */
import crypto from "crypto";
import { defineEventHandler, getCookie, setCookie, readBody } from "h3";
import { database } from "../utils/database";
import { database, data } from "../utils/database";
import { isString } from "../utils/isString";
import { cookieSettings } from "../utils/rootUtils";
import Snowflake from "~/utils/snowflake";
import { createError } from "#imports";
export function getPasswordHash(password: string) {
return crypto.createHmac("sha512", "42")
.update(password)
@ -28,26 +26,19 @@ export default defineEventHandler(async (e) => {
const hashedPassword = getPasswordHash(password);
const account = await database.user.findUnique({
where: {
username: login,
password: hashedPassword,
},
select: {
id: true,
},
});
const [account] = await database.query(
"SELECT CONVERT(`id`, CHAR(32)) AS `id` from `users` WHERE `username` = ? AND `password` = ? LIMIT 1",
[login, hashedPassword],
)as unknown as data<{id: string}>;
if (account === null) throw createError({ statusCode: 400, message: "Invalid username or password." });
if (account.length === 0) throw createError({ statusCode: 400, message: "Invalid username or password." });
const sessionId = new Snowflake();
const sessionId = new Snowflake().toString();
await database.session.create({
data: {
id: sessionId.state,
userId: account.id,
},
});
setCookie(e, "token", sessionId.toString(), cookieSettings);
return { message: "Login successful", token: sessionId.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 };
});

View file

@ -1,11 +1,9 @@
import { defineEventHandler, getCookie, deleteCookie } from "h3";
/* global defineEventHandler, createError, getCookie, deleteCookie */
import { isAuthorised } from "../middleware/auth";
import { database } from "../utils/database";
import { cookieSettings } from "../utils/rootUtils";
import { createError } from "#imports";
export default defineEventHandler(async (e) => {
const token = getCookie(e, "token");
if (token === undefined) {
@ -23,10 +21,9 @@ export default defineEventHandler(async (e) => {
});
}
database.session.delete({
where: {
id: BigInt(token),
},
});
database.query(
"DELETE FROM `sessions` WHERE `id` = ?",
[token],
);
return { message: "Logged out" };
});

View file

@ -1,65 +1,37 @@
import { defineEventHandler } from "h3";
import { type Order, type Client, Prisma } from "@prisma/client";
/* global defineEventHandler */
import getPaginatedParameters, { type pageData } from "../utils/baaPageParsing";
import { database } from "../utils/database";
import { prismaToWeb } from "~/server/utils/prismaToWeb";
import BaaPagination from "../utils/baaPagination";
import { data, database } from "../utils/database";
import { client, orderSummary } from "~/utils/types/database";
type orderSummary = Omit<Order, "clientId"> & {
client: Client;
value: number;
imported_products_count: number;
work_count: number;
};
export const baaWrapper = new BaaPagination<orderSummary, "id">(
"orderSummaries",
"id",
"*, CONVERT(`client`, CHAR) AS `client`, CONVERT(`user`, CHAR) as `user`",
);
export async function getOrders(
pageParameters: pageData,
where?: Prisma.OrderWhereInput,
) {
const data = await database.order.findPaginated(
pageParameters,
{
select: {
id: true,
client: true,
userId: true,
draft: true,
imported_products: {
select: {
price: true,
},
},
work: {
select: {
price: true,
},
},
},
where,
},
);
export default defineEventHandler(async (e) => {
const orders = await baaWrapper.RESTget(e, 50, 200);
const rvalue = new Array<orderSummary>();
for (const i of data) {
const importedProductsPriceSum = i.imported_products.reduce((pv, cv) => pv + cv.price.toNumber(), 0);
const workPriceSum = i.work.reduce((pv, cv) => pv + cv.price.toNumber(), 0);
rvalue.push({
id: i.id,
client: i.client,
draft: i.draft,
imported_products_count: i.imported_products.length,
userId: i.userId,
value: importedProductsPriceSum + workPriceSum,
work_count: i.work.length,
});
const uniqueClients: Array<string> = [];
for (const i of orders) {
if (!uniqueClients.includes(i.client))
uniqueClients.push(database.escape(i.client));
}
return rvalue;
}
const [clients] = await database.query(
["SELECT",
"*,",
"CONVERT(`id`, CHAR) AS `id`",
"FROM `clients`",
"WHERE `id` IN",
`(${uniqueClients.join(', ')})`,
].join(" "),
) as data<client>;
export default defineEventHandler((e) => {
const pageParameters = getPaginatedParameters(e, 50, 200);
return getOrders(pageParameters, {}).then(prismaToWeb);
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;
});

View file

@ -1,44 +1,37 @@
import { defineEventHandler, readBody, setResponseStatus } from "h3";
import * as Prisma from "@prisma/client";
/* global defineEventHandler, createError, readBody, setResponseStatus */
import { createValidationError, handleRecursedValidationError } from "../utils/validation";
import { database } from "../utils/database";
import { database as db } from "../utils/database";
import getRequestingUser from "../utils/getRequestingUser";
import { getOrder } from "./orders/[id].get";
import { prismaToWeb } from "~/server/utils/prismaToWeb";
import Snowflake from "~/utils/snowflake";
import { createError } from "#imports";
type importedProduct<inOrder extends boolean = boolean> = {
orderId: inOrder extends true ? never : string,
type importedProduct = {
name: string | null,
link: string,
price_imported: number,
price: number,
}
type work<inOrder extends boolean = boolean> = {
orderId: inOrder extends true ? never : string,
offerId: string,
type work = {
offer: string,
price: number,
notes: string | null,
fulfilled: boolean,
is_fulfilled: boolean | 0 | 1,
}
type order = {
clientId: string,
// userId: string,
draft: boolean,
imported_products: Array<importedProduct<true>>,
work: Array<work<true>>,
client: string,
// user: string,
is_draft: boolean | 0 | 1,
imported_products: Array<importedProduct>,
work: Array<work>,
};
export function checkIsWork<Patch extends boolean = boolean, inOrder extends boolean = boolean>(
export function checkIsWork<Patch extends boolean = boolean>(
value: any,
patch: Patch,
needsOrderId: inOrder,
): value is Patch extends true ? Partial<work<inOrder>> : work<inOrder> {
): value is Patch extends true ? Partial<work> : work {
const errors = new Map<string, string>();
if (typeof value !== "object") {
@ -48,24 +41,19 @@ export function checkIsWork<Patch extends boolean = boolean, inOrder extends boo
});
}
if (!(typeof value.orderId === "string" || (patch && value.orderId === undefined) || !needsOrderId)) errors.set("orderId", "is not string");
if (!(typeof value.offerId === "string" || (patch && value.offerId === undefined))) errors.set("offerId", "is not string");
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" || (patch && value.is_fulfilled === undefined))) errors.set("is_fulfilled", "is not boolean");
// TODO: Excessive property checking
// Excessive properties should be checked and an error should be thrown if there is one
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, inOrder extends boolean = boolean>(
export function checkIsImportedProduct<Patch extends boolean = boolean>(
value: any,
patch: Patch,
needsOrderId: inOrder,
): value is Patch extends true ? Partial<importedProduct<inOrder>> : importedProduct<inOrder> {
): value is Patch extends true ? Partial<importedProduct> : importedProduct {
const errors = new Map<string, string>();
if (typeof value !== "object") {
@ -75,14 +63,10 @@ export function checkIsImportedProduct<Patch extends boolean = boolean, inOrder
});
}
if (!(typeof value.orderId === "string" || (patch && value.orderId === undefined) || !needsOrderId)) errors.set("orderId", "is not string");
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 === "number" || (patch && value.price === undefined))) errors.set("price", "is not number");
// TODO: Excessive property checking
// Excessive properties should be checked and an error should be thrown if there is one
if (!(typeof value.price || (patch && value.price === undefined))) errors.set("price", "is not number");
if (errors.size !== 0) throw createValidationError(errors);
@ -92,7 +76,7 @@ export function checkIsImportedProduct<Patch extends boolean = boolean, inOrder
export function checkIsOrder<Patch extends boolean = boolean>(
value: any,
patch: Patch,
): value is Patch extends true ? Partial<Omit<order, "imported_products" | "work">> : order {
): value is Patch extends true ? Partial<Pick<order, "client" | "is_draft">> : order {
const errors = new Map<string, string>();
if (typeof value !== "object") {
@ -102,22 +86,19 @@ export function checkIsOrder<Patch extends boolean = boolean>(
});
}
if (!(typeof value.clientId === "string" || (patch && value.clientId === undefined))) errors.set("clientId", "is not string");
if (!(typeof value.draft === "boolean" || (patch && value.is_draft === undefined))) errors.set("draft", "is not boolean");
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");
// TODO: Excessive property checking
// Excessive properties should be checked and an error should be thrown if there is one
if (!patch) {
const importedProducts = value.imported_products;
if (importedProducts instanceof Array) {
for (const i in importedProducts) {
try {
checkIsImportedProduct(importedProducts[i], patch, false);
checkIsImportedProduct(importedProducts[i], patch);
} catch (e) {
handleRecursedValidationError(e, errors, `imported_products[${i}]`);
}
@ -128,7 +109,7 @@ export function checkIsOrder<Patch extends boolean = boolean>(
if (work instanceof Array) {
for (const i in work) {
try {
checkIsWork(work[i], patch, false);
checkIsWork(work[i], patch);
} catch (e) {
handleRecursedValidationError(e, errors, `work[${i}]`);
}
@ -143,49 +124,49 @@ export function checkIsOrder<Patch extends boolean = boolean>(
export default defineEventHandler(async (e) => {
const body = await readBody(e);
const id = new Snowflake().state;
const id = new Snowflake().toString();
const user = await getRequestingUser(e);
if (!checkIsOrder(body, false)) throw createError({ message: "Invalid body", statusCode: 400 });
await database.order.create({
data: {
clientId: BigInt(body.clientId),
draft: body.draft,
imported_products: {
createMany: {
data: body.imported_products.reduce(
(pV, cV) => {
pV.push({
...cV,
id: new Snowflake().state,
});
return pV;
},
[] as Array<Omit<Prisma.Prisma.ImportedProductCreateManyOrderInput, "orderId">>,
),
},
},
work: {
createMany: {
data: body.work.reduce(
(pV, cV) => {
pV.push({
...cV,
id: new Snowflake().state,
offerId: BigInt(cV.offerId),
});
return pV;
},
[] as Array<Omit<Prisma.Prisma.WorkCreateManyOrderInput, "orderId">>,
),
},
},
id,
userId: user.id,
},
});
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).then(prismaToWeb);
return getOrder(id);
});

View file

@ -1,22 +1,17 @@
import { defineEventHandler } from "h3";
/* global defineEventHandler, createError */
import { ResultSetHeader } from "mysql2";
import { database } from "~/server/utils/database";
import { createError } from "#imports";
export default defineEventHandler(async (e) => {
const id = e.context.params?.id as string;
const id = e.context.params?.id;
try {
await database.order.delete({
where: {
id: BigInt(id),
},
});
} catch (e) {
// FIXME: should be 500 on errors other than "RecordNotFound"
throw createError({ statusCode: 404 });
}
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

@ -1,38 +1,108 @@
import { defineEventHandler } from "h3";
import { createError } from "#imports";
/* global defineEventHandler, createError */
import { database } from "~/server/utils/database";
import { prismaToWeb } from "~/server/utils/prismaToWeb";
import { offer as offerType, order } from "~/utils/types/database";
import { database, data } from "~/server/utils/database";
export async function orderExists(id: bigint) {
const exists = await database.order.findUnique({
where: {
id,
},
});
return exists !== null;
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 getOrder(id: bigint) {
const order = await database.order.findUnique({
where: {
id,
},
include: {
imported_products: true,
work: {
include: {
offer: true,
},
},
},
});
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 });
return order;
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 as string;
return getOrder(BigInt(key)).then(prismaToWeb);
const key = e.context.params?.id;
return getOrder(key as string);
});

View file

@ -1,27 +1,17 @@
import { defineEventHandler, readBody } from "h3";
/* global defineEventHandler, readBody, createError */
import { checkIsOrder } from "../orders.post";
import { getOrder } from "./[id].get";
import { database } from "~/server/utils/database";
import { prismaToWeb } from "~/server/utils/prismaToWeb";
import { createError } from "#imports";
import { database as db } from "~/server/utils/database";
export default defineEventHandler(async (e) => {
const body = await readBody(e);
const id = e.context.params?.id as string;
const id = e.context.params?.id;
if (!checkIsOrder(body, true)) throw createError({ message: "Invalid body", statusCode: 400 });
if (!checkIsOrder(e, true)) throw createError({ message: "Invalid body", statusCode: 400 });
await database.order.update({
where: {
id: BigInt(id),
},
data: {
clientId: body.clientId ? BigInt(body.clientId) : undefined,
draft: body.draft,
},
});
const database = await db.new();
await database.beginTransaction();
return getOrder(BigInt(id)).then(prismaToWeb);
for (const [k, v] of Object.entries(body))
database.query(`UPDATE TABLE \`orders\` SET \`${k}\` = ? WHERE \`id\` = ?`, [v, id]);
});

View file

@ -1,27 +1,12 @@
import { defineEventHandler } from "h3";
/* global defineEventHandler, createError */
import { orderExists } from "../[id].get";
import { database } from "~/server/utils/database";
import { prismaToWeb } from "~/server/utils/prismaToWeb";
import { createError } from "#imports";
import { orderExists, getImportedProducts } from "../[id].get";
export default defineEventHandler(async (e) => {
const orderId = e.context.params?.id as string;
const id = e.context.params?.id as string;
if (!(await orderExists(BigInt(orderId)))) throw createError({ statusCode: 404 });
if (!orderExists(id)) throw createError({ statusCode: 404 });
return database.importedProduct.findMany({
where: {
orderId: BigInt(orderId),
},
select: {
id: true,
link: true,
name: true,
orderId: true,
price: true,
price_imported: true,
},
}).then(prismaToWeb);
const importedProducts = await getImportedProducts(id);
return importedProducts;
});

View file

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

View file

@ -1,31 +1,12 @@
import { defineEventHandler } from "h3";
/* global defineEventHandler, createError */
import { orderExists } from "../[id].get";
import { database } from "~/server/utils/database";
import { prismaToWeb } from "~/server/utils/prismaToWeb";
import { createError } from "#imports";
import { orderExists, getWork } from "../[id].get";
export default defineEventHandler(async (e) => {
const orderId = e.context.params?.id as string;
const id = e.context.params?.id as string;
if (!await orderExists(BigInt(orderId))) throw createError({ statusCode: 404 });
if (!orderExists(id)) throw createError({ statusCode: 404 });
const data = await database.work.findMany({
where: {
orderId: BigInt(orderId),
},
select: {
id: true,
fulfilled: true,
notes: true,
offer: true,
orderId: true,
price: true,
},
});
if (!data) throw createError({ statusCode: 404 });
return prismaToWeb(data);
const work = await getWork(id);
return work;
});

View file

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

View file

@ -1,24 +1,20 @@
import { defineEventHandler } from "h3";
/* global defineEventHandler, createError */
import { ResultSetHeader } from "mysql2";
import { orderExists } from "../../[id].get";
import { database } from "~/server/utils/database";
import { createError } from "#imports";
export default defineEventHandler(async (e) => {
const idOrder = e.context.params?.id as string;
const idWork = e.context.params?.idWork as string;
try {
await database.work.delete({
where: {
id: BigInt(idWork),
orderId: BigInt(idOrder),
},
});
} catch (e) {
// FIXME: should be 500 on errors other than "RecordNotFound"
throw createError({ statusCode: 404 });
}
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

@ -1,32 +1,11 @@
import { defineEventHandler } from "h3";
/* global defineEventHandler, createError */
import { orderExists } from "../../[id].get";
import { database } from "~/server/utils/database";
import { prismaToWeb } from "~/server/utils/prismaToWeb";
import { orderExists, getWork } from "../../[id].get";
import { createError } from "#imports";
export default defineEventHandler(async (e) => {
export default defineEventHandler((e) => {
const idOrder = e.context.params?.id as string;
const idWork = e.context.params?.idWork as string;
if (!await orderExists(BigInt(idOrder))) throw createError({ statusCode: 404 });
const data = await database.work.findUnique({
where: {
orderId: BigInt(idOrder),
id: BigInt(idWork),
},
select: {
id: true,
fulfilled: true,
notes: true,
offer: true,
orderId: true,
price: true,
},
});
if (!data) throw createError({ statusCode: 404 });
return prismaToWeb(data);
if (!orderExists(idOrder)) throw createError({ statusCode: 404 });
return getWork(idWork);
});

View file

@ -1,8 +1,14 @@
import { defineEventHandler } from "h3";
/* global defineEventHandler, getCookie */
import getRequestingUser from "~/server/utils/getRequestingUser";
import { prismaToWeb } from "~/server/utils/prismaToWeb";
import { database, data } from "~/server/utils/database";
import { user } from "~/utils/types/database";
export default defineEventHandler((e) => {
return getRequestingUser(e).then(prismaToWeb);
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;
});

View file

@ -1,8 +1,6 @@
import { defineEventHandler, getCookie } from "h3";
import { createError } from "#imports";
/* global defineEventHandler, createError, getCookie */
import { database } from "~/server/utils/database";
import getRequestingUser from "~/server/utils/getRequestingUser";
import { database, data } from "~/server/utils/database";
const endpointsWithoutAuth: string[] = [
"/dbtest",
@ -35,14 +33,13 @@ export default defineEventHandler(async (e) => {
export async function isAuthorised(token: string | undefined): Promise<boolean> {
if (!token) return false;
try {
await database.session.findUniqueOrThrow({
where: {
id: BigInt(token),
},
});
const [[session]] = await database.query(
"SELECT EXISTS(SELECT `id` FROM `sessions` WHERE `id` = ? AND `expiry_date` >= NOW()) as `logged_in`",
[token],
) as unknown as data<{logged_in: number}>;
return true;
} catch (e) {
return session.logged_in === 1;
} catch {
return false;
}
}

View file

@ -1,108 +0,0 @@
// BAA stands for Before Around After
import { getQuery, type H3Event } from "h3";
import { type QueryObject } from "ufo";
import { isString } from "./isString";
import { createError } from "#imports";
type queryType<none extends boolean = boolean> = none extends false ? {
type: "before" | "after" | "around",
id: bigint
} : {
type: null
};
export type pageData<none extends boolean = boolean> = queryType<none> & { count: number }
/**
* Gets queryType for a given query with a value
* @param query the query to parse
* @throws if query malformed (multiple before/after/around)
*/
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: BigInt(before) };
}
if (isString(after)) {
setLocationParametersCount++;
rvalue = { type: "after", id: BigInt(after) };
}
if (isString(around)) {
setLocationParametersCount++;
rvalue = { type: "around", id: BigInt(around) };
}
if (setLocationParametersCount > 1) {
throw createError({
statusCode: 400,
message: "multiple location parameters not allowed",
});
}
return rvalue;
}
/** Gets the count parameter from the query object.
* @param query the query to check.
* @param defaultCount the default count if the query doesn't have count parameter. (default 50)
* @param countLimit the maximum count of the parameter before throwing. (default 200)
* @returns the value of count parameter.
* @throws if the parameter in query exceeds provided countLimit.
*/
function getRequestedCount(
query: QueryObject,
defaultCount = 50,
countLimit = 200,
) {
let count = defaultCount;
if (query.limit) count = Number(query.limit);
if (count > countLimit) {
throw createError({
statusCode: 400,
message: `Cannot retrieve more than ${countLimit} records`,
});
}
if (count <= 0) {
throw createError({
statusCode: 400,
message: "Tried to retireve 0 or less records",
});
}
return count;
}
/** Gets the baa page parameters from the H3event,
* @param e the H3event to fetch parameters.
* @param defaultCount the default count to use if there is no count parameter. (default 50)
* @param countLimit the maximum value of the count parameter before throwing an error. (default 200)
* @returns the page data found in the query.
* @throws if event has a count parameter in the query that exceed provided countLimit.
*/
export default function getPaginatedParameters(
e: H3Event,
defaultCount = 50,
countLimit = 200,
): pageData {
const query = getQuery(e);
const queryParameters = getLocationParameterType(query);
const queryCount = getRequestedCount(query, defaultCount, countLimit);
return {
...queryParameters,
count: queryCount,
};
}

View file

@ -0,0 +1,261 @@
/* global defineEventHandler, getQuery, createError, readBody, setResponseStatus */
import { QueryObject } from "ufo";
import { H3Event } from "h3";
import { ResultSetHeader } from "mysql2/promise";
import { data, database } from "./database";
import { isString } from "./isString";
import Snowflake from "~/utils/snowflake";
import { client } from "~/utils/types/database";
type queryType = {
type: "before" | "after" | "around",
id: string
} | {
type: null
};
/**
* Before, around, after pagination wrapper
*/
export default class BaaPagination<T extends {[k: string]: any}, keyType extends string = "id"> {
readonly table: string;
readonly key: keyType;
readonly select: string;
readonly groupBy: string;
private get sqlGroupBy() {
return this.groupBy !== "" ? `GROUP BY ${this.groupBy}` : "";
}
/**
* Gets queryType for a given query with a value
* @param query the query to parse
* @throws if query malformed (multiple before/after/around)
*/
static 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 getPagedResults(
queryType: queryType,
limit = 50,
where = "",
bind: Array<any> = [],
) {
const sqlwhere = where !== "" ? `AND (${where})` : "";
switch (queryType.type) {
case "before": {
const [data] = await database.query(
`SELECT ${this.select}, CONVERT(\`${this.key}\`, CHAR) AS \`${this.key}\` FROM \`${this.table}\` WHERE \`${this.key}\` < ? ${sqlwhere} ORDER BY \`${this.key}\` DESC ${this.sqlGroupBy} LIMIT ?`,
[queryType.id, ...bind, limit],
) as unknown as data<T>;
return data;
}
case "after": {
const [data] = await database.query(
`SELECT ${this.select}, CONVERT(\`${this.key}\`, CHAR) AS \`${this.key}\` FROM \`${this.table}\` WHERE \`${this.key}\` > ? ${sqlwhere} ORDER BY \`${this.key}\` DESC ${this.sqlGroupBy} LIMIT ?`,
[queryType.id, ...bind, limit],
) as unknown as data<T>;
return data;
}
case "around": {
const [data] = await database.query(
` SELECT ${this.select}, CONVERT(\`${this.key}\`, CHAR) AS \`${this.key}\` FROM (\n` +
`(SELECT * FROM \`${this.table}\` WHERE \`${this.key}\` >= ? ${sqlwhere} ORDER BY \`${this.key}\` ${this.sqlGroupBy} ASC LIMIT ?)\n` +
"UNION ALL\n" +
`(SELECT ${this.select} FROM \`${this.table}\` WHERE \`${this.key}\` < ? ${sqlwhere} ORDER BY \`${this.key}\` DESC ${this.sqlGroupBy} LIMIT ?)\n` +
`) as \`x\` ORDER BY \`${this.key}\` DESC`,
[queryType.id, ...bind, Math.ceil(limit / 2), queryType.id, ...bind, Math.floor(limit / 2)],
) as unknown as data<T>;
return data;
}
case null: {
const [data] = await database.query(
`SELECT ${this.select}, CONVERT(\`${this.key}\`, CHAR) AS \`${this.key}\` FROM \`${this.table}\` WHERE TRUE ${sqlwhere} ORDER BY \`${this.key}\` DESC ${this.sqlGroupBy} LIMIT ?`,
[...bind, limit],
) as unknown as data<T>;
return data;
}
default:
throw createError("Not implemented");
}
}
RESTget(
e: H3Event,
defaultLimit = 50,
limitLimit = 200,
where = "",
bind: Array<any> = [],
) {
const query = getQuery(e);
let limit = defaultLimit;
if (query.limit) limit = Number(query.limit);
if (limit > limitLimit) {
throw createError({
statusCode: 400,
message: `Cannot retrieve more than ${limitLimit} records`,
});
}
if (limit <= 0) {
throw createError({
statusCode: 400,
message: "Tried to retireve 0 or less records",
});
}
const queryData = BaaPagination.getLocationParameterType(query);
return this.getPagedResults(queryData, limit, where, bind);
}
async RESTpost<K extends keyof Omit<T, keyType>>(
e: H3Event,
fields: Array<K>,
valueChecker: (obj: unknown) => obj is {[P in K]: T[P]},
) {
const body = await readBody(e);
const id = new Snowflake().toString();
if (!valueChecker(body)) throw createError({ message: "Invalid body", statusCode: 400 });
const arrayToInsert: Array<any> = [id];
arrayToInsert.push(...fields.map(field => body[field]));
await database.query(
`INSERT INTO \`${this.table}\` ` +
`(\`${this.key}\`,\`${fields.join("`, `")}\`) ` +
"VALUES (" +
"?, ".repeat(fields.length) +
"?)",
arrayToInsert,
);
setResponseStatus(e, 201);
// 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 };
}
async RESTgetRecord(e: H3Event) {
const key = e.context.params?.[this.key];
const [data] = await database.query(
`SELECT ${this.select}, CONVERT(\`${this.key}\`, CHAR) AS \`${this.key}\` FROM \`${this.table}\` WHERE \`${this.key}\` = ?`,
[key],
) as data<T>;
if (!data[0]) {
throw createError({
statusCode: 404,
});
}
return data[0];
}
async RESTpatchRecord(
e: H3Event,
valueChecker: (obj: unknown) => obj is Partial<Omit<T, keyType>>,
) {
const body = await readBody(e);
const key = e.context.params?.[this.key];
if (!valueChecker(body)) throw createError({ message: "Invalid body", statusCode: 400 });
for (const [k, v] of Object.entries(body)) {
// FIXME: use single database.query method instead of looping through keys and values
const [res] = await database.query(
// I believe it is safe to put key (k) in the template
// because it is limited to 4 values here
`UPDATE \`${this.table}\` SET \`${k}\` = ? WHERE \`${this.key}\` = ?`,
[v, key],
) as unknown as [ResultSetHeader];
if (res.affectedRows !== 1) {
throw createError({
statusCode: 404,
});
}
}
const [data] = await database.query(
`SELECT ${this.select}, CONVERT(\`${this.key}\`, CHAR) AS \`${this.key}\` FROM \`${this.table}\` WHERE \`${this.key}\` = ?`,
[key],
) as data<T>;
return data[0];
}
async RESTdeleteRecord(e: H3Event) {
const key = e.context.params?.[this.key];
const [result] = await database.query(
`DELETE FROM \`${this.table}\` WHERE \`${this.key}\` = ?`,
[key],
) as unknown as [ResultSetHeader];
if (result.affectedRows === 0) throw createError({ statusCode: 404 });
return null;
}
async RESTrecordCount(
e :H3Event,
where = "",
bind: Array<any> = [],
) {
const sqlwhere = where !== "" ? `WHERE ${where}` : "";
const [[data]] = await database.query(
`SELECT COUNT(*) as \`count\` FROM \`${this.table}\` ${sqlwhere} ${this.sqlGroupBy}`,
bind,
) as data<{count: number}>;
if (!data) throw createError("Database returned no rows");
return data;
}
constructor(
table: string,
key: keyType,
select = "*",
groupBy = "",
) {
this.table = table;
this.key = key;
this.select = select;
this.groupBy = groupBy;
}
}

View file

@ -1,99 +1,19 @@
import { PrismaClient, Prisma } from "@prisma/client";
import mysql, { Connection } from "mysql2/promise";
import { type pageData } from "./baaPageParsing";
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,
};
type model = PrismaClient[Uncapitalize<Prisma.ModelName>];
export const database =
await mysql.createConnection(connectionOptions) as Connection & {
new: (localConnectionOptions: mysql.ConnectionOptions | undefined) => Promise<Connection>
};
database.new = (localConnectionOptions: mysql.ConnectionOptions | undefined) => { return mysql.createConnection({ ...localConnectionOptions, ...connectionOptions }); };
function getBeforeParameters<T, A>(
pageData: pageData<false>,
fetchArgs: Prisma.Args<T, "findMany">,
) {
const _fetchArgs = Object.assign({}, fetchArgs);
return Object.assign(_fetchArgs, {
take: pageData.count,
orderBy: [
{ id: "desc" },
],
where: {
AND: [
{
id: {
_lt: pageData.id,
},
},
fetchArgs.where,
],
},
});
}
function getAfterParameters<T>(
pageData: pageData<false>,
fetchArgs: Prisma.Args<T, "findMany">,
) {
const _fetchArgs = Object.assign({}, fetchArgs);
return Object.assign(_fetchArgs, {
take: pageData.count,
orderBy: [
{ id: "desc" },
],
where: {
AND: [
{
id: {
_gt: pageData.id,
},
},
fetchArgs.where,
],
},
});
}
function getNullParameters<T>(
pageData: pageData<true>,
fetchArgs: Prisma.Args<T, "findMany">,
) {
const _fetchArgs = Object.assign({}, fetchArgs);
return Object.assign(_fetchArgs, {
take: pageData.count,
orderBy: [
{ id: "desc" },
],
});
}
export const database = new PrismaClient().$extends({
model: {
$allModels: {
findPaginated<T, A>(
this: T,
pageData: pageData,
fetchArgs: Prisma.Exact<A, Prisma.Args<T, "findMany">>,
): Promise<Prisma.Result<T, A, "findMany">> {
const context = Prisma.getExtensionContext(this) as any;
switch (pageData.type) {
case "before":
return context.findMany(getBeforeParameters(pageData, fetchArgs));
case "after":
return context.findMany(getAfterParameters(pageData, fetchArgs));
case "around":
return Promise.all([
context.findMany(getBeforeParameters({
type: "before",
id: pageData.id,
count: Math.ceil(pageData.count),
}, fetchArgs)),
context.findMany(getAfterParameters({
type: "after",
id: pageData.id,
count: Math.floor(pageData.count),
}, fetchArgs)),
]).then(rv => rv.flat()) as Promise<any>;
case null:
return context.findMany(getNullParameters(pageData, fetchArgs));
}
},
},
},
});
export type data<T> = [T[], mysql.FieldPacket[]];

View file

@ -1,30 +1,24 @@
import { getCookie, H3Event } from "h3";
/* global getCookie, createError */
import { H3Event } from "h3";
import { database } from "./database";
import { createError } from "#imports";
import { database, data } from "./database";
import { user } from "~/utils/types/database";
export default async function getRequestingUser(e: H3Event) {
const cookie = getCookie(e, "token");
if (!cookie) throw createError("User not found");
const { user } = await database.session.findUnique({
where: {
id: BigInt(cookie),
},
select: {
user: {
select: {
display_name: true,
email: true,
id: true,
username: true,
},
},
},
}).then((e) => {
if (e === null) throw createError("User not found");
return e;
});
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,44 +0,0 @@
import { Decimal } from "@prisma/client/runtime/library";
type func = (...args: any[]) => any | Function;
export type replaceJsonUnparsableToString<T> =
T extends Array<infer E> ? Array<replaceJsonUnparsableToString<E>>
: {
[K in keyof T]:
T[K] extends null ? null
: T[K] extends func ? never
: T[K] extends Decimal ? `${number}`
: T[K] extends Array<infer E> ? Array<replaceJsonUnparsableToString<E>>
: T[K] extends object ? replaceJsonUnparsableToString<T[K]>
: T[K] extends bigint ? `${bigint}`
: T[K]
};
type exactToInterface = (...args: any[]) => any extends Function ? true : false;
function arrayPrismaToWeb<T>(array: Array<T>) {
return array.reduce(
(pV, cV) => {
pV.push(prismaToWeb(cV));
return pV;
},
[] as Array<replaceJsonUnparsableToString<T>>,
);
}
export function prismaToWeb<T>(ivalue: T): replaceJsonUnparsableToString<T> {
const rvalue: any = ivalue instanceof Array ? [] : {};
for (const i in ivalue) {
const current = ivalue[i];
if (current === null) rvalue[i] = null;
else if (typeof current === 'function') continue;
else if (current instanceof Decimal) rvalue[i] = current.toString();
else if (current instanceof Array) rvalue[i] = arrayPrismaToWeb(current);
else if (typeof current === 'object') rvalue[i] = prismaToWeb(current);
else if (typeof current === 'bigint') rvalue[i] = current.toString();
else rvalue[i] = current;
}
return rvalue;
}

View file

@ -1,4 +1,4 @@
import { createError } from "#imports";
/* global createError */
export function createValidationError(errors: Map<string, string>) {
let message = "Invalid parameters: ";

View file

@ -1,86 +0,0 @@
{ pkgs ? import <nixpkgs> {} }:
let
# Updating this package will force an update for nodePackages.prisma. The
# version of prisma-engines and nodePackages.prisma must be the same for them to
# function correctly.
prisma-version = "5.5.2";
prisma-src = pkgs.fetchFromGitHub {
owner = "prisma";
repo = "prisma-engines";
rev = prisma-version;
hash = "sha256-d24b+Jobt5+vH7SGYOnDIR9DOtM0Y2XSfHZGkr7EidA=";
};
new-prisma-engines = pkgs.rustPlatform.buildRustPackage {
pname = "prisma-engines";
version = prisma-version;
src = builtins.storePath prisma-src;
# Use system openssl.
OPENSSL_NO_VENDOR = 1;
nativeBuildInputs = [ pkgs.pkg-config pkgs.git ];
buildInputs = [
pkgs.openssl
pkgs.protobuf
];
cargoLock = {
lockFile = "${prisma-src}/Cargo.lock";
outputHashes = {
"barrel-0.6.6-alpha.0" = "sha256-USh0lQ1z+3Spgc69bRFySUzhuY79qprLlEExTmYWFN8=";
"graphql-parser-0.3.0" = "sha256-0ZAsj2mW6fCLhwTETucjbu4rPNzfbNiHu2wVTBlTNe4=";
"mysql_async-0.31.3" = "sha256-QIO9s0Upc0/1W7ux1RNJNGKqzO4gB4gMV3NoakAbxkQ=";
"postgres-native-tls-0.5.0" = "sha256-UYPsxhCkXXWk8yPbqjNS0illwjS5mVm3Z/jFwpVwqfw=";
};
};
preBuild = ''
export OPENSSL_DIR=${pkgs.lib.getDev pkgs.openssl}
export OPENSSL_LIB_DIR=${pkgs.lib.getLib pkgs.openssl}/lib
export PROTOC=${pkgs.protobuf}/bin/protoc
export PROTOC_INCLUDE="${pkgs.protobuf}/include";
export SQLITE_MAX_VARIABLE_NUMBER=250000
export SQLITE_MAX_EXPR_DEPTH=10000
'';
cargoBuildFlags = [
"-p" "query-engine"
"-p" "query-engine-node-api"
"-p" "schema-engine-cli"
"-p" "prisma-fmt"
];
postInstall = ''
mv $out/lib/libquery_engine${pkgs.stdenv.hostPlatform.extensions.sharedLibrary} $out/lib/libquery_engine.node
'';
# Tests are long to compile
doCheck = false;
# meta = with lib; {
# description = "A collection of engines that power the core stack for Prisma";
# homepage = "https://www.prisma.io/";
# license = licenses.asl20;
# platforms = platforms.unix;
# maintainers = with maintainers; [ pimeys tomhoule ivan aqrln ];
# };
};
in
pkgs.mkShell {
nativeBuildInputs = [
new-prisma-engines
pkgs.nodejs_18
pkgs.openssl
];
shellHook = ''
export PRISMA_SCHEMA_ENGINE_BINARY="${new-prisma-engines}/bin/schema-engine"
export PRISMA_QUERY_ENGINE_BINARY="${new-prisma-engines}/bin/query-engine"
export PRISMA_QUERY_ENGINE_LIBRARY="${new-prisma-engines}/lib/libquery_engine.node"
export PRISMA_FMT_BINARY="${new-prisma-engines}/bin/prisma-fmt"
'';
}

View file

@ -1,4 +1,4 @@
import { type CookieSerializeOptions } from "cookie-es";
import { CookieSerializeOptions } from "cookie-es";
export const cookieSettings: CookieSerializeOptions = {
sameSite: "lax",

99
utils/types/database.ts Normal file
View file

@ -0,0 +1,99 @@
export interface client {
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;
}
export interface session {
id: string;
user: string;
expiry_date: string;
}
export interface imported_product {
id: string;
// order: string,
name?: string;
link: string;
price_imported: string;
price: string;
}
export interface offer {
id: string;
name: string;
description?: string;
recommended_price?: string;
}
export interface work {
id: string;
// order: string,
offer: string | offer;
price: string;
notes: string;
is_fulfilled: boolean;
}
export interface order {
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;
// order_template: string,
offer: string | offer;
price: string;
notes?: string;
}
export interface order_template {
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}`;