From da351b40b2c2c52fe42ca37e41676ef5555bbd86 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 12 Nov 2025 15:29:04 +0100 Subject: [PATCH] Add node-fetch dependency and implement meeting report submission endpoint This commit introduces the node-fetch package to facilitate HTTP requests in the backend. Additionally, a new API endpoint for submitting meeting reports has been implemented in the nuscoreApiRoutes. The endpoint handles report data submission, cookie management, and error handling, enhancing the functionality of the meeting report feature. Frontend components have been updated to support this new functionality, improving the overall user experience. --- backend/node_modules/.package-lock.json | 91 ++++++ backend/package-lock.json | 92 ++++++ backend/package.json | 1 + backend/routes/nuscoreApiRoutes.js | 89 ++++++ backend/server.js | 17 ++ .../src/components/MatchReportApiDialog.vue | 262 ++++++++++++++---- frontend/src/views/ScheduleView.vue | 1 - 7 files changed, 504 insertions(+), 49 deletions(-) diff --git a/backend/node_modules/.package-lock.json b/backend/node_modules/.package-lock.json index 6da0403..da95e02 100644 --- a/backend/node_modules/.package-lock.json +++ b/backend/node_modules/.package-lock.json @@ -1318,6 +1318,15 @@ "node": ">= 10" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/date-fns": { "version": "2.30.0", "license": "MIT", @@ -1886,6 +1895,29 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "dev": true, @@ -2004,6 +2036,18 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/formidable": { "version": "3.5.4", "dev": true, @@ -3155,10 +3199,48 @@ "node": ">=6.0.0" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-ensure": { "version": "0.0.0", "license": "MIT" }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-gyp": { "version": "8.4.1", "dev": true, @@ -5032,6 +5114,15 @@ "dev": true, "license": "MIT" }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "license": "BSD-2-Clause" diff --git a/backend/package-lock.json b/backend/package-lock.json index 7d5e601..4741be0 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -23,6 +23,7 @@ "multer": "^1.4.5-lts.1", "mysql2": "^3.10.3", "node-cron": "^4.2.1", + "node-fetch": "^3.3.2", "nodemailer": "^7.0.9", "pdf-parse": "^1.1.1", "pdfjs-dist": "^5.4.394", @@ -1352,6 +1353,15 @@ "node": ">= 10" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/date-fns": { "version": "2.30.0", "license": "MIT", @@ -1920,6 +1930,29 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "dev": true, @@ -2038,6 +2071,18 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/formidable": { "version": "3.5.4", "dev": true, @@ -3189,10 +3234,48 @@ "node": ">=6.0.0" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-ensure": { "version": "0.0.0", "license": "MIT" }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-gyp": { "version": "8.4.1", "dev": true, @@ -5066,6 +5149,15 @@ "dev": true, "license": "MIT" }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "license": "BSD-2-Clause" diff --git a/backend/package.json b/backend/package.json index 0814d64..ddbfa92 100644 --- a/backend/package.json +++ b/backend/package.json @@ -28,6 +28,7 @@ "multer": "^1.4.5-lts.1", "mysql2": "^3.10.3", "node-cron": "^4.2.1", + "node-fetch": "^3.3.2", "nodemailer": "^7.0.9", "pdf-parse": "^1.1.1", "pdfjs-dist": "^5.4.394", diff --git a/backend/routes/nuscoreApiRoutes.js b/backend/routes/nuscoreApiRoutes.js index ca2be52..064a1f2 100644 --- a/backend/routes/nuscoreApiRoutes.js +++ b/backend/routes/nuscoreApiRoutes.js @@ -182,4 +182,93 @@ router.get('/meetingdetails/:uuid', async (req, res) => { } }); +// Submit Meeting Report API-Endpunkt +router.put('/submit/:uuid', async (req, res) => { + const { uuid } = req.params; + const reportData = req.body; + + try { + // Hole Cookies für diese UUID (falls vorhanden) + // Versuche zuerst UUID, dann Code als Fallback + let cookies = cookieStore.get(uuid) || {}; + + // Falls keine Cookies für UUID vorhanden, versuche Code zu finden + if (Object.keys(cookies).length === 0 && reportData.gameCode) { + cookies = cookieStore.get(reportData.gameCode) || {}; + } + + const url = `https://ttde-apps.liga.nu/nuliga/rs/tt/2022/meetingentry/reports/${uuid}/submit`; + + const response = await fetch(url, { + method: 'PUT', + headers: { + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:141.0) Gecko/20100101 Firefox/141.0', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'de,en-US;q=0.7,en;q=0.3', + 'Accept-Encoding': 'gzip, deflate, br, zstd', + 'Content-Type': 'application/json', + 'Referer': `https://ttde-apps.liga.nu/nuliga/nuscore-tt/meeting/${uuid}/report-clearance/review`, + 'Origin': 'https://ttde-apps.liga.nu', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'Connection': 'keep-alive', + ...(Object.keys(cookies).length > 0 && { 'Cookie': formatCookies(cookies) }) + }, + body: JSON.stringify(reportData) + }); + + const responseText = await response.text(); + let responseData; + + try { + responseData = JSON.parse(responseText); + } catch (e) { + // Falls keine JSON-Antwort, verwende Text + responseData = { message: responseText }; + } + + if (!response.ok) { + console.error(`❌ Submit fehlgeschlagen: HTTP ${response.status}`, responseData); + return res.status(response.status).json({ + error: 'Fehler beim Absenden des Spielberichts', + details: responseData, + status: response.status, + statusText: response.statusText + }); + } + + // Speichere neue Cookies falls vorhanden + const newCookies = extractCookies(response.headers.raw()['set-cookie']); + if (Object.keys(newCookies).length > 0) { + cookieStore.set(uuid, { ...cookies, ...newCookies }); + if (reportData.gameCode) { + cookieStore.set(reportData.gameCode, { ...cookies, ...newCookies }); + } + } + + // CORS-Header setzen + res.set({ + 'Content-Type': 'application/json; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + 'Cache-Control': 'no-cache, no-store, must-revalidate' + }); + + res.json({ + success: true, + data: responseData, + message: 'Spielbericht erfolgreich abgesendet' + }); + + } catch (error) { + console.error(`❌ Fehler beim Absenden des Spielberichts für UUID ${uuid}:`, error); + res.status(500).json({ + error: 'Fehler beim Absenden des Spielberichts', + details: error.message + }); + } +}); + export default router; diff --git a/backend/server.js b/backend/server.js index 1efab7d..d265a5f 100644 --- a/backend/server.js +++ b/backend/server.js @@ -176,6 +176,14 @@ app.get('*', (req, res) => { await renameColumnIfExists('official_tournaments', 'meldeschluesse', 'registration_deadlines', 'TEXT NULL'); const isDev = process.env.STAGE === 'dev'; + + // Foreign Keys temporär deaktivieren für MySQL + try { + await sequelize.query('SET FOREIGN_KEY_CHECKS = 0'); + } catch (e) { + console.warn('[sync] Could not disable foreign key checks:', e?.message); + } + const safeSync = async (model) => { try { if (isDev) { @@ -235,6 +243,15 @@ app.get('*', (req, res) => { await safeSync(ApiLog); await safeSync(MemberTransferConfig); await safeSync(MemberContact); + await safeSync(ClubTeam); + await safeSync(TeamDocument); + + // Foreign Keys wieder aktivieren + try { + await sequelize.query('SET FOREIGN_KEY_CHECKS = 1'); + } catch (e) { + console.warn('[sync] Could not enable foreign key checks:', e?.message); + } // Start scheduler service schedulerService.start(); diff --git a/frontend/src/components/MatchReportApiDialog.vue b/frontend/src/components/MatchReportApiDialog.vue index 35dbe82..2fda516 100644 --- a/frontend/src/components/MatchReportApiDialog.vue +++ b/frontend/src/components/MatchReportApiDialog.vue @@ -113,6 +113,19 @@ +
+ + +
+
{{ (meetingDetails && meetingDetails.playMode) || meetingData.playMode || meetingData.matchSystem || meetingData.system }} @@ -526,8 +539,9 @@ type="password" class="pin-input" :placeholder="match.homePin || 'PIN eingeben'" - :disabled="isMatchCompleted" + :disabled="isMatchCompleted || teamNotAppeared === 'home'" /> + (nicht erforderlich - Mannschaft nicht angetreten)
@@ -538,8 +552,9 @@ type="password" class="pin-input" :placeholder="match.guestPin || 'PIN eingeben'" - :disabled="isMatchCompleted" + :disabled="isMatchCompleted || teamNotAppeared === 'guest'" /> + (nicht erforderlich - Mannschaft nicht angetreten)
@@ -652,7 +667,7 @@