Improvements
This commit is contained in:
parent
5e74ab8322
commit
45175c031e
5 changed files with 225 additions and 95 deletions
|
|
@ -1,10 +0,0 @@
|
||||||
# Server Configuration
|
|
||||||
PORT=3001
|
|
||||||
NODE_ENV=development
|
|
||||||
|
|
||||||
# CORS Configuration
|
|
||||||
FRONTEND_URL=*
|
|
||||||
|
|
||||||
# File Paths
|
|
||||||
DATA_PATH=../fizika.json
|
|
||||||
PICS_PATH=../pics
|
|
||||||
|
|
@ -1,61 +1,26 @@
|
||||||
# Multi-stage Dockerfile for Fizika Admin Backend
|
|
||||||
FROM node:18-alpine AS base
|
FROM node:18-alpine AS base
|
||||||
|
|
||||||
# Install dumb-init for proper signal handling
|
# Install dumb-init for proper signal handling
|
||||||
RUN apk add --no-cache dumb-init
|
RUN apk add --no-cache dumb-init
|
||||||
|
|
||||||
# Create app directory
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
# Create non-root user
|
|
||||||
RUN addgroup -g 1001 -S nodejs
|
|
||||||
RUN adduser -S fizika -u 1001
|
|
||||||
|
|
||||||
# Copy package files
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
# Development stage
|
|
||||||
FROM base AS dependencies
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
RUN npm ci --only=production && npm cache clean --force
|
RUN npm ci --only=production && npm cache clean --force
|
||||||
|
|
||||||
# Development dependencies for building
|
|
||||||
FROM base AS dev-dependencies
|
|
||||||
RUN npm ci
|
|
||||||
|
|
||||||
# Production stage
|
|
||||||
FROM base AS production
|
FROM base AS production
|
||||||
|
COPY --from=base /usr/src/app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
# Copy production dependencies
|
|
||||||
COPY --from=dependencies /usr/src/app/node_modules ./node_modules
|
|
||||||
|
|
||||||
# Copy application code
|
|
||||||
COPY --chown=fizika:nodejs . .
|
|
||||||
|
|
||||||
# Create necessary directories and set permissions
|
|
||||||
RUN mkdir -p /usr/src/app/data /usr/src/app/pics && \
|
|
||||||
chown -R fizika:nodejs /usr/src/app/data /usr/src/app/pics
|
|
||||||
|
|
||||||
# Security: Remove package files and any other sensitive data
|
|
||||||
RUN rm -f package*.json
|
RUN rm -f package*.json
|
||||||
|
|
||||||
# Set environment variables
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV PORT=3001
|
ENV PORT=3001
|
||||||
|
|
||||||
# Expose port
|
|
||||||
EXPOSE 3001
|
EXPOSE 3001
|
||||||
|
|
||||||
# Switch to non-root user
|
|
||||||
USER fizika
|
|
||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
CMD node -e "require('http').get('http://localhost:3001/api/fizika', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))"
|
CMD node -e "require('http').get('http://localhost:3001/api/fizika', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))"
|
||||||
|
|
||||||
# Use dumb-init for proper signal handling
|
|
||||||
ENTRYPOINT ["dumb-init", "--"]
|
ENTRYPOINT ["dumb-init", "--"]
|
||||||
|
|
||||||
# Start the application
|
|
||||||
CMD ["node", "server.js"]
|
CMD ["node", "server.js"]
|
||||||
|
|
@ -257,37 +257,11 @@
|
||||||
<div class="buttonwrapper">
|
<div class="buttonwrapper">
|
||||||
<select id="evszam">
|
<select id="evszam">
|
||||||
<option value="all/">Összes év</option>
|
<option value="all/">Összes év</option>
|
||||||
<option value="2024/">2024</option>
|
<!-- Years will be populated dynamically from question data -->
|
||||||
<option value="2023/">2023</option>
|
|
||||||
<option value="2022/">2022</option>
|
|
||||||
<option value="2021/">2021</option>
|
|
||||||
<option value="2020/">2020</option>
|
|
||||||
<option value="2019/">2019</option>
|
|
||||||
<option value="2018/">2018</option>
|
|
||||||
<option value="2017/">2017</option>
|
|
||||||
<option value="2016/">2016</option>
|
|
||||||
<option value="2015/">2015</option>
|
|
||||||
<option value="2014/">2014</option>
|
|
||||||
<option value="2013/">2013</option>
|
|
||||||
<option value="2012/">2012</option>
|
|
||||||
<option value="2011/">2011</option>
|
|
||||||
<option value="2010/">2010</option>
|
|
||||||
<option value="2009/">2009</option>
|
|
||||||
<option value="2008/">2008</option>
|
|
||||||
<option value="2007/">2007</option>
|
|
||||||
<option value="2006/">2006</option>
|
|
||||||
<option value="2005/">2005</option>
|
|
||||||
</select>
|
</select>
|
||||||
<select id="honap">
|
<select id="honap">
|
||||||
<option value="all">Összes feladatsora</option>
|
<option value="all">Összes feladatsora</option>
|
||||||
<option value="1" class="f">Május-Június</option>
|
<!-- Months will be populated dynamically based on selected year -->
|
||||||
<option value="2" class="f">Október-November</option>
|
|
||||||
<option value="1" class="f2006">Február-Március</option>
|
|
||||||
<option value="2" class="f2006">Május-Június</option>
|
|
||||||
<option value="3" class="f2006">Október-November</option>
|
|
||||||
<option value="m1" class="f2016">1. Mintafeladatsor</option>
|
|
||||||
<option value="m2" class="f2016">2. Mintafeladatsor</option>
|
|
||||||
<option value="m3" class="f2016">3. Mintafeladatsor</option>
|
|
||||||
</select>
|
</select>
|
||||||
<select id="feladat">
|
<select id="feladat">
|
||||||
<option value="/all">Összes feladat</option>
|
<option value="/all">Összes feladat</option>
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,17 @@ setInterval(function () {
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
eredmeny();
|
eredmeny();
|
||||||
|
|
||||||
|
// Initialize year dropdown with dynamic years from question data
|
||||||
|
if (typeof initializeYearDropdown === 'function') {
|
||||||
|
initializeYearDropdown().then(() => {
|
||||||
|
// Initialize month dropdown for default "all" year selection
|
||||||
|
if (typeof initializeMonthDropdown === 'function') {
|
||||||
|
initializeMonthDropdown('all/');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$(window).on("mousewheel", function () {
|
$(window).on("mousewheel", function () {
|
||||||
$("body").stop();
|
$("body").stop();
|
||||||
});
|
});
|
||||||
|
|
@ -316,17 +327,24 @@ $(document).ready(function () {
|
||||||
isSearch = false;
|
isSearch = false;
|
||||||
});
|
});
|
||||||
$("#evszam").change(function () {
|
$("#evszam").change(function () {
|
||||||
if ($("#evszam").val() == "2006/") {
|
const selectedYear = $("#evszam").val();
|
||||||
|
|
||||||
|
// Initialize month dropdown dynamically based on selected year
|
||||||
|
if (typeof initializeMonthDropdown === 'function') {
|
||||||
|
initializeMonthDropdown(selectedYear);
|
||||||
|
} else {
|
||||||
|
// Fallback to original logic if dynamic function not available
|
||||||
|
if (selectedYear == "2006/") {
|
||||||
$(".f2006").show();
|
$(".f2006").show();
|
||||||
$(".f2016").hide();
|
$(".f2016").hide();
|
||||||
$(".f").hide();
|
$(".f").hide();
|
||||||
$(".fnem17").hide();
|
$(".fnem17").hide();
|
||||||
} else if ($("#evszam").val() == "2016/") {
|
} else if (selectedYear == "2016/") {
|
||||||
$(".f2006").hide();
|
$(".f2006").hide();
|
||||||
$(".f2016").show();
|
$(".f2016").show();
|
||||||
$(".f").show();
|
$(".f").show();
|
||||||
$(".fnem17").show();
|
$(".fnem17").show();
|
||||||
} else if ($("#evszam").val() == "2017/") {
|
} else if (selectedYear == "2017/") {
|
||||||
$(".f2006").hide();
|
$(".f2006").hide();
|
||||||
$(".f2016").hide();
|
$(".f2016").hide();
|
||||||
$(".f").show();
|
$(".f").show();
|
||||||
|
|
@ -337,6 +355,8 @@ $(document).ready(function () {
|
||||||
$(".f").show();
|
$(".f").show();
|
||||||
$(".fnem17").show();
|
$(".fnem17").show();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$("#honap").val("all");
|
$("#honap").val("all");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,3 +122,184 @@ function shuffleArray(array) {
|
||||||
[array[i], array[j]] = [array[j], array[i]];
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize year dropdown with dynamic years from question data
|
||||||
|
const initializeYearDropdown = async () => {
|
||||||
|
try {
|
||||||
|
// Load questions if not already loaded
|
||||||
|
if (questions === null) {
|
||||||
|
try {
|
||||||
|
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 for year dropdown initialization');
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to load questions from API, falling back to local file:', error);
|
||||||
|
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 for year dropdown');
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error('Both API and local file failed:', fallbackError);
|
||||||
|
return; // Don't update dropdown if data unavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract unique years from question sources
|
||||||
|
const yearSet = new Set();
|
||||||
|
questions.forEach(q => {
|
||||||
|
const yearMatch = q.source.match(/^(\d{4})\//);
|
||||||
|
if (yearMatch) {
|
||||||
|
yearSet.add(parseInt(yearMatch[1]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert to sorted array (newest first)
|
||||||
|
const uniqueYears = Array.from(yearSet).sort((a, b) => b - a);
|
||||||
|
|
||||||
|
// Get existing dropdown
|
||||||
|
const yearDropdown = document.getElementById('evszam');
|
||||||
|
if (!yearDropdown) return;
|
||||||
|
|
||||||
|
// Preserve the "Összes év" option and add dynamic years
|
||||||
|
const allYearsOption = '<option value="all/">Összes év</option>';
|
||||||
|
const yearOptions = uniqueYears.map(year =>
|
||||||
|
`<option value="${year}/">${year}</option>`
|
||||||
|
).join('');
|
||||||
|
|
||||||
|
yearDropdown.innerHTML = allYearsOption + yearOptions;
|
||||||
|
|
||||||
|
console.log('Year dropdown initialized with years:', uniqueYears);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize year dropdown:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize month dropdown dynamically based on selected year
|
||||||
|
const initializeMonthDropdown = (selectedYear) => {
|
||||||
|
if (!questions) return;
|
||||||
|
|
||||||
|
const monthDropdown = document.getElementById('honap');
|
||||||
|
if (!monthDropdown) return;
|
||||||
|
|
||||||
|
// Always include "Összes feladatsora" option
|
||||||
|
let monthOptions = '<option value="all">Összes feladatsora</option>';
|
||||||
|
|
||||||
|
if (selectedYear === 'all/') {
|
||||||
|
// Show all possible month patterns
|
||||||
|
const monthSet = new Set();
|
||||||
|
questions.forEach(q => {
|
||||||
|
const sourceMatch = q.source.match(/^(\d{4})\/(.+?)\//);
|
||||||
|
if (sourceMatch) {
|
||||||
|
monthSet.add(sourceMatch[2]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert to sorted array and create options
|
||||||
|
const uniqueMonths = Array.from(monthSet).sort();
|
||||||
|
uniqueMonths.forEach(month => {
|
||||||
|
monthOptions += `<option value="${month}" class="fdynamic">${getMonthLabel(month)}</option>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Extract year from selected value (e.g., "2024/" -> "2024")
|
||||||
|
const year = selectedYear.replace('/', '');
|
||||||
|
|
||||||
|
// Get unique months for this specific year
|
||||||
|
const monthSet = new Set();
|
||||||
|
questions.forEach(q => {
|
||||||
|
const sourceMatch = q.source.match(`^${year}\/(.+?)\/`);
|
||||||
|
if (sourceMatch) {
|
||||||
|
monthSet.add(sourceMatch[1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const uniqueMonths = Array.from(monthSet).sort();
|
||||||
|
|
||||||
|
// Special handling for known year patterns
|
||||||
|
if (year === '2006') {
|
||||||
|
// Preserve existing 2006 logic but add any new months found
|
||||||
|
monthOptions += '<option value="1" class="f2006">Február-Március</option>';
|
||||||
|
monthOptions += '<option value="2" class="f2006">Május-Június</option>';
|
||||||
|
monthOptions += '<option value="3" class="f2006">Október-November</option>';
|
||||||
|
|
||||||
|
// Add any dynamic months not covered by the standard ones
|
||||||
|
uniqueMonths.forEach(month => {
|
||||||
|
if (!['1', '2', '3'].includes(month)) {
|
||||||
|
monthOptions += `<option value="${month}" class="f2006">${getMonthLabel(month)}</option>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (year === '2016') {
|
||||||
|
// Preserve existing 2016 logic but add any new months found
|
||||||
|
monthOptions += '<option value="1" class="f">Május-Június</option>';
|
||||||
|
monthOptions += '<option value="2" class="f">Október-November</option>';
|
||||||
|
monthOptions += '<option value="m1" class="f2016">1. Mintafeladatsor</option>';
|
||||||
|
monthOptions += '<option value="m2" class="f2016">2. Mintafeladatsor</option>';
|
||||||
|
monthOptions += '<option value="m3" class="f2016">3. Mintafeladatsor</option>';
|
||||||
|
|
||||||
|
// Add any dynamic months not covered
|
||||||
|
uniqueMonths.forEach(month => {
|
||||||
|
if (!['1', '2', 'm1', 'm2', 'm3'].includes(month)) {
|
||||||
|
monthOptions += `<option value="${month}" class="f">${getMonthLabel(month)}</option>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (year === '2017') {
|
||||||
|
// Preserve existing 2017 logic but add any new months found
|
||||||
|
monthOptions += '<option value="1" class="f">Május-Június</option>';
|
||||||
|
monthOptions += '<option value="2" class="f">Október-November</option>';
|
||||||
|
|
||||||
|
// Add any dynamic months
|
||||||
|
uniqueMonths.forEach(month => {
|
||||||
|
if (!['1', '2'].includes(month)) {
|
||||||
|
monthOptions += `<option value="${month}" class="f">${getMonthLabel(month)}</option>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// For other years, use standard logic plus dynamic months
|
||||||
|
const hasStandard1 = uniqueMonths.includes('1');
|
||||||
|
const hasStandard2 = uniqueMonths.includes('2');
|
||||||
|
|
||||||
|
if (hasStandard1) {
|
||||||
|
monthOptions += '<option value="1" class="f">Május-Június</option>';
|
||||||
|
}
|
||||||
|
if (hasStandard2) {
|
||||||
|
monthOptions += '<option value="2" class="f">Október-November</option>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any non-standard months
|
||||||
|
uniqueMonths.forEach(month => {
|
||||||
|
if (!['1', '2'].includes(month)) {
|
||||||
|
monthOptions += `<option value="${month}" class="f">${getMonthLabel(month)}</option>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
monthDropdown.innerHTML = monthOptions;
|
||||||
|
console.log(`Month dropdown initialized for year: ${selectedYear}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to get readable month labels
|
||||||
|
const getMonthLabel = (monthValue) => {
|
||||||
|
// Handle known patterns
|
||||||
|
const knownLabels = {
|
||||||
|
'1': 'Május-Június',
|
||||||
|
'2': 'Október-November',
|
||||||
|
'3': 'Harmadik időszak',
|
||||||
|
'm1': '1. Mintafeladatsor',
|
||||||
|
'm2': '2. Mintafeladatsor',
|
||||||
|
'm3': '3. Mintafeladatsor'
|
||||||
|
};
|
||||||
|
|
||||||
|
return knownLabels[monthValue] || monthValue;
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue