Use API
This commit is contained in:
parent
30825d4ee1
commit
567ffea181
7 changed files with 157 additions and 86 deletions
15
README.md
15
README.md
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,7 @@ NODE_ENV=development
|
||||||
|
|
||||||
# CORS Configuration
|
# CORS Configuration
|
||||||
FRONTEND_URL=*
|
FRONTEND_URL=*
|
||||||
|
|
||||||
|
# File Paths
|
||||||
|
DATA_PATH=../fizika.json
|
||||||
|
PICS_PATH=../pics
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue