This commit is contained in:
Andras Schmelczer 2025-08-31 13:32:58 +01:00
parent 30825d4ee1
commit 567ffea181
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
7 changed files with 157 additions and 86 deletions

View file

@ -58,7 +58,7 @@ fizika/
3. **Access the applications:** 3. **Access the applications:**
- **Student Quiz**: http://localhost (or your domain) - **Student Quiz**: http://localhost (or your domain)
- **Admin Panel**: http://localhost:3001/admin.html - **Admin Panel**: http://localhost:3001/
### Option 2: Local Development ### Option 2: Local Development
@ -87,15 +87,20 @@ NODE_ENV=production
# Frontend integration # Frontend integration
FRONTEND_URL=* FRONTEND_URL=*
# File paths (relative to backend directory)
DATA_PATH=../fizika.json
PICS_PATH=../pics
``` ```
## 🔌 API Integration ## 🔌 API Integration
The frontend automatically detects the backend: The frontend **prioritizes backend API** with local fallback:
- **Local Development**: `http://localhost:3001` - **Primary**: Auto-detects backend (`localhost:3001` for dev, same origin for production)
- **Production**: Configure `API_BASE` in `js/load.js` - **Fallback**: Local `fizika.json` and `pics/` directory when backend unavailable
- **Images**: Try backend API first, fallback to local `pics/` directory
If the backend is unavailable, the frontend falls back to loading `fizika.json` directly. **Graceful degradation** - Quiz works even when backend is down, using local files.
## 📊 API Endpoints ## 📊 API Endpoints

View file

@ -4,3 +4,7 @@ NODE_ENV=development
# CORS Configuration # CORS Configuration
FRONTEND_URL=* FRONTEND_URL=*
# File Paths
DATA_PATH=../fizika.json
PICS_PATH=../pics

View file

@ -56,8 +56,8 @@ A secure Node.js/Express backend for managing physics quiz questions and images
``` ```
4. **Access the admin interface:** 4. **Access the admin interface:**
- Open http://localhost:3001/admin.html - Open http://localhost:3001/
- Default password: `admin123` (change this!) - No authentication required
### Local Development ### Local Development
@ -169,7 +169,7 @@ The project includes a GitHub Actions workflow that:
## Admin Interface ## Admin Interface
Access the admin interface at `/admin.html`: Access the admin interface at `/`:
- **Questions Tab**: Add, edit, delete quiz questions - **Questions Tab**: Add, edit, delete quiz questions
- **Images Tab**: Upload and manage image files - **Images Tab**: Upload and manage image files
- **Responsive Design**: Works on desktop and mobile - **Responsive Design**: Works on desktop and mobile

View file

@ -11,6 +11,8 @@ services:
- NODE_ENV=production - NODE_ENV=production
- PORT=3001 - PORT=3001
- FRONTEND_URL=${FRONTEND_URL:-*} - FRONTEND_URL=${FRONTEND_URL:-*}
- DATA_PATH=${DATA_PATH:-/usr/src/app/fizika.json}
- PICS_PATH=${PICS_PATH:-/usr/src/app/pics}
volumes: volumes:
# Mount data files # Mount data files
- ./fizika.json:/usr/src/app/fizika.json:ro - ./fizika.json:/usr/src/app/fizika.json:ro
@ -37,6 +39,8 @@ services:
- NODE_ENV=development - NODE_ENV=development
- PORT=3001 - PORT=3001
- FRONTEND_URL=* - FRONTEND_URL=*
- DATA_PATH=/usr/src/app/fizika.json
- PICS_PATH=/usr/src/app/pics
volumes: volumes:
- ./backend:/usr/src/app - ./backend:/usr/src/app
- ./fizika.json:/usr/src/app/fizika.json - ./fizika.json:/usr/src/app/fizika.json

View file

