MCS Lose
A small full-stack demo app to manage "Lose" entries (Losnummer records) using a React + TypeScript frontend, an Express (Node) backend, and a MySQL database. The project is containerised with Docker Compose for easy local development.
Quick overview
- Backend: Express.js (ES Modules), serves JSON APIs and EJS root view.
- Frontend: React + TypeScript + Vite (UI for importing CSVs and managing entries).
- Database: MySQL 8.0 with a simple schema (
lose
andadmin_user
). - Orchestration:
docker-compose.yml
to run backend and MySQL (frontend is prepared but commented out in compose).
Goals
- Import and manage losnummer (ticket) entries.
- Allow admins to login and perform protected operations (create, delete, update rows).
- Keep the setup easy to run locally via Docker.
Prerequisites
- Docker & Docker Compose (tested on macOS in this workspace).
- Alternatively, Node 18+ and npm/yarn to run services locally without containers.
Quick start (recommended: Docker)
Start backend + MySQL using Docker Compose:
docker compose up -d --build
Stop and remove containers:
docker compose down
The backend will be available at http://localhost:8002 by default (see docker-compose.yml
). MySQL is exposed on host port 3308 -> container 3306.
Running services locally (without Docker)
Backend
cd backend
npm install
# set env vars (see Environment section) or create a .env file
npm start
Frontend
cd frontend
npm install
npm run dev
Environment variables
The backend reads configuration from environment variables (some are present in docker-compose.yml
):
- DB_HOST - MySQL host (e.g.
mysql
in compose) - DB_USER - MySQL user (
root
in compose) - DB_PASSWORD - MySQL root password
- DB_NAME - MySQL database name (e.g.
mcs_lose
) - SECRET_KEY - secret used to sign JWT tokens (required for authentication)
Important: do not commit secrets to source control. In production, provide SECRET_KEY
through a secure secret manager.
API (backend)
All endpoints are served by the Express backend in backend/server.js
.
Public
- GET / — renders
index.ejs
(simple root view) - POST /lose — accepts an update payload and attempts a conditional UPDATE on
lose
rows (no auth) - POST /login — admin login; returns a JWT token on success
Protected (require Authorization: Bearer )
- GET /table-data — returns all rows from
lose
- POST /create-entry — bulk-insert losnummer values (body: { losnummer: [...] })
- DELETE /remove-entries — delete rows by losnummer (body: { losnummern: [...] })
- PUT /save-row — update a single row (body contains row fields)
- DELETE /reset-data — delete all rows from
lose
Authentication
- Tokens are generated using
jose
and signed withSECRET_KEY
inbackend/services/tokenService.js
. - The
authenticate
middleware expects anAuthorization
header withBearer <token>
.
Database schema
Schema available in backend/scheme.sql
— main tables:
lose
(columns):losnummer
VARCHAR(255) UNIQUEvorname
,nachname
,adresse
,plz
,email
(nullable)
admin_user
(columns):username
(unique),password
(plain text in current implementation)
Notes:
- The current SQL uses simple types and a single
losnummer
unique constraint. Consider adding an auto-increment id column if needed.
Project structure (high level)
-
backend/
server.js
— Express app, routes and middlewareservices/database.js
— MySQL pool and query helpersservices/tokenService.js
— token generation and authentication middlewarescheme.sql
— DDL for tables
-
frontend/
- React + TypeScript app scaffolded with Vite
src/components
— UI components (Admin, Table, Import GUI, Forms)
-
docker-compose.yml — config for backend and MySQL containers
Notable implementation details & known issues
- Body size limits are increased in
server.js
to accept large CSV/JSON payloads (limit: 10mb). - Passwords for
admin_user
are stored and checked as plaintext indatabase.js
— this is insecure. Use salted password hashing (bcrypt) for real deployments. tokenService.js
currently logs the generated token to console — remove that in production.getTableData()
inservices/database.js
has an incorrect code path checkingresult.entries.length
(should checkresult.length
). This may cause unexpected behavior when the table is empty.- Queries use parameterized queries with
mysql2
which protects against SQL injection for provided query parameters.
Suggestions / TODOs
- Fix the
getTableData()
empty-check bug. - Hash admin passwords and add a registration/migration path for admin accounts.
- Add input validation on API endpoints and better error handling/user feedback.
- Consider adding database migrations (Flyway, knex, sequelize migrations).
- Add tests and CI for backend endpoints.
- Optionally enable the frontend service in
docker-compose.yml
(it's currently commented out).
Development tips
- To inspect the MySQL data created by Docker Compose, you can connect with your preferred MySQL client to
localhost:3308
(user and password fromdocker-compose.yml
). - To generate a token from the backend for testing, create an admin user entry in
admin_user
and POST to/login
.
License
No license specified. Add a LICENSE file if you want to open-source this project.
Contacts / credits
Project created in this workspace. See backend
and frontend
folders for authorship and details.