Enhance backend and frontend setup with MySQL integration, Docker configurations, and toast notifications

- Updated .gitignore to include additional environment and build files
- Configured Dockerfiles for backend and frontend with npm install and port exposure
- Added MySQL connection pool and query function in backend services
- Implemented form submission with toast notifications in MainForm component
- Updated package.json and package-lock.json for new dependencies
- Enhanced routing and layout in frontend with toast notifications
This commit is contained in:
2025-08-11 19:56:43 +02:00
parent 08c820ac35
commit 0a2f1e650d
17 changed files with 411 additions and 15 deletions

116
.gitignore vendored
View File

@@ -1,4 +1,114 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Production builds
dist/
build/
out/
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Editor directories and files
.vscode/
.idea/
*.swp
*.swo
*~
weather-app.code-workspace
# OS generated files
.DS_Store .DS_Store
backend/node_modules/ .DS_Store?
client/node_modules/ ._*
todo.txt .Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Logs
logs/
*.log
# Runtime data
pids/
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
*.lcov
# nyc test coverage
.nyc_output
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
# Storybook build outputs
.out
.storybook-out
# Temporary folders
tmp/
temp/
# Frontend specific (React/Vite)
frontend/dist/
frontend/build/
frontend/.vite/
# Backend specific (Express)
backend/uploads/
backend/public/uploads/
# Database
*.db
*.sqlite
*.sqlite3
# API keys and secrets (additional protection)
config/
secrets/
keys/

View File

@@ -0,0 +1,12 @@
FROM node:20-alpine
WORKDIR /backend
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8002
CMD ["npm", "start"]

View File

@@ -12,7 +12,8 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"ejs": "^3.1.10", "ejs": "^3.1.10",
"express": "^5.1.0" "express": "^5.1.0",
"mysql2": "^3.14.3"
} }
}, },
"node_modules/accepts": { "node_modules/accepts": {
@@ -34,6 +35,15 @@
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/aws-ssl-profiles": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -176,6 +186,15 @@
} }
} }
}, },
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -381,6 +400,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"license": "MIT",
"dependencies": {
"is-property": "^1.0.2"
}
},
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -512,6 +540,12 @@
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT"
},
"node_modules/jake": { "node_modules/jake": {
"version": "10.9.4", "version": "10.9.4",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
@@ -529,6 +563,36 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"node_modules/lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/lru.min": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz",
"integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==",
"license": "MIT",
"engines": {
"bun": ">=1.0.0",
"deno": ">=1.30.0",
"node": ">=8.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wellwelwel"
}
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -598,6 +662,38 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/mysql2": {
"version": "3.14.3",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.3.tgz",
"integrity": "sha512-fD6MLV8XJ1KiNFIF0bS7Msl8eZyhlTDCDl75ajU5SJtpdx9ZPEACulJcqJWr1Y8OYyxsFc4j3+nflpmhxCU5aQ==",
"license": "MIT",
"dependencies": {
"aws-ssl-profiles": "^1.1.1",
"denque": "^2.1.0",
"generate-function": "^2.3.1",
"iconv-lite": "^0.6.3",
"long": "^5.2.1",
"lru.min": "^1.0.0",
"named-placeholders": "^1.1.3",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.2"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/named-placeholders": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
"license": "MIT",
"dependencies": {
"lru-cache": "^7.14.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/negotiator": { "node_modules/negotiator": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
@@ -789,6 +885,11 @@
"node": ">= 18" "node": ">= 18"
} }
}, },
"node_modules/seq-queue": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
},
"node_modules/serve-static": { "node_modules/serve-static": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
@@ -882,6 +983,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/sqlstring": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",

View File

@@ -14,6 +14,7 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"ejs": "^3.1.10", "ejs": "^3.1.10",
"express": "^5.1.0" "express": "^5.1.0",
"mysql2": "^3.14.3"
} }
} }

View File

@@ -1,9 +1,10 @@
import express from "express"; import express from "express";
import cors from "cors"; import cors from "cors";
import env from "dotenv"; import env from "dotenv";
import { query } from "./services/database.js";
env.config(); env.config();
const app = express(); const app = express();
const port = 8001; const port = 8002;
app.use(cors()); app.use(cors());
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ extended: true }));
@@ -14,6 +15,15 @@ app.get("/", (req, res) => {
res.render("index.ejs", { title: port }); res.render("index.ejs", { title: port });
}); });
app.post("/lose", async (req, res) => {
const result = await query(req.body);
if (result.success) {
res.status(200).send("Update successful");
} else {
res.status(400).send("Update failed");
}
});
app.listen(port, () => { app.listen(port, () => {
console.log(`Server is running on port: ${port}`); console.log(`Server is running on port: ${port}`);
}); });

View File

@@ -0,0 +1,34 @@
import mysql from "mysql2";
import dotenv from "dotenv";
dotenv.config();
// Create a MySQL connection pool using environment variables for configuration
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();
export async function query(params) {
const [result] = await pool.query(
"UPDATE lose SET vorname = ?, nachname = ?, adresse = ?, plz = ?, email = ? WHERE losnummer = ? AND vorname IS NULL AND nachname IS NULL AND adresse IS NULL AND plz IS NULL AND email IS NULL",
[
params.vorname,
params.nachname,
params.adresse,
params.plz,
params.email,
params.losnummer,
]
);
if (result.affectedRows > 0) {
console.log("Update successful:", result);
return { success: true };
} else {
return { success: false };
}
}

View File

