From 5e74ab83225036955bb5c59a1d15f244fa615ffd Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sun, 31 Aug 2025 13:51:59 +0100 Subject: [PATCH] Small fixes --- backend/CLAUDE.md | 134 ++++++++++++++++++++++++++++++++++++++ backend/public/admin.js | 55 +++++----------- backend/public/index.html | 19 +++++- backend/server.js | 18 +---- frontend/index.html | 1 - frontend/js/fizika.js | 59 +++++++++-------- 6 files changed, 201 insertions(+), 85 deletions(-) create mode 100644 backend/CLAUDE.md diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md new file mode 100644 index 0000000..124ed9c --- /dev/null +++ b/backend/CLAUDE.md @@ -0,0 +1,134 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Common Development Commands + +### Running the Application +```bash +npm run dev # Development mode with nodemon hot reload +npm start # Production mode +``` + +### Docker Commands +```bash +# Production deployment +docker-compose up -d + +# Development with hot reload +docker-compose --profile dev up fizika-admin-dev + +# Build image manually +docker build -t fizika-admin ./backend +``` + +### Environment Setup +```bash +cp .env.example .env +# Edit .env with your configuration +``` + +## Architecture Overview + +This is a single-file Node.js/Express backend (`server.js`) that serves as both: +1. **API Server**: Provides REST endpoints for question and image management +2. **Static File Server**: Serves the admin interface from `/public/` directory + +### Key Components + +**Data Storage**: +- Questions stored in JSON file at `../frontend/fizika.json` (configurable via `DATA_PATH`) +- Images stored in `../frontend/pics/` directory (configurable via `PICS_PATH`) + +**Admin Interface**: +- Built-in web UI served from `/public/index.html` +- JavaScript client in `/public/admin.js` +- No authentication required (simplified for admin use) + +**API Structure**: +- Public endpoints: `/api/fizika`, `/api/images`, `/api/pics/:filename` +- Admin endpoints: `/api/admin/questions`, `/api/admin/images` +- No JWT authentication implemented despite README documentation + +### Question Data Format +```json +{ + "id": 1, + "source": "2016/m1/1", + "description": "Question text...", + "a": "Option A", + "b": "Option B", + "c": "Option C", + "d": "Option D", + "correct": 2, + "type": "md", + "image": "optional-image.jpg" +} +``` + +### Complete Question Types (17 Categories) + +**IMPORTANT**: The README only mentions 3 types, but the frontend supports all 17: + +**Mechanics (Mechanika)**: +- `mec` - Mechanika (general mechanics) +- `mk` - Kinematika (kinematics) +- `md` - Dinamika (dynamics) +- `me` - Munka és energia (work and energy) +- `mf` - Folyadékok és gázok mechanikája (fluid mechanics) +- `mr` - Rezgések és hullámok (oscillations and waves) + +**Thermodynamics**: +- `h` - Hőtan (thermodynamics) + +**Electricity**: +- `ele` - Elektromosság (general electricity) +- `es` - Elektrosztatika (electrostatics) +- `ee` - Egyenáram (direct current) +- `ev` - Váltakozó áram (alternating current) + +**Other Physics**: +- `m` - Mágnesesség (magnetism) +- `o` - Fénytan (optics) +- `atm` - Atomfizika (general atomic physics) +- `ah` - Atomhéj (electron shells) +- `am` - Atommag (atomic nucleus) +- `cs` - Égi mechanika, csillagászat (celestial mechanics, astronomy) +- `v` - Vegyes (mixed/various) + +### Security Considerations +- Helmet.js for security headers +- CORS configuration via `FRONTEND_URL` environment variable +- File upload restricted to images only (5MB limit) +- Input validation minimal - add validation when modifying endpoints + +## File Structure +``` +backend/ +├── server.js # Main application file +├── public/ +│ ├── index.html # Admin interface HTML +│ └── admin.js # Admin interface JavaScript +├── package.json +├── Dockerfile +├── docker-compose.yml +└── .env.example +``` + +## Important Notes + +- **Single File Architecture**: All server logic is in `server.js` - no separate route files or modules +- **File-based Data**: Uses JSON file for persistence, not a database +- **No Authentication**: Despite README documentation mentioning JWT, no auth is implemented +- **Path Dependencies**: Assumes frontend directory structure (`../frontend/fizika.json`, `../frontend/pics/`) +- **Admin UI Included**: Built-in web interface accessible at root path `/` +- **Question Types**: Support all 17 physics categories listed above, not just the 3 in README + +## Making Changes + +When modifying the API: +1. All changes go in `server.js` +2. Test both API endpoints and admin UI functionality +3. Ensure question type validation supports all 17 categories if adding validation +4. Consider impact on file paths and data format +5. Update environment variables in `.env.example` if needed \ No newline at end of file diff --git a/backend/public/admin.js b/backend/public/admin.js index 4bc5958..4739746 100644 --- a/backend/public/admin.js +++ b/backend/public/admin.js @@ -1,36 +1,27 @@ const API_BASE = window.location.origin; -// Initialize document.addEventListener("DOMContentLoaded", function () { loadQuestions(); loadImages(); - - // Set up event listeners setupEventListeners(); }); function setupEventListeners() { - // Tab switching document.querySelectorAll('.tab[data-tab]').forEach(tab => { tab.addEventListener('click', (e) => { switchTab(e.target.dataset.tab); }); }); - - // Add question button + document.getElementById('addQuestionBtn').addEventListener('click', showAddQuestionModal); - - // Image upload + document.getElementById('imageUpload').addEventListener('change', uploadImage); - - // Modal close buttons + document.getElementById('closeModalBtn').addEventListener('click', closeModal); document.getElementById('cancelBtn').addEventListener('click', closeModal); - - // Question form submit + document.getElementById('questionForm').addEventListener('submit', saveQuestion); - - // Close modal when clicking outside + document.getElementById('questionModal').addEventListener('click', (e) => { if (e.target.id === 'questionModal') { closeModal(); @@ -38,7 +29,6 @@ function setupEventListeners() { }); } -// Tab switching function switchTab(tabName) { document .querySelectorAll(".tab-content") @@ -53,7 +43,6 @@ function switchTab(tabName) { if (tabName === "images") loadImages(); } -// Questions management async function loadQuestions() { try { const response = await fetch(`${API_BASE}/api/admin/questions`); @@ -65,7 +54,7 @@ async function loadQuestions() { } } catch (error) { document.getElementById("questionsList").innerHTML = - '
Hiba a kérdések betöltésekor
'; + `
Hiba a kérdések betöltésekor ${error.message}
`; } } @@ -77,14 +66,12 @@ function displayQuestions(questions) {

ID: ${q.id} - ${q.source}

Kérdés: ${q.description.substring( - 0, - 100 - )}...

-

Típus: ${ - q.type - } | Helyes válasz: ${ - ["A", "B", "C", "D"][q.correct - 1] - }

+ 0, + 100 + )}...

+

Típus: ${q.type + } | Helyes válasz: ${["A", "B", "C", "D"][q.correct - 1] + }

@@ -93,14 +80,13 @@ function displayQuestions(questions) { ` ) .join(""); - - // Add event listeners for edit and delete buttons + container.querySelectorAll('[data-edit-id]').forEach(btn => { btn.addEventListener('click', (e) => { editQuestion(parseInt(e.target.dataset.editId)); }); }); - + container.querySelectorAll('[data-delete-id]').forEach(btn => { btn.addEventListener('click', (e) => { deleteQuestion(parseInt(e.target.dataset.deleteId)); @@ -213,7 +199,6 @@ async function deleteQuestion(id) { } } -// Images management async function loadImages() { try { const response = await fetch(`${API_BASE}/api/images`); @@ -242,8 +227,7 @@ function displayImages(images) { ` ) .join(""); - - // Add event listeners for delete buttons + container.querySelectorAll('[data-delete-image]').forEach(btn => { btn.addEventListener('click', (e) => { deleteImage(e.target.dataset.deleteImage); @@ -279,7 +263,7 @@ async function uploadImage() { ); } } catch (error) { - showAlert("imagesAlert", "Kapcsolat hiba", "danger"); + showAlert("imagesAlert", `Kapcsolat hiba: ${error.message}`, "danger"); } } @@ -301,11 +285,10 @@ async function deleteImage(filename) { throw new Error("Delete failed"); } } catch (error) { - showAlert("imagesAlert", "Hiba a törlés során", "danger"); + showAlert("imagesAlert", `Hiba a törlés során: ${error.message}`, "danger"); } } -// Utility functions function closeModal() { document.getElementById("questionModal").style.display = "none"; } @@ -319,6 +302,4 @@ function showAlert(elementId, message, type) { setTimeout(() => { alertDiv.style.display = "none"; }, 5000); -} - -// This is now handled in setupEventListeners() \ No newline at end of file +} \ No newline at end of file diff --git a/backend/public/index.html b/backend/public/index.html index d353bd1..068322b 100644 --- a/backend/public/index.html +++ b/backend/public/index.html @@ -338,9 +338,24 @@
diff --git a/backend/server.js b/backend/server.js index d900778..90b1777 100644 --- a/backend/server.js +++ b/backend/server.js @@ -19,8 +19,8 @@ app.use(express.json({ limit: '100mb' })); app.use(express.static('public')); // File paths -const DATA_PATH = process.env.DATA_PATH || path.join(__dirname, '../fizika.json'); -const PICS_PATH = process.env.PICS_PATH || path.join(__dirname, '../pics'); +const DATA_PATH = process.env.DATA_PATH || path.join(__dirname, '../frontend/fizika.json'); +const PICS_PATH = process.env.PICS_PATH || path.join(__dirname, '../frontend/pics'); // Multer configuration for image uploads const storage = multer.diskStorage({ @@ -54,11 +54,7 @@ const writeData = async (data) => { await fs.writeFile(DATA_PATH, JSON.stringify(data, null, 2)); }; -const validateQuestion = (q) => { - return q.description && q.a && q.b && q.c && q.d && - q.correct >= 1 && q.correct <= 4 && - ['md', 'me', 'mf'].includes(q.type) && q.source; -}; + // Public routes app.get('/api/fizika', async (req, res) => { @@ -94,10 +90,6 @@ app.get('/api/admin/questions', async (req, res) => { app.post('/api/admin/questions', async (req, res) => { try { - if (!validateQuestion(req.body)) { - return res.status(400).json({ error: 'Invalid question data' }); - } - const data = await readData(); const maxId = Math.max(...data.map(q => q.id), 0); @@ -113,10 +105,6 @@ app.post('/api/admin/questions', async (req, res) => { app.put('/api/admin/questions/:id', async (req, res) => { try { - if (!validateQuestion(req.body)) { - return res.status(400).json({ error: 'Invalid question data' }); - } - const data = await readData(); const index = data.findIndex(q => q.id === parseInt(req.params.id)); diff --git a/frontend/index.html b/frontend/index.html index 3e189d8..2955186 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -238,7 +238,6 @@ id="numberof" placeholder="Feladatok mennyisége: " min="1" - max="435" />
diff --git a/frontend/js/fizika.js b/frontend/js/fizika.js index d800d34..864769a 100644 --- a/frontend/js/fizika.js +++ b/frontend/js/fizika.js @@ -25,7 +25,7 @@ async function ajaxLoad(type) { $("#loadingGif").show(); let result = ""; - + try { if (type == 1) { var source = @@ -117,11 +117,11 @@ function showCorrect(id, correctAns) { teszt(id, correctAns); eval( "$('" + - "#label" + - id + - ".rad" + - correctAns + - "').css('background-color', '#C6FF8C');" + "#label" + + id + + ".rad" + + correctAns + + "').css('background-color', '#C6FF8C');" ); $("#state").html("Helyes válaszok bejelölve!"); $("#state2").html( @@ -155,20 +155,20 @@ function teszt(id, correctAns) { ); eval( "localStorage.teszt" + - numberOfPreviousTests + - "date = '" + - datum.toLocaleDateString() + - "'" + numberOfPreviousTests + + "date = '" + + datum.toLocaleDateString() + + "'" ); eval( "localStorage.teszt" + numberOfPreviousTests + "time = '" + ido + "'" ); eval( "localStorage.teszt" + - numberOfPreviousTests + - "total = '" + - totalPoints + - "'" + numberOfPreviousTests + + "total = '" + + totalPoints + + "'" ); startTimer = 0; timer = 0; @@ -218,21 +218,21 @@ function eredmeny() { var isGood = eval(localString); $("#ered tr:last").after( "" + - i + - "." + - eval(datumString) + - "" + - eval(timeString) + - " perc" + - " " + - eval(localString) + - "%" + - Math.round((eval(localString) * eval(totalString)) / 100) + - "/" + - eval(totalString) + - " pont" + i + + "." + + eval(datumString) + + "" + + eval(timeString) + + " perc" + + " " + + eval(localString) + + "%" + + Math.round((eval(localString) * eval(totalString)) / 100) + + "/" + + eval(totalString) + + " pont" ); } } else { @@ -290,7 +290,6 @@ $(document).ready(function () { $("#fooldal").hide(); }); $("#beredmenyek").click(function () { - //eredmeny(); $("#bfooldal").css("font-weight", "400"); $("#bteszt").css("font-weight", "400"); $("#beredmenyek").css("font-weight", "700");