diff --git a/README.md b/README.md index 36c4eed..0727cd9 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,22 @@ If you want to be always up to date, you can clone this repository by running: git clone https://git.the1s.de/theis.gaedigk/stockhome.git ``` -> **NOTE**: To do this, you must have git installed. [How to install git?](https://git-scm.com/install/) +> **NOTE:** To do this, you must have git installed. [How to install git?](https://git-scm.com/install/) -### 3. Run Stockhome +### 3. Create `.env` file + +In the root directory of this repository create an .env file and enter the following records: + +```txt +MYSQL_ROOT_PASSWORD= +AUTH_SIGNATURE= +``` + +Make sure that you have set an secure root password and a secure signature. + +> **NOTE:** These two values cannot contain special characters. + +### 4. Start Stockhome First, navigate into the root directory of this repository and run: @@ -64,7 +77,7 @@ docker compose up -d --build The database and all necessary services are started and initialised automatically. -### 4. First login +### 5. First login The default admin credentials are always: diff --git a/backend/database.scheme.sql b/backend/database.scheme.sql index 05ed839..7d94897 100644 --- a/backend/database.scheme.sql +++ b/backend/database.scheme.sql @@ -47,4 +47,4 @@ CREATE TABLE IF NOT EXISTS app_settings ( updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -INSERT INTO app_settings (name, value) VALUES ("app-name", null), ("currency", null); \ No newline at end of file +INSERT INTO app_settings (name, value) VALUES ("app-name", null), ("currency", null), ("first-startup", "true"); \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 44d4ec8..6d93b4d 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,16 +1,27 @@ import express from "express"; import cors from "cors"; import dotenv from "dotenv"; +import mysql from "mysql2"; +import { readFile } from "fs/promises"; dotenv.config(); - const app = express(); -const PORT = process.env.PORT; app.set("view engine", "ejs"); +const port = 8004; + app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); +const pool = mysql + .createPool({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + }) + .promise(); + // frontend routes import userRouter from "./routes/app/users.route.js"; app.use("/users", userRouter); @@ -21,10 +32,105 @@ app.use("/products", productRouter); import storageRouter from "./routes/app/storage.route.js"; app.use("/storage", storageRouter); -app.listen(PORT, () => { - console.log(`Server is running on http://localhost:${PORT}`); +app.listen(port, () => { + runStartup(port); }); +// Startup code +const runStartup = async (port) => { + // Check if database is configured; create schema if app_settings is missing. + let firstStartupValue = null; + try { + const [firstResponse] = await pool.query( + `SELECT value FROM app_settings WHERE name = "first-startup";`, + ); + firstStartupValue = firstResponse[0]?.value ?? null; + } catch (err) { + if (err?.code !== "ER_NO_SUCH_TABLE") { + throw err; + } + } + + if (firstStartupValue !== "false") { + const schemaPath = new URL("./database.scheme.sql", import.meta.url); + const schemaSql = await readFile(schemaPath, "utf8"); + const statements = schemaSql + .split(";") + .map((statement) => statement.trim()) + .filter((statement) => statement.length > 0); + + for (const statement of statements) { + await pool.query(statement); + } + + // create admin credentials + const [result] = await pool.query( + `SELECT value FROM app_settings WHERE name = "first-startup";`, + ); + + if (result[0]?.value === "true") { + const insertResult = await insertFirstData(); + + if (insertResult.affectedRows > 0) { + // print out admin credentials + console.log("Successfully created admin user!"); + console.log("Username: admin"); + console.log("Password: admin"); + + // Set startup variable to true if scheme insert was successfull + const [scndResponse] = await pool.query( + `UPDATE app_settings SET value = "false" WHERE name = "first-startup";`, + ); + + if (scndResponse.affectedRows > 0) { + console.log("Database settet up successfully!"); + } else { + console.error("There was an error while setting up the database!"); + } + } else { + console.error("Error while creating admin user."); + } + } + } + + console.log("Everything is settet up successfully!"); + console.log(`Server is running on http://localhost:${port}`); +}; + +const insertFirstData = async () => { + const [insertResult] = await pool.query( + `INSERT INTO users (username, first_name, last_name, email, password, is_admin) VALUES ("admin", "admin", "admin", "admin@example.com", "admin", 1)`, + ); + + await pool.query( + `INSERT INTO storage_locations (name, description) VALUES (?, ?);`, + ["Default Storage", "Initial storage location"], + ); + + const [storageRows] = await pool.query( + `SELECT uuid FROM storage_locations WHERE name = ? LIMIT 1;`, + ["Default Storage"], + ); + + const storageUuid = storageRows[0]?.uuid ?? null; + if (storageUuid) { + await pool.query( + `INSERT INTO products (name, description, price, amount, storage_location, picture) VALUES (?, ?, ?, ?, ?, ?);`, + [ + "Welcome Product", + "Your first item in Stockhome", + "0.00", + 1, + storageUuid, + null, + ], + ); + } + + console.log("Welcome product is ready...") + return insertResult; +}; + // error handling code app.use((err, req, res, next) => { console.error(err.stack); diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 0f90e16..8ac092a 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -2,9 +2,9 @@ services: database: container_name: stockhome-mysql image: mysql:8.0 - restart: unless-stopped ports: - "3312:3306" + restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: stockhome @@ -12,16 +12,27 @@ services: volumes: - ./.docker/volumes/stockhome_mysql:/var/lib/mysql - ./mysql-timezone.cnf:/etc/mysql/conf.d/timezone.cnf:ro + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 5s + timeout: 3s + retries: 20 backend: container_name: stockhome-backend + ports: + - "8004:8004" build: context: ./backend dockerfile: Dockerfile - ports: - - "8004:8004" environment: + DB_HOST: stockhome-mysql + DB_USER: root + DB_NAME: stockhome + DB_PASSWORD: ${MYSQL_ROOT_PASSWORD} + SECRET_KEY: ${AUTH_SIGNATURE} NODE_ENV: production depends_on: - - database + database: + condition: service_healthy restart: unless-stopped diff --git a/docker-compose.yml b/docker-compose.yml index b5a2c41..0aa0c0f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,11 @@ services: volumes: - ./.docker/volumes/stockhome_mysql:/var/lib/mysql - ./mysql-timezone.cnf:/etc/mysql/conf.d/timezone.cnf:ro + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 5s + timeout: 3s + retries: 20 backend: container_name: stockhome-backend @@ -17,14 +22,22 @@ services: context: ./backend dockerfile: Dockerfile environment: + DB_HOST: stockhome-mysql + DB_USER: root + DB_NAME: stockhome + DB_PASSWORD: ${MYSQL_ROOT_PASSWORD} + SECRET_KEY: ${AUTH_SIGNATURE} NODE_ENV: production depends_on: - - database + database: + condition: service_healthy restart: unless-stopped - rontend: + frontend: container_name: stockhome-frontend build: ./frontend + environment: + VITE_BACKEND_URL: http://localhost:8004 depends_on: - backend restart: unless-stopped