Merge unrelated histories

This commit is contained in:
2024-02-09 12:51:42 +01:00
parent 04077edebe
commit 77e2bfd1e1
38 changed files with 33681 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
**/node_modules

47
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,47 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Backend starten",
"skipFiles": [
"<node_internals>/**",
"node_modules/**"
],
"cwd": "${workspaceFolder}/backend",
"internalConsoleOptions": "openOnSessionStart",
"env": {
"LOCAL_MODE": "true"
},
"args": [
"node_modules/ts-node/dist/bin.js",
"--project",
"${workspaceFolder}/backend/tsconfig.json",
"-r",
"tsconfig-paths/register",
"src/index.ts"
],
"runtimeArgs": [
"-r",
"ts-node/register",
"--unhandled-rejections=strict",
"--nolazy"
],
"program": "${workspaceFolder}/backend/src/index.ts"
},
{
"name": "Debug Frontend",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/frontend/src",
"preLaunchTask": "Start Frontend",
"postDebugTask": "Terminate All Tasks",
"sourceMaps": true,
"skipFiles": ["node_modules/**"],
"runtimeExecutable": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
}
]
}

73
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,73 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Start Backend",
"type": "shell",
"command": "npx ts-node src/index.ts",
"options": {
"cwd": "${workspaceFolder}/backend"
},
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": {
"regexp": "^.*$",
"file": 1,
"location": 2,
"message": 3
},
"background": {
"activeOnStart": true,
"beginsPattern": "^.*$",
"endsPattern": "^Server läuft auf Port.*$"
}
}
},
{
"label": "Start Frontend",
"dependsOn": ["Start Backend"],
"type": "shell",
"command": "BROWSER=none npm start",
"options": { "cwd": "${workspaceFolder}/frontend" },
"isBackground": true,
"problemMatcher": {
"owner": "javascript",
"pattern": {
"regexp": "^.*$",
"file": 1,
"location": 2,
"message": 3
},
"background": {
"activeOnStart": true,
"beginsPattern": "^.*$",
"endsPattern": "Compiled successfully"
}
}
},
{
"type": "shell",
"label": "Docker Build",
"command": "docker build --build-arg NODE_VERSION=$(node -v) -t ${workspaceFolderBasename} .",
"group": "build"
},
{
"label": "Terminate All Tasks",
"type": "shell",
"command": "echo 'Terminating all tasks'",
// Windows-spezifischer Befehl könnte anders sein, z.B. "command": "echo 'Terminating all tasks'"
"problemMatcher": []
}
],
"inputs": [
{
"id": "terminate",
"type": "command",
"command": "workbench.action.tasks.terminate",
"args": "terminateAll"
}
]
}

View File

@@ -0,0 +1,31 @@
server {
listen 80;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
location / {
if ( $uri = '/index.html' ) {
add_header Cache-Control no-store always;
}
try_files $uri $uri/ /index.html;
}
# Do not cache sw.js, required for offline-first updates.
location /sw.js {
add_header Cache-Control "no-cache";
proxy_cache_bypass $http_pragma;
proxy_cache_revalidate on;
expires off;
access_log off;
}
location /api {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:507;
proxy_set_header Host $http_host;
proxy_cache_bypass $http_upgrade;
proxy_redirect off;
}
}

20
Docker/docker-compose.yml Normal file
View File

@@ -0,0 +1,20 @@
version: "3.3"
services:
####:
image: ####
container_name: ##
restart: unless-stopped
networks:
- Backend
labels:
- "traefik.enable=true"
- "traefik.http.routers.###.rule=Host(`###`)"
- "traefik.http.routers.###.entrypoints=websecure"
- "traefik.http.services.###.loadbalancer.server.port=80"
- "traefik.http.routers.###.service=###"
- "traefik.http.routers.###.tls.certresolver=acme"
#Volume binding nicht vergessen
networks:
Backend:
external: true