@@ -0,0 +1,43 @@
services:
# mcs_lose-frontend:
# container_name: mcs_lose-frontend
# build: ./frontend
# ports:
# - "8001:8001"
# environment:
# - CHOKIDAR_USEPOLLING=true
# volumes:
# - ./frontend:/app
# - /app/node_modules
# restart: unless-stopped
mcs_lose-backend:
container_name: mcs_lose-backend_express
build: ./backend
ports:
- "8002:8002"
environment:
DB_HOST: mysql
DB_USER: root
DB_PASSWORD: D7Ze0lwV9hMrNQHdz1Q8yi0MIQuOO8
DB_NAME: mcs_lose
depends_on:
- mysql
volumes:
- ./backend:/mcs_lose-backend
restart: unless-stopped
mysql:
container_name: mcs_lose-mysql
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: D7Ze0lwV9hMrNQHdz1Q8yi0MIQuOO8
MYSQL_DATABASE: mcs_lose
volumes:
- mysql-data:/var/lib/mysql
ports:
- "3308:3306"
volumes:
mysql-data:

View File

@@ -0,0 +1,12 @@
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8001
CMD ["npm", "run", "dev"]

View File

@@ -13,6 +13,7 @@
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-router-dom": "^7.8.0", "react-router-dom": "^7.8.0",
"react-toastify": "^11.0.5",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
@@ -2371,6 +2372,15 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -3851,6 +3861,19 @@
"react-dom": ">=18" "react-dom": ">=18"
} }
}, },
"node_modules/react-toastify": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
"integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==",
"license": "MIT",
"dependencies": {
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": "^18 || ^19",
"react-dom": "^18 || ^19"
}
},
"node_modules/resolve-from": { "node_modules/resolve-from": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",

View File

@@ -15,6 +15,7 @@
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-router-dom": "^7.8.0", "react-router-dom": "^7.8.0",
"react-toastify": "^11.0.5",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",

View File

@@ -1,6 +1,7 @@
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Admin from './components/Admin'; // Beispiel-Komponente import Admin from './components/Admin';
import Home from './components/Home'; // Beispiel-Komponente import Home from './components/Home';
import "react-toastify/dist/ReactToastify.css";
function App() { function App() {
return ( return (

View File

@@ -1,6 +1,7 @@
import "../App.css"; import "../App.css";
import Layout from "../layout/Layout"; import Layout from "../layout/Layout";
import MainForm from "./MainForm"; import MainForm from "./MainForm";
import "react-toastify/dist/ReactToastify.css";
function App() { function App() {
return ( return (

View File

@@ -1,11 +1,16 @@
import React from "react"; import React from "react";
import { registerLos } from "../utils/register"; import { registerLos } from "../utils/register";
import { toast } from "react-toastify";
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
const formData = new FormData(event.currentTarget); const formData = new FormData(event.currentTarget);
const data = Object.fromEntries(formData.entries()); const data = Object.fromEntries(formData.entries());
await registerLos(data); toast.promise(registerLos(data), {
pending: "Registriere Los...",
success: "Los erfolgreich registriert!",
error: "Fehler bei der Registrierung des Loses.",
});
}; };
const MainForm: React.FC = () => { const MainForm: React.FC = () => {
@@ -72,15 +77,15 @@ const MainForm: React.FC = () => {
<div className="space-y-1"> <div className="space-y-1">
<label <label
htmlFor="strasse" htmlFor="adresse"
className="text-sm font-medium text-zinc-800" className="text-sm font-medium text-zinc-800"
> >
Straße + Haus Nr.: Straße + Haus Nr.:
</label> </label>
<input <input
type="text" type="text"
id="strasse" id="adresse"
name="strasse" name="adresse"
placeholder="Musterstraße 1" placeholder="Musterstraße 1"
required required
className="w-full rounded-xl border border-black/25 bg-white/80 px-4 py-2.5 text-sm text-zinc-800 placeholder-zinc-400 shadow-inner outline-none focus:border-black/40 focus:ring-2 focus:ring-black/10" className="w-full rounded-xl border border-black/25 bg-white/80 px-4 py-2.5 text-sm text-zinc-800 placeholder-zinc-400 shadow-inner outline-none focus:border-black/40 focus:ring-2 focus:ring-black/10"

View File

@@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import Header from "../components/Header"; import Header from "../components/Header";
import "../App.css"; import "../App.css";
import { ToastContainer } from "react-toastify";
type LayoutProps = { type LayoutProps = {
children: React.ReactNode; children: React.ReactNode;
@@ -12,6 +13,7 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
<Header /> <Header />
<main>{children}</main> <main>{children}</main>
<footer></footer> <footer></footer>
<ToastContainer />
</> </>
); );
}; };

View File

@@ -1,3 +1,17 @@
import { myToast } from "./toastify";
export const registerLos = async (data: any) => { export const registerLos = async (data: any) => {
console.log(data); await fetch("http://localhost:8002/lose", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}).then((response) => {
if (response.ok) {
myToast("Los erfolgreich registriert!", "success");
} else {
myToast("Fehler bei der Registrierung des Loses.", "error");
} }
});
};

View File

@@ -0,0 +1,17 @@
import { toast, type ToastOptions } from "react-toastify";
export type ToastType = "success" | "error" | "info" | "warning";
export const myToast = (message: string, msgType: ToastType) => {
let config: ToastOptions = {
position: "top-right",
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "dark",
};
toast[msgType](message, config);
};

View File

@@ -7,7 +7,7 @@ export default defineConfig({
plugins: [react(), svgr(), tailwindcss()], plugins: [react(), svgr(), tailwindcss()],
server: { server: {
host: "0.0.0.0", host: "0.0.0.0",
port: 5001, port: 8001,
watch: { watch: {
usePolling: true, usePolling: true,
}, },