32 Commits

Author SHA1 Message Date
theis.gaedigk cf2df0aaac edited code design 2026-05-09 21:46:01 +02:00
theis.gaedigk 1199d6468f noted out public web-ui port 2026-05-09 21:45:19 +02:00
theis.gaedigk 7cd958c31e edited ip adresses 2026-05-09 21:43:12 +02:00
theis.gaedigk f89cf84a38 edited docker config 2026-05-09 21:38:56 +02:00
theis.gaedigk e3fc1d8659 edited again 2026-05-09 21:30:59 +02:00
theis.gaedigk 060f8d01c6 edited again 2026-05-09 21:27:47 +02:00
theis.gaedigk 667609d70c fixed docker config 2026-05-09 21:24:42 +02:00
theis.gaedigk b05f19acd9 edited docker compose 2026-05-09 21:22:28 +02:00
theis.gaedigk 2aa9a968f5 Merge branch 'dev' into prod 2026-05-04 22:40:33 +02:00
theis.gaedigk 2a4825269b fix: update SQL queries to use parameterized table names for security
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 22:40:24 +02:00
theis.gaedigk e42a2f510a edited docker compose 2026-05-04 22:05:18 +02:00
theis.gaedigk d2b22fc71f Merge branch 'dev' into prod 2026-05-04 22:04:26 +02:00
theis.gaedigk 4df6d243f3 feat: implement dynamic API base URL and add language toggle in MainForm
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 22:03:21 +02:00
theis.gaedigk 471c0c7a49 Merge branch 'dev' into prod 2026-01-21 16:33:03 +01:00
theis.gaedigk 75ff65e76b Merge branch 'dev' into prod 2026-01-21 16:28:23 +01:00
theis.gaedigk 7cf1245ef6 Merge branch 'dev' into prod 2026-01-21 14:27:37 +01:00
theis.gaedigk 2adbfa75a5 Merge branch 'dev' into prod 2026-01-21 14:07:26 +01:00
theis.gaedigk 216a1cb1d4 Merge branch 'dev' into prod 2026-01-20 20:43:59 +01:00
theis.gaedigk 7fc98d6c9f Merge branch 'dev' into prod 2026-01-20 20:34:52 +01:00
theis.gaedigk e346cf9445 e 2026-01-20 20:33:41 +01:00
theis.gaedigk c030b6dbe6 Merge branch 'dev' into prod 2026-01-20 20:33:31 +01:00
theis.gaedigk 6f26b9bbc3 e 2026-01-20 20:22:59 +01:00
theis.gaedigk a34a70572f edited 2026-01-20 20:19:12 +01:00
theis.gaedigk 4b3c8a2424 edited compose file 2026-01-20 20:17:53 +01:00
theis.gaedigk 568b3bf495 edited 2026-01-20 20:08:14 +01:00
theis.gaedigk 5653d32857 fix: update WireGuard PASSWORD_HASH to a static value 2026-01-20 20:06:44 +01:00
theis.gaedigk 7cf5b8df48 Merge branch 'dev' into prod 2026-01-20 20:03:51 +01:00
theis.gaedigk 65c5fc0f8f Merge branch 'dev' into prod 2026-01-20 19:59:27 +01:00
theis.gaedigk b626a67907 Merge branch 'dev' into prod 2026-01-20 19:46:56 +01:00
theis.gaedigk 6643a176a6 Merge branch 'dev' into prod 2026-01-20 19:43:53 +01:00
theis.gaedigk 89803754a7 Merge branch 'dev' into prod 2026-01-20 19:38:27 +01:00
theis.gaedigk 5052b3e83a changed fetch urls 2026-01-20 19:23:53 +01:00
7 changed files with 122 additions and 137 deletions
+8 -5
View File
@@ -40,7 +40,7 @@ export const confirmUser = async (username) => {
console.log(tableName); console.log(tableName);
const [createTable] = await pool.query( const [createTable] = await pool.query(
`CREATE TABLE IF NOT EXISTS ${tableName} ( `CREATE TABLE IF NOT EXISTS ?? (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
Vorname VARCHAR(100) NOT NULL, Vorname VARCHAR(100) NOT NULL,
Nachname Varchar(100) NOT NULL, Nachname Varchar(100) NOT NULL,
@@ -56,14 +56,16 @@ export const confirmUser = async (username) => {
Plz_Ort Varchar(100), Plz_Ort Varchar(100),
Zahlungsmethode Varchar(100), Zahlungsmethode Varchar(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)` )`,
[tableName],
); );
if (createTable) { if (createTable) {
let nextID; let nextID;
const getNextID = async () => { const getNextID = async () => {
const [rows] = await pool.query( const [rows] = await pool.query(
`SELECT id FROM ${tableName} ORDER BY id DESC LIMIT 1` `SELECT id FROM ?? ORDER BY id DESC LIMIT 1`,
[tableName],
); );
nextID = rows.length > 0 ? rows[0].id + 1 : 1; nextID = rows.length > 0 ? rows[0].id + 1 : 1;
}; };
@@ -87,8 +89,9 @@ export const newEntry = async (formData, username) => {
const tableName = confirmation.tableName; const tableName = confirmation.tableName;
const [result] = await pool.query( const [result] = await pool.query(
`INSERT INTO ${tableName} (Vorname, Nachname, EMail, Telefonnummer, Lose, Firmenname, Vorname_Geschaeftlich, Nachname_Geschaeftlich, EMail_Geschaeftlich, Telefonnummer_Geschaeftlich, Strasse_Hausnr, Plz_Ort, Zahlungsmethode) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, `INSERT INTO ?? (Vorname, Nachname, EMail, Telefonnummer, Lose, Firmenname, Vorname_Geschaeftlich, Nachname_Geschaeftlich, EMail_Geschaeftlich, Telefonnummer_Geschaeftlich, Strasse_Hausnr, Plz_Ort, Zahlungsmethode) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[ [
tableName,
formData.firstName, formData.firstName,
formData.lastName, formData.lastName,
formData.email, formData.email,
@@ -102,7 +105,7 @@ export const newEntry = async (formData, username) => {
formData.street, formData.street,
formData.postalCode, formData.postalCode,
formData.paymentMethod, formData.paymentMethod,
] ],
); );
return { success: true, insertId: result.insertId }; return { success: true, insertId: result.insertId };
+1
View File
@@ -6,6 +6,7 @@ CREATE TABLE users (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); );
/* This scheme does not have to be implemented manually. It always will be generated by the backend */
CREATE TABLE xx_DD_MM_YYYY ( CREATE TABLE xx_DD_MM_YYYY (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
Vorname VARCHAR(100) NOT NULL, Vorname VARCHAR(100) NOT NULL,
-115
View File
@@ -1,115 +0,0 @@
services:
frontend:
container_name: ca-lose-frontend
hostname: lose-verkaufen
build: ./frontend
networks:
ca-lose-internal:
ipv4_address: 172.25.0.2
proxynet:
ipv4_address: 172.20.0.61
restart: unless-stopped
backend:
container_name: ca-lose-backend
hostname: backend
build: ./backend
environment:
NODE_ENV: production
DB_HOST: ca-lose-mysql
DB_USER: root
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: ca_lose
depends_on:
- database
networks:
ca-lose-internal:
ipv4_address: 172.25.0.3
restart: unless-stopped
database:
container_name: ca-lose-mysql
hostname: database
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: ca_lose
TZ: Europe/Berlin
volumes:
- ca-lose_mysql:/var/lib/mysql
- ./mysql-timezone.cnf:/etc/mysql/conf.d/timezone.cnf:ro
networks:
ca-lose-internal:
ipv4_address: 172.25.0.4
proxynet:
ipv4_address: 172.20.0.60
# DNS Server für Hostname-Auflösung innerhalb des VPN
dnsmasq:
container_name: ca-lose-dns
image: andyshinn/dnsmasq:latest
restart: unless-stopped
cap_add:
- NET_ADMIN
command: >
--no-daemon
--log-queries
--address=/lose-verkaufen/172.25.0.2
--address=/frontend/172.25.0.2
--address=/backend/172.25.0.3
--address=/database/172.25.0.4
--address=/wg-admin/172.25.0.10
networks:
ca-lose-internal:
ipv4_address: 172.25.0.53
# WireGuard VPN mit Web-UI (wg-easy)
wireguard:
image: ghcr.io/wg-easy/wg-easy:latest
container_name: ca-lose-wireguard
cap_add:
- NET_ADMIN
- SYS_MODULE
environment:
LANG: de
WG_HOST: dus3.the1s.de
WG_PORT: "51830"
PORT: "51821"
WG_DEFAULT_ADDRESS: 10.14.14.x
WG_DEFAULT_DNS: "172.25.0.53"
WG_ALLOWED_IPS: 172.25.0.0/24
WG_PERSISTENT_KEEPALIVE: "25"
WG_POST_UP: "iptables -t nat -A POSTROUTING -s 10.14.14.0/24 -o eth0 -j MASQUERADE; iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT; iptables -A FORWARD -i eth0 -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -A FORWARD -i wg0 -d 172.25.0.2 -j ACCEPT; iptables -A FORWARD -i wg0 -d 172.25.0.53 -j ACCEPT; iptables -A FORWARD -i wg0 -j DROP"
WG_POST_DOWN: "iptables -t nat -D POSTROUTING -s 10.14.14.0/24 -o eth0 -j MASQUERADE; iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT; iptables -D FORWARD -i eth0 -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT; iptables -D FORWARD -i wg0 -d 172.25.0.2 -j ACCEPT; iptables -D FORWARD -i wg0 -d 172.25.0.53 -j ACCEPT; iptables -D FORWARD -i wg0 -j DROP"
volumes:
- wireguard-data:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51830:51830/udp"
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
restart: unless-stopped
depends_on:
- dnsmasq
- frontend
networks:
ca-lose-internal:
ipv4_address: 172.25.0.10
proxynet:
ipv4_address: 172.20.0.50
volumes:
ca-lose_mysql:
wireguard-data:
networks:
ca-lose-internal:
driver: bridge
ipam:
config:
- subnet: 172.25.0.0/24
gateway: 172.25.0.1
proxynet:
external: true
+75 -13
View File
@@ -1,16 +1,19 @@
services: services:
# frontend: frontend:
# container_name: ca-lose-frontend container_name: ca-lose-frontend
# build: ./frontend hostname: lose-verkaufen
# ports: build: ./frontend
# - "8002:80" depends_on:
# restart: unless-stopped - backend
networks:
ca-lose-internal:
ipv4_address: 172.25.0.2
restart: unless-stopped
backend: backend:
container_name: ca-lose-backend container_name: ca-lose-backend
hostname: backend
build: ./backend build: ./backend
ports:
- "8004:8004"
environment: environment:
NODE_ENV: production NODE_ENV: production
DB_HOST: ca-lose-mysql DB_HOST: ca-lose-mysql
@@ -19,21 +22,80 @@ services:
DB_NAME: ca_lose DB_NAME: ca_lose
depends_on: depends_on:
- database - database
networks:
ca-lose-internal:
ipv4_address: 172.25.0.3
restart: unless-stopped restart: unless-stopped
database: database:
container_name: ca-lose-mysql container_name: ca-lose-mysql
hostname: database
image: mysql:8.0 image: mysql:8.0
restart: unless-stopped restart: unless-stopped
ports:
- "3311:3306"
environment: environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: ca_lose MYSQL_DATABASE: ca_lose
TZ: Europe/Berlin TZ: Europe/Berlin
volumes: volumes:
- ca-lose_mysql:/var/lib/mysql - ../docker/volumes/ca-lose_mysql:/var/lib/mysql
- ./mysql-timezone.cnf:/etc/mysql/conf.d/timezone.cnf:ro - ./mysql-timezone.cnf:/etc/mysql/conf.d/timezone.cnf:ro
networks:
ca-lose-internal:
ipv4_address: 172.25.0.4
volumes: # DNS Server für Hostname-Auflösung innerhalb des VPN
ca-lose_mysql: dnsmasq:
container_name: ca-lose-dns
image: andyshinn/dnsmasq:latest
restart: unless-stopped
cap_add:
- NET_ADMIN
command: >
--no-daemon
--log-queries
--address=/lose-verkaufen/172.25.0.2
--address=/frontend/172.25.0.2
--address=/backend/172.25.0.3
--address=/database/172.25.0.4
--address=/wireguard/172.25.0.6
networks:
ca-lose-internal:
ipv4_address: 172.25.0.5
# WireGuard VPN mit Web-UI (wg-easy)
wireguard:
image: ghcr.io/wg-easy/wg-easy:latest
container_name: ca-lose-wireguard
cap_add:
- NET_ADMIN
- SYS_MODULE
environment:
LANG: de
WG_HOST: dus3.the1s.de
WG_PORT: "51830"
WG_DEFAULT_DNS: "172.25.0.5"
PORT: "80" # Web-UI Port
PASSWORD_HASH: ${WIREGUARD_PASSWORD_HASH}
volumes:
- ../docker/volumes/ca-lose-wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51830:51830/udp"
# - "51831:80/tcp" # only for short configuration access - remove in production - external: 51831 internal: 80
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
restart: unless-stopped
depends_on:
- dnsmasq
networks:
ca-lose-internal:
ipv4_address: 172.25.0.6
networks:
ca-lose-internal:
driver: bridge
ipam:
config:
- subnet: 172.25.0.0/24
gateway: 172.25.0.1
+4
View File
@@ -0,0 +1,4 @@
export const API_BASE =
(import.meta as any).env?.VITE_BACKEND_URL ||
import.meta.env.VITE_BACKEND_URL ||
"http://localhost:8004";
+31 -3
View File
@@ -10,12 +10,15 @@ import {
Box, Box,
Paper, Paper,
Typography, Typography,
IconButton,
} from "@mui/material"; } from "@mui/material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { submitFormData } from "../utils/sender"; import { submitFormData } from "../utils/sender";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import * as React from "react"; import * as React from "react";
import TranslateIcon from "@mui/icons-material/Translate";
import { API_BASE } from "../config/api.config";
interface Message { interface Message {
type: "error" | "info" | "success" | "warning"; type: "error" | "info" | "success" | "warning";
@@ -24,7 +27,7 @@ interface Message {
} }
export const MainForm = () => { export const MainForm = () => {
const { t } = useTranslation(); const { t, i18n } = useTranslation();
const [invoice, setInvoice] = useState(false); const [invoice, setInvoice] = useState(false);
const [msg, setMsg] = useState<Message | null>(null); const [msg, setMsg] = useState<Message | null>(null);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@@ -47,11 +50,27 @@ export const MainForm = () => {
const [users, setUsers] = useState<string[]>([]); const [users, setUsers] = useState<string[]>([]);
const [selectedUser, setSelectedUser] = useState<string | null>(null); const [selectedUser, setSelectedUser] = useState<string | null>(null);
const changeTranslation = () => {
const clientLng = i18n.language;
if (clientLng === "en") {
i18n.changeLanguage("de");
} else if (clientLng === "de") {
i18n.changeLanguage("en");
} else {
setMsg({
type: "error",
headline: "Error",
text: "Cannot change langugage.",
});
}
};
useEffect(() => { useEffect(() => {
// Fetch user data or any other data needed for the form // Fetch user data or any other data needed for the form
try { try {
const fetchUsers = async () => { const fetchUsers = async () => {
const response = await fetch("http://localhost:8004/default/users"); const response = await fetch(`${API_BASE}/default/users`);
const data = await response.json(); const data = await response.json();
setUsers(data.users); setUsers(data.users);
}; };
@@ -80,7 +99,7 @@ export const MainForm = () => {
const confirmUser = async (selectedUser: string) => { const confirmUser = async (selectedUser: string) => {
try { try {
const response = await fetch( const response = await fetch(
`http://localhost:8004/default/confirm-user?username=${selectedUser}`, `${API_BASE}/default/confirm-user?username=${selectedUser}`,
); );
const data = await response.json(); const data = await response.json();
setNextID(data.nextID); setNextID(data.nextID);
@@ -129,6 +148,7 @@ export const MainForm = () => {
elevation={6} elevation={6}
className="w-full rounded-2xl" className="w-full rounded-2xl"
sx={{ sx={{
position: "relative",
backgroundColor: "#fff", backgroundColor: "#fff",
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)", boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
width: "100%", width: "100%",
@@ -143,6 +163,14 @@ export const MainForm = () => {
}, },
}} }}
> >
<Box sx={{ position: "absolute", top: 12, right: 12 }}>
<IconButton
onClick={() => changeTranslation()}
aria-label="translate"
>
<TranslateIcon />
</IconButton>
</Box>
<form <form
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
+3 -1
View File
@@ -1,3 +1,5 @@
import { API_BASE } from "../config/api.config";
interface FormData { interface FormData {
firstName: string; firstName: string;
lastName: string; lastName: string;
@@ -18,7 +20,7 @@ export const submitFormData = async (data: FormData, username: string) => {
console.log(data); console.log(data);
try { try {
const response = await fetch( const response = await fetch(
`http://localhost:8004/default/new-entry?username=${username}`, `${API_BASE}/default/new-entry?username=${username}`,
{ {
method: "POST", method: "POST",
headers: { headers: {