@ -15,16 +15,25 @@ app.use(cors({
credentials: true credentials: true
})); }));
app.use(express.json({ limit: '10mb' })); app.use(express.json({ limit: '100mb' }));
app.use(express.static('public')); app.use(express.static('public'));
// File paths // File paths
const DATA_PATH = path.join(__dirname, '../frontend/fizika.json'); const DATA_PATH = process.env.DATA_PATH || path.join(__dirname, '../fizika.json');
const PICS_PATH = path.join(__dirname, '../frontend/pics'); const PICS_PATH = process.env.PICS_PATH || path.join(__dirname, '../pics');
// Multer configuration for image uploads // Multer configuration for image uploads
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, PICS_PATH);
},
filename: (req, file, cb) => {
cb(null, file.originalname);
}
});
const upload = multer({ const upload = multer({
dest: PICS_PATH, storage: storage,
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
fileFilter: (req, file, cb) => { fileFilter: (req, file, cb) => {
if (file.mimetype.startsWith('image/')) { if (file.mimetype.startsWith('image/')) {
@ -32,9 +41,6 @@ const upload = multer({
} else { } else {
cb(new Error('Only images allowed')); cb(new Error('Only images allowed'));
} }
},
filename: (req, file, cb) => {
cb(null, file.originalname);
} }
}); });

View file

@ -25,6 +25,8 @@ async function ajaxLoad(type) {
$("#loadingGif").show(); $("#loadingGif").show();
let result = ""; let result = "";
try {
if (type == 1) { if (type == 1) {
var source = var source =
"^" + $("#evszam").val() + $("#honap").val() + $("#feladat").val() + "$"; "^" + $("#evszam").val() + $("#honap").val() + $("#feladat").val() + "$";
@ -88,6 +90,27 @@ async function ajaxLoad(type) {
$("#megoldas").show(); $("#megoldas").show();
$("#state").html("Feladatok sikeresen letöltve!"); $("#state").html("Feladatok sikeresen letöltve!");
} }
} catch (error) {
$("#loadingGif").hide();
$("#content").html(`
<div class="buttonwrapper">
<b style="font-size: 1.5rem; color: #dc3545;">
Nem sikerült betölteni a feladatokat
</b>
<p style="margin-top: 1rem; color: #666;">
${error.message}
</p>
<p style="margin-top: 0.5rem; color: #666;">
Ellenőrizd az internetkapcsolatot vagy próbáld újra.
</p>
<button class="button" onclick="location.reload()" style="margin-top: 1rem;">
Újrapróbálás
</button>
</div>
`);
$("#state").html("Hiba a feladatok betöltésekor");
console.error('Quiz loading error:', error);
}
} }
function showCorrect(id, correctAns) { function showCorrect(id, correctAns) {

View file

@ -1,5 +1,20 @@
let questions = null; let questions = null;
const API_BASE = window.location.hostname === 'localhost' ? 'http://localhost:3001' : 'https://your-backend-domain.com';
// Auto-detect API base URL
const getApiBase = () => {
const protocol = window.location.protocol;
const hostname = window.location.hostname;
// If running on localhost, assume backend is on port 3001
if (hostname === 'localhost' || hostname === '127.0.0.1') {
return `${protocol}//${hostname}:3001`;
}
// For production, assume backend is on same origin
return "https://fizika-backend.schmelczer.dev"
};
const API_BASE = getApiBase();
const loadQuestions = async ( const loadQuestions = async (
isSearch, isSearch,
@ -9,10 +24,25 @@ const loadQuestions = async (
) => { ) => {
if (questions === null) { if (questions === null) {
try { try {
questions = await (await fetch(`${API_BASE}/api/fizika`)).json(); const response = await fetch(`${API_BASE}/api/fizika`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
questions = await response.json();
console.log('Questions loaded from backend API');
} catch (error) { } catch (error) {
console.error('Failed to load questions from API, falling back to local file:', error); console.warn('Failed to load questions from API, falling back to local file:', error);
questions = await (await fetch("fizika.json")).json(); try {
const fallbackResponse = await fetch("fizika.json");
if (!fallbackResponse.ok) {
throw new Error(`Local file not available: ${fallbackResponse.status}`);
}
questions = await fallbackResponse.json();
console.log('Questions loaded from local fallback file');
} catch (fallbackError) {
console.error('Both API and local file failed:', fallbackError);
throw new Error('Unable to load quiz data from either backend API or local file');
}
} }
} }
@ -38,7 +68,7 @@ const loadQuestions = async (
<div class="feladat card" id="feladat${id}"> <div class="feladat card" id="feladat${id}">
<h2 style="float: left;">${i + 1}.</h2><h2>${source}</h2> <h2 style="float: left;">${i + 1}.</h2><h2>${source}</h2>
<pre>${description}</pre> <pre>${description}</pre>
${image ? `<img src="${API_BASE}/api/pics/${image}"><br>` : ""} ${image ? `<img src="${API_BASE}/api/pics/${image}" onerror="this.src='pics/${image}'"><br>` : ""}
<form id="form${id}""> <form id="form${id}"">
<input type="radio" id="rad1" name="group"> <input type="radio" id="rad1" name="group">
<label id="label${id}" class="rad1">${a}</label> <label id="label${id}" class="rad1">${a}</label>
@ -49,8 +79,7 @@ const loadQuestions = async (
<input type="radio" id="rad3" name="group"> <input type="radio" id="rad3" name="group">
<label id="label${id}" class="rad3">${c}</label> <label id="label${id}" class="rad3">${c}</label>
<br> <br>
${ ${d
d
? ` ? `
<input type="radio" id="rad4" name="group"> <input type="radio" id="rad4" name="group">
<label id="label${id}" class="rad4">${d}</label> <label id="label${id}" class="rad4">${d}</label>