+ It looks like you've run the server with an empty or uninitialized database or with database without any users.
+ Below you can initialize the database register your first user and.
+
+ { formValue[k] = v }"
+ />
+
+
+
+ Initialize
+
+
+
+
diff --git a/pages/login.vue b/pages/login.vue
index 5761adf..0667932 100644
--- a/pages/login.vue
+++ b/pages/login.vue
@@ -19,6 +19,7 @@ const redirectTo = ref(route.redirectedFrom);
definePageMeta({
layout: false,
+ middleware: ["first-run"],
});
async function submit() {
diff --git a/schemaModel.sql b/schemaModel.sql
new file mode 100644
index 0000000..b03f0ac
--- /dev/null
+++ b/schemaModel.sql
@@ -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`;
diff --git a/server/api/firstRun.get.ts b/server/api/firstRun.get.ts
new file mode 100644
index 0000000..ae1bb23
--- /dev/null
+++ b/server/api/firstRun.get.ts
@@ -0,0 +1,15 @@
+/* global defineEventHandler */
+import { data, database } from "../utils/database";
+
+export async function isFirstRun() {
+ 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) => {
+ return isFirstRun();
+});
diff --git a/server/api/firstRun.post.ts b/server/api/firstRun.post.ts
new file mode 100644
index 0000000..657b8d5
--- /dev/null
+++ b/server/api/firstRun.post.ts
@@ -0,0 +1,33 @@
+/* global defineEventHandler, setResponseStatus, readBody, createError */
+
+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";
+
+export default defineEventHandler(async (e) => {
+ if (!isFirstRun()) {
+ setResponseStatus(e, 404);
+ return "";
+ }
+
+ const body = await readBody(e);
+ if (typeof body !== "object") throw createError({ message: "Invalid body", statusCode: 400 });
+ const username = body.username;
+ if (typeof username !== "string") throw createError({ message: "username is not string", statusCode: 400 });
+ const password = body.password;
+ if (typeof password !== "string") throw createError({ message: "password is not string", statusCode: 400 });
+ const email = body.email;
+ if (typeof email !== "string") throw createError({ message: "email is not string", statusCode: 400 });
+
+ 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 "";
+});
diff --git a/server/api/login.post.ts b/server/api/login.post.ts
index b011aaf..f603761 100644
--- a/server/api/login.post.ts
+++ b/server/api/login.post.ts
@@ -6,6 +6,12 @@ import { isString } from "../utils/isString";
import { cookieSettings } from "../utils/rootUtils";
import Snowflake from "~/utils/snowflake";
+export function getPasswordHash(password: string) {
+ return crypto.createHmac("sha512", "42")
+ .update(password)
+ .digest();
+}
+
export default defineEventHandler(async (e) => {
if (getCookie(e, "token"))
throw createError({ statusCode: 501, message: "Case not implemented: logging in while cookie is set" });
@@ -18,12 +24,10 @@ export default defineEventHandler(async (e) => {
if (!isString(login)) throw createError({ statusCode: 400, message: "Login is not string." });
if (!isString(password)) throw createError({ statusCode: 400, message: "Password is not string." });
- const hashedPassword = crypto.createHmac("sha512", "42")
- .update(password)
- .digest("hex");
+ const hashedPassword = getPasswordHash(password);
const [account] = await database.query(
- "SELECT CONVERT(`id`, CHAR(32)) AS `id` from `users` WHERE `username` = ? AND LOWER(HEX(`password`)) = ? LIMIT 1",
+ "SELECT CONVERT(`id`, CHAR(32)) AS `id` from `users` WHERE `username` = ? AND `password` = ? LIMIT 1",
[login, hashedPassword],
)as unknown as data<{id: string}>;
diff --git a/server/middleware/auth.ts b/server/middleware/auth.ts
index cb16180..82760c6 100644
--- a/server/middleware/auth.ts
+++ b/server/middleware/auth.ts
@@ -8,6 +8,7 @@ const endpointsWithoutAuth: string[] = [
"/hi",
"/login",
"/logout",
+ "/firstRun",
];
export default defineEventHandler(async (e) => {
@@ -31,10 +32,14 @@ export default defineEventHandler(async (e) => {
*/
export async function isAuthorised(token: string | undefined): Promise {
if (!token) return false;
- 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}>;
+ try {
+ 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 session.logged_in === 1;
+ return session.logged_in === 1;
+ } catch {
+ return false;
+ }
}