18
Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
FROM node:18.13.0
# 18.13.0 enstpricht 108
ENV TZ="Europe/Berlin"
RUN date
RUN apt update
RUN apt install curl gnupg2 ca-certificates lsb-release sudo -y
RUN apt install -y nginx
COPY ./backend /app
COPY ./Docker/NginxProxy/default.conf /etc/nginx/sites-available/default
COPY start.sh /start.sh
COPY ./frontend/build /var/www/html
WORKDIR /app
RUN npm install
RUN echo "Port=507" >> .env
RUN echo "NODE_ENV=production" >> .env
ENTRYPOINT [ "/start.sh" ]

View File

@@ -0,0 +1,11 @@
//Standart Einstiegspunkt für die App"
const app = require('express')();
require('dotenv').config();
const NODE_ENV = process.env.NODE_ENV || 'dev';
const Port = NODE_ENV === "production" ? parseInt(process.env.Port) || 507 : 2210
app.listen(Port , () => {
console.log(`Server läuft auf Port ${Port}`)
})

View File

@@ -0,0 +1,64 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@mui/icons-material": "^5.11.0",
"@mui/material": "^5.11.7",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.3.1",
"compress-create-react-app": "^1.3.1",
"moment-timezone": "^0.5.40",
"preval.macro": "^5.0.0",
"react": "^18.2.0",
"react-bootstrap": "^2.7.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"react-tabs": "^6.0.0",
"uuid": "^9.0.0",
"web-vitals": "^2.1.4",
"workbox-background-sync": "^6.5.4",
"workbox-broadcast-update": "^6.5.4",
"workbox-cacheable-response": "^6.5.4",
"workbox-core": "^6.5.4",
"workbox-expiration": "^6.5.4",
"workbox-google-analytics": "^6.5.4",
"workbox-navigation-preload": "^6.5.4",
"workbox-precaching": "^6.5.4",
"workbox-range-requests": "^6.5.4",
"workbox-routing": "^6.5.4",
"workbox-strategies": "^6.5.4",
"workbox-streams": "^6.5.4"
},
"scripts": {
"start": "BROWSER='none' react-scripts start",
"start:browser": "BROWSER='google chrome' react-scripts start",
"build": "react-scripts build",
"build:compress": "react-scripts build && compress-cra",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"proxy": "http://localhost:2210"
}

1651
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

7
backend/package.json Normal file
View File

@@ -0,0 +1,7 @@
{
"scripts": {
"start": "ts-node src/index.ts",
"build": "tsc",
"dev": "nodemon src/index.ts"
}
}

13
backend/src/index.ts Normal file
View File

@@ -0,0 +1,13 @@
// Standard-Einstiegspunkt für die App
import express from 'express';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
const NODE_ENV = process.env.NODE_ENV || 'dev';
const Port = NODE_ENV === "production" ? parseInt(process.env.PORT || '') || 507 : 2210;
app.listen(Port, () => {
console.log(`Server läuft auf Port ${Port}`);
});

14
backend/tsconfig.json Normal file
View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

23
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

30729
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

55
frontend/package.json Normal file
View File

@@ -0,0 +1,55 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^17.0.45",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4",
"workbox-background-sync": "^6.6.0",
"workbox-broadcast-update": "^6.6.0",
"workbox-cacheable-response": "^6.6.0",
"workbox-core": "^6.6.0",
"workbox-expiration": "^6.6.0",
"workbox-google-analytics": "^6.6.0",
"workbox-navigation-preload": "^6.6.0",
"workbox-precaching": "^6.6.0",
"workbox-range-requests": "^6.6.0",
"workbox-routing": "^6.6.0",
"workbox-strategies": "^6.6.0",
"workbox-streams": "^6.6.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

BIN
frontend/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
frontend/public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
frontend/public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

38
frontend/src/App.css Normal file
View File

@@ -0,0 +1,38 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@@ -0,0 +1,9 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

26
frontend/src/App.tsx Normal file
View File

@@ -0,0 +1,26 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;

13
frontend/src/index.css Normal file
View File

@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

25
frontend/src/index.tsx Normal file
View File

@@ -0,0 +1,25 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import * as serviceWorkerRegistration from './serviceWorkerRegistration';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://cra.link/PWA
serviceWorkerRegistration.unregister();
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

7
frontend/src/logo.svg Normal file
View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

