feat: initialize backend and frontend structure with Docker support
- Added backend package.json with dependencies for Express, MySQL, and EJS. - Created server.js for backend server setup with basic routing and error handling. - Added EJS view for the index page. - Set up Docker Compose configuration for frontend, backend, and MySQL database services. - Created Dockerfile for frontend build process using Node and Nginx. - Configured Nginx for serving frontend application. - Implemented i18n setup for internationalization with English and German language support. - Added localization files for English and German languages.
This commit is contained in:
122
.gitignore
vendored
122
.gitignore
vendored
@@ -5,7 +5,8 @@
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
@@ -40,3 +41,122 @@ Temporary Items
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
# 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?
|
||||
._*
|
||||
.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)
|
||||
secrets/
|
||||
keys/
|
||||
|
||||
ToDo.txt
|
||||
|
||||
|
||||
# only in development branch
|
||||
next-env.d.ts
|
||||
12
backend/Dockerfile
Normal file
12
backend/Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /backend
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8004
|
||||
CMD ["npm", "start"]
|
||||
1045
backend/package-lock.json
generated
Normal file
1045
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
backend/package.json
Normal file
19
backend/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.3",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^5.2.1",
|
||||
"mysql2": "^3.16.0"
|
||||
}
|
||||
}
|
||||
0
backend/routes/default/frontend.data.js
Normal file
0
backend/routes/default/frontend.data.js
Normal file
0
backend/routes/default/frontend.route.js
Normal file
0
backend/routes/default/frontend.route.js
Normal file
24
backend/server.js
Normal file
24
backend/server.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT;
|
||||
app.set("view engine", "ejs");
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.render("index");
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on http://localhost:${PORT}`);
|
||||
});
|
||||
|
||||
// error handling code
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).send("Something broke!");
|
||||
});
|
||||
11
backend/views/index.ejs
Normal file
11
backend/views/index.ejs
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>backend</title>
|
||||
</head>
|
||||
<body>
|
||||
backend
|
||||
</body>
|
||||
</html>
|
||||
37
docker-compose.yml
Normal file
37
docker-compose.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
services:
|
||||
frontend:
|
||||
container_name: ca-lose-frontend
|
||||
build: ./frontend
|
||||
ports:
|
||||
- "8002:80"
|
||||
restart: unless-stopped
|
||||
|
||||
backend:
|
||||
container_name: ca-lose-backend
|
||||
build: ./backend
|
||||
ports:
|
||||
- "8004:8004"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
DB_HOST: ca-lose
|
||||
DB_USER: root
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
DB_NAME: ca-lose
|
||||
depends_on:
|
||||
- ca-lose
|
||||
restart: unless-stopped
|
||||
|
||||
database:
|
||||
container_name: ca-lose
|
||||
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
|
||||
|
||||
volumes:
|
||||
ca-lose_mysql:
|
||||
19
frontend/Dockerfile
Normal file
19
frontend/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
FROM node:22-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine AS runner
|
||||
|
||||
WORKDIR /usr/share/nginx/html
|
||||
COPY --from=builder /app/dist .
|
||||
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
18
frontend/nginx.conf
Normal file
18
frontend/nginx.conf
Normal file
@@ -0,0 +1,18 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location ~* \.(?:js|mjs|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
||||
expires 1y;
|
||||
access_log off;
|
||||
add_header Cache-Control "public, immutable";
|
||||
try_files $uri =404;
|
||||
}
|
||||
}
|
||||
113
frontend/package-lock.json
generated
113
frontend/package-lock.json
generated
@@ -14,8 +14,10 @@
|
||||
"@mui/icons-material": "^7.3.6",
|
||||
"@mui/material": "^7.3.6",
|
||||
"@mui/styled-engine-sc": "^7.3.6",
|
||||
"js-cookie": "^3.0.5",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-i18next": "^16.2.0",
|
||||
"react-router-dom": "^7.11.0",
|
||||
"styled-components": "^6.1.19"
|
||||
},
|
||||
@@ -2997,6 +2999,47 @@
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "25.7.4",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.4.tgz",
|
||||
"integrity": "sha512-hRkpEblXXcXSNbw8mBNq9042OEetgyB/ahc/X17uV/khPwzV+uB8RHceHh3qavyrkPJvmXFKXME2Sy1E0KjAfw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
@@ -3084,6 +3127,15 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/js-cookie": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
|
||||
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -3517,6 +3569,33 @@
|
||||
"react": "^19.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "16.5.2",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.5.2.tgz",
|
||||
"integrity": "sha512-GG/SBVxx9dvrO1uCs8VYdKfOP8NEBUhNP+2VDQLCifRJ8DL1qPq296k2ACNGyZMDe7iyIlz/LMJTQOs8HXSRvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"html-parse-stringify": "^3.0.1",
|
||||
"use-sync-external-store": "^1.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 25.6.2",
|
||||
"react": ">= 16.8.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz",
|
||||
@@ -3534,9 +3613,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz",
|
||||
"integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==",
|
||||
"version": "7.12.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz",
|
||||
"integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
@@ -3556,12 +3635,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.11.0.tgz",
|
||||
"integrity": "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==",
|
||||
"version": "7.12.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz",
|
||||
"integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.11.0"
|
||||
"react-router": "7.12.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -3913,7 +3992,7 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -3995,6 +4074,15 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz",
|
||||
@@ -4070,6 +4158,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
"@mui/icons-material": "^7.3.6",
|
||||
"@mui/material": "^7.3.6",
|
||||
"@mui/styled-engine-sc": "^7.3.6",
|
||||
"js-cookie": "^3.0.5",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-i18next": "^16.2.0",
|
||||
"react-router-dom": "^7.11.0",
|
||||
"styled-components": "^6.1.19"
|
||||
},
|
||||
|
||||
34
frontend/src/utils/i18n/index.ts
Normal file
34
frontend/src/utils/i18n/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
import enLang from "./locales/en/en.json";
|
||||
import deLang from "./locales/de/de.json";
|
||||
|
||||
// the translations
|
||||
// (tip move them in a JSON file and import them,
|
||||
// or even better, manage them separated from your code: https://react.i18next.com/guides/multiple-translation-files)
|
||||
const resources = {
|
||||
en: {
|
||||
translation: enLang,
|
||||
},
|
||||
de: {
|
||||
translation: deLang,
|
||||
},
|
||||
};
|
||||
|
||||
i18n
|
||||
.use(initReactI18next) // passes i18n down to react-i18next
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: "en", // use en if detected lng is not available
|
||||
lng: Cookies.get("language") || "en", // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
|
||||
// you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
|
||||
// if you're using a language detector, do not define the lng option
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // react already safes from xss
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
0
frontend/src/utils/i18n/locales/de/de.json
Normal file
0
frontend/src/utils/i18n/locales/de/de.json
Normal file
0
frontend/src/utils/i18n/locales/en/en.json
Normal file
0
frontend/src/utils/i18n/locales/en/en.json
Normal file
Reference in New Issue
Block a user