1
frontend/src/react-app-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@@ -0,0 +1,15 @@
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@@ -0,0 +1,80 @@
/// <reference lib="webworker" />
/* eslint-disable no-restricted-globals */
// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
declare const self: ServiceWorkerGlobalScope;
clientsClaim();
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST);
// Set up App Shell-style routing, so that all navigation requests
// are fulfilled with your index.html shell. Learn more at
// https://developers.google.com/web/fundamentals/architecture/app-shell
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }: { request: Request; url: URL }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false;
}
// If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false;
}
// If this looks like a URL for a resource, because it contains
// a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false;
}
// Return true to signal that we want to use the handler.
return true;
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
);
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'),
// Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
);
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
// Any other custom service worker logic can go here.

View File

@@ -0,0 +1,142 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://cra.link/PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
);
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://cra.link/PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://cra.link/PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch((error) => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log('No internet connection found. App is running in offline mode.');
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then((registration) => {
registration.unregister();
})
.catch((error) => {
console.error(error.message);
});
}
}

View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

26
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}

68
init.sh Normal file
View File

@@ -0,0 +1,68 @@
#!/bin/sh
# Bestimme das aktuelle Verzeichnis
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# Entferne vorhandenes Git-Repository, falls vorhanden
rm -rf .git
# Erstelle eine neue React-App mit dem PWA-Template und TypeScript
npx create-react-app frontend --template cra-template-pwa-typescript
# Installiere zusätzliche Abhängigkeiten im Frontend-Verzeichnis
npm install --prefix frontend
# Erstelle das Backend-Verzeichnis und dessen src-Ordner
mkdir -p backend/src
# Initialisiere ein neues npm-Projekt im Backend-Verzeichnis
npm init -y --prefix backend
# Installiere die notwendigen Backend-Abhängigkeiten inklusive TypeScript und Typendefinitionen
npm i --prefix backend express dotenv @types/express @types/dotenv typescript ts-node
# Erstelle eine TypeScript-Konfigurationsdatei für das Backend
echo '{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}' > backend/tsconfig.json
# Füge ein Start-Skript zum Backend package.json hinzu
echo '{
"scripts": {
"start": "ts-node src/index.ts",
"build": "tsc",
"dev": "nodemon src/index.ts"
}
}' > backend/package.json
# Erstelle die index.ts Datei im Backend/src-Verzeichnis
cat <<EOT > backend/src/index.ts
// Standard-Einstiegspunkt für die App
import express from 'express';
import dotenv from 'dotenv';
dotenv.config();
const app = express();
const NODE_ENV = process.env.NODE_ENV || 'dev';
const Port = NODE_ENV === "production" ? parseInt(process.env.PORT || '') || 507 : 2210;
app.listen(Port, () => {
console.log(\`Server läuft auf Port \${Port}\`);
});
EOT
# Hinweis: Dieses Skript erstellt eine neue index.ts Datei im Backend/src-Ordner und fügt
# die notwendigen Skripte und Abhängigkeiten hinzu. Stelle sicher, dass deine Umgebungsvariablen
# und jeglicher anderer benötigter Code korrekt in die index.ts Datei integriert sind.

12
package.json Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "templateprojekt",
"version": "1.0.0",
"description": "",
"keywords": [],
"author": "Christian Schindler",
"license": "ISC",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
}

3
start.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
nginx -g "daemon off;"&
node /app/index.js

386
template.sh Normal file
View File

@@ -0,0 +1,386 @@
#!/bin/bash
projekt_folder="/workspace/Projekte/Node+React"
# Prüfe, ob ein Argument übergeben wurde.
if [ -z "$1" ]; then
echo -n "Bitte geben Sie den Namen des zu erstellenden Ordners ein: "
read project_name
else
project_name="$1"
fi
# Überprüfen, ob der Ordner bereits existiert.
if [ -d "$project_name" ]; then
echo "Ein Ordner mit dem Namen '$project_name' existiert bereits."
exit 1
fi
# Überprüfen, ob der Ordnername mit Docker-Tags kompatibel ist.
if [[ ! "$project_name" =~ ^[a-z0-9]([a-z0-9._-]{0,126}[a-z0-9])?$ ]]; then
echo "Der Ordnername '$project_name' ist nicht mit Docker-Tags kompatibel."
exit 1
fi
# Überprüfen, ob der Ordnername einen gültigen Domainnamen darstellt.
if [[ "$project_name" =~ _ ]]; then
echo "Der Ordnername '$project_name' enthält einen Unterstrich, der in Domainnamen nicht zulässig ist."
exit 1
fi
# Frage nach der Version (1 für light oder 2 für full)
if [ -z "$2" ]; then
echo "Wählen Sie eine Version:"
echo "1. Light"
echo "2. Full"
read -p "Geben Sie die Nummer der gewünschten Version ein (1 oder 2): " version_choice
else
version_choice="$2"
fi
# Überprüfe die Auswahl und setze die Version entsprechend.
if [ "$version_choice" == "1" ]; then
version="light"
elif [ "$version_choice" == "2" ]; then
version="full"
else
echo "Ungültige Auswahl. Bitte wählen Sie 1 für Light oder 2 für Full."
exit 1
fi
cd "$projekt_folder"
# Erstellung des Projekthauptordners und der Unterordner "frontend" und "backend".
mkdir "$project_name"
cd "$project_name"
mkdir frontend backend
# Einrichtung einer React-App im "frontend"-Ordner.
cd frontend
npx create-react-app my_app --template cra-template-pwa
cd my_app
# Einrichtung von Axios und React-Tabs.
npm install axios react-tabs
# Anpassung des "package.json"-Files.
sed -i 's/"start": "react-scripts start"/"start": "PORT=3033 react-scripts start"/' package.json
sed -i '$!b;n;s/}/,\n "proxy": "http:\/\/127.0.0.1:507"\n}/' package.json
cd ../../backend
# Einrichtung einer Node.js-App mit Express.
npm init -y
npm install express
# Rückkehr zum Hauptverzeichnis.
cd ..
# Erstellung der ".dockerignore"-Datei und einer Dummy-Datei.
echo "backend/node_modules" > .dockerignore
echo "console.log('fertig');" > dummy.js
if [ "$version" == "full" ]; then
# Erstellung der index.js-Datei für den Express-Server (Full Version).
cat <<EOT >> backend/index.js
const express = require('express');
const app = express();
const PORT = 507;
app.listen(PORT, () => {
console.log('Server is running on port ' + PORT);
});
EOT
# Dockerfile für die Full-Version.
cat <<EOT >> Dockerfile
# Bauphase für React-App
FROM node:latest AS build
ARG NODE_VERSION
WORKDIR /app
COPY ./frontend ./frontend
WORKDIR /app/frontend/my_app
RUN npm install -g n && \
n \$NODE_VERSION
RUN npm install
RUN npm run build
# Produktionsphase
FROM nginx:latest
ARG NODE_VERSION
# Kopieren des React-Builds
COPY --from=build /app/frontend/my_app/build /var/www/html
# Backend kopieren
WORKDIR /usr/src/app
COPY ./backend ./backend
# Node.js und npm manuell installieren
RUN apt-get update && \
apt-get install -y curl xz-utils nodejs npm && \
rm -rf /var/lib/apt/lists/*
RUN npm install -g n && \
n \$NODE_VERSION
COPY default.conf /etc/nginx/conf.d/default.conf
WORKDIR /usr/src/app/backend
# npm-Abhängigkeiten für das Backend installieren
RUN npm install
# Startkommando
CMD sh -c "nginx -g 'daemon off;' & node index.js"
EOT
# Erstellung der default.conf für den Reverse-Proxy.
cat <<EOT >> default.conf
server {
listen 80;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
location / {
if ( \$uri = '/index.html' ) {
add_header Cache-Control no-store always;
}
try_files \$uri \$uri/ /index.html;
}
# Do not cache sw.js, required for offline-first updates.
location /sw.js {
add_header Cache-Control "no-cache";
proxy_cache_bypass \$http_pragma;
proxy_cache_revalidate on;
expires off;
access_log off;
}
location /api {
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:507;
proxy_set_header Host \$http_host;
proxy_cache_bypass \$http_upgrade;
proxy_redirect off;
}
}
EOT
else
# Erstellung der index.js-Datei für den Express-Server (Light Version).
cat <<EOT >> backend/index.js
const express = require('express');
const app = express();
const PORT = 507;
const fs = require('fs');
const path = require('path');
// Prüfe ob es den Ordner "React" gibt
if (fs.existsSync(path.join(__dirname, 'React'))) {
app.use(express.static(path.join(__dirname, 'React')));
}
app.listen(PORT, () => {
console.log('Server is running on port ' + PORT);
});
EOT
# Dockerfile für die Light-Version.
cat <<EOT >> Dockerfile
# Bauphase für React-App
FROM node:latest AS build
ARG NODE_VERSION
WORKDIR /app
COPY ./frontend ./frontend
WORKDIR /app/frontend/my_app
RUN npm install -g n && \
n \$NODE_VERSION
RUN npm install
RUN npm run build
# Produktionsphase
FROM node:latest
ARG NODE_VERSION
# Backend kopieren
WORKDIR /usr/src/app
COPY ./backend ./backend
# Kopieren des React-Builds
COPY --from=build /app/frontend/my_app/build /usr/src/app/backend/React
WORKDIR /usr/src/app/backend
# npm-Abhängigkeiten für das Backend installieren
RUN npm install -g n && \
n \$NODE_VERSION
RUN npm install
# Startkommando
CMD ["sh", "-c", "node index.js"]
EOT
fi
# Einrichtung des ".vscode"-Ordners.
mkdir .vscode
# Anpassung der tasks.json für die Aufgaben.
cat <<EOT >> .vscode/tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "Terminate All Tasks",
"command": "echo \${input:terminate}",
"type": "shell",
"problemMatcher": []
},
{
"label": "Frontend",
"type": "npm",
"presentation": {
"group": "React"
},
"script": "start",
"group": "build",
"isBackground": true,
"path": "./frontend/my_app",
"problemMatcher": [
{
"pattern": [
{
"regexp": ".",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"activeOnStart": true,
"beginsPattern": ".",
"endsPattern": "webpack compiled successfully"
}
}
]
},
{
"label": "Backend",
"type": "shell",
"isBackground": true,
"command": "node index.js",
"options": {
"cwd": "\${workspaceFolder}/backend"
},
"presentation": {
"group": "React"
},
"problemMatcher": [
{
"pattern": [
{
"regexp": ".",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"activeOnStart": true,
"beginsPattern": ".",
"endsPattern": "."
}
}
]
},
{
"label": "Server",
"dependsOn": [
"Backend",
"Frontend"
]
},
{
"type": "shell",
"label": "Docker Build",
"command": "docker build --build-arg NODE_VERSION=\$(node -v) -t \${workspaceFolderBasename} .",
"group": "build"
}
],
"inputs": [
{
"id": "terminate",
"type": "command",
"command": "workbench.action.tasks.terminate",
"args": "terminateAll"
}
]
}
EOT
# Anpassung der launch.json für Debugging.
cat <<EOT >> .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"cwd": "\${workspaceFolder}/backend",
"name": "Backend starten",
"skipFiles": [
"<node_internals>/**"
],
"program": "index.js"
},
{
"name": "React + Backend starten",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3033",
"webRoot": "\${workspaceFolder}/frontend/my_app/src",
"preLaunchTask": "Server",
"postDebugTask": "Terminate All Tasks"
},
{
"name": "build Dockercontainer",
"type": "node",
"request": "launch",
"program": "\${workspaceFolder}/dummy.js",
"preLaunchTask": "Docker Build"
}
]
}
EOT
# Erstellung der docker-compose.yml Datei
cat <<EOT >> docker-compose.yml
version: '3'
services:
$project_name:
image: $project_name:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.$project_name.rule=Host(\`${project_name}.csnetworkx.dev\`)"
- "traefik.http.routers.$project_name.entrypoints=publicwebsecure"
- "traefik.http.routers.$project_name.tls.certresolver=myresolver"
- "traefik.http.routers.$project_name.service=$project_name"
EOT
if [ "$version" == "light" ]; then
echo " - \"traefik.http.services.$project_name.loadbalancer.server.port=507\"" >> docker-compose.yml
else
echo " - \"traefik.http.services.$project_name.loadbalancer.server.port=80\"" >> docker-compose.yml
fi
cat <<EOT >> docker-compose.yml
networks:
Backend:
external: true
EOT