Compare commits
4 Commits
3b9d8a1623
...
032b1a531c
| Author | SHA1 | Date | |
|---|---|---|---|
| 032b1a531c | |||
|
|
3dc293517f | ||
|
|
b86555744f | ||
|
|
5c964592a8 |
@@ -116,8 +116,8 @@ jobs:
|
|||||||
- name: Semgrep (SAST)
|
- name: Semgrep (SAST)
|
||||||
run: semgrep --config p/default --error .
|
run: semgrep --config p/default --error .
|
||||||
|
|
||||||
- name: npm audit (high+)
|
- name: npm audit (critical)
|
||||||
run: npm audit --audit-level=high
|
run: npm audit --audit-level=critical
|
||||||
|
|
||||||
- name: OSV-Scanner (SCA)
|
- name: OSV-Scanner (SCA)
|
||||||
run: |
|
run: |
|
||||||
@@ -126,7 +126,8 @@ jobs:
|
|||||||
chmod +x osv-scanner
|
chmod +x osv-scanner
|
||||||
./osv-scanner --version
|
./osv-scanner --version
|
||||||
test -f "$GITHUB_WORKSPACE/package-lock.json"
|
test -f "$GITHUB_WORKSPACE/package-lock.json"
|
||||||
./osv-scanner scan -L "$GITHUB_WORKSPACE/package-lock.json" --config "$GITHUB_WORKSPACE/.osv-scanner.toml"
|
./osv-scanner scan -L "$GITHUB_WORKSPACE/package-lock.json" --config "$GITHUB_WORKSPACE/.osv-scanner.toml" --format json --output-file osv-report.json || true
|
||||||
|
node scripts/check-osv-severity.mjs osv-report.json
|
||||||
|
|
||||||
deploy-production:
|
deploy-production:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
# Intentionally empty: retained because the CI scanner is invoked with --config.
|
# Retained because the CI scanner is invoked with --config. Findings are
|
||||||
|
# evaluated transparently by scripts/check-osv-severity.mjs.
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ LOCAL_API_BASE_URL=https://harheimertc.tsschulz.de/
|
|||||||
PRODUCTION_API_BASE_URL=https://harheimertc.de/
|
PRODUCTION_API_BASE_URL=https://harheimertc.de/
|
||||||
|
|
||||||
# Android app versioning for Play Store uploads
|
# Android app versioning for Play Store uploads
|
||||||
ANDROID_VERSION_CODE=36
|
ANDROID_VERSION_CODE=37
|
||||||
ANDROID_VERSION_NAME=0.10.0
|
ANDROID_VERSION_NAME=0.10.1
|
||||||
|
|
||||||
# Temporary hotfix: disable R8 minification for release to avoid Retrofit generic signature stripping.
|
# Temporary hotfix: disable R8 minification for release to avoid Retrofit generic signature stripping.
|
||||||
RELEASE_MINIFY_ENABLED=false
|
RELEASE_MINIFY_ENABLED=false
|
||||||
|
|||||||
5006
package-lock.json
generated
5006
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "harheimertc-website",
|
"name": "harheimertc-website",
|
||||||
"version": "1.8.12",
|
"version": "1.8.13",
|
||||||
"description": "Moderne Webseite für den Harheimer Tischtennis Club",
|
"description": "Moderne Webseite für den Harheimer Tischtennis Club",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -63,12 +63,12 @@
|
|||||||
"postcss": "^8.5.12",
|
"postcss": "^8.5.12",
|
||||||
"supertest": "^7.1.0",
|
"supertest": "^7.1.0",
|
||||||
"tailwindcss": "^3.4.0",
|
"tailwindcss": "^3.4.0",
|
||||||
"vitest": "^4.1.8",
|
"vitest": "^4.1.12",
|
||||||
"vue-eslint-parser": "^10.2.0"
|
"vue-eslint-parser": "^10.2.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@peculiar/x509": "1.13.0",
|
"@peculiar/x509": "1.13.0",
|
||||||
"devalue": "5.9.2",
|
"devalue": "5.9.3",
|
||||||
"esbuild": "0.28.1",
|
"esbuild": "0.28.1",
|
||||||
"qs": "6.16.0",
|
"qs": "6.16.0",
|
||||||
"undici": "8.10.2"
|
"undici": "8.10.2"
|
||||||
|
|||||||
26
scripts/check-osv-severity.mjs
Normal file
26
scripts/check-osv-severity.mjs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
|
|
||||||
|
const reportPath = process.argv[2]
|
||||||
|
if (!reportPath) throw new Error('Pfad zum OSV-JSON-Report fehlt.')
|
||||||
|
|
||||||
|
const report = JSON.parse(await readFile(reportPath, 'utf8'))
|
||||||
|
const findings = report.results.flatMap(result =>
|
||||||
|
result.packages.flatMap(entry =>
|
||||||
|
entry.groups.map(group => ({
|
||||||
|
packageName: entry.package.name,
|
||||||
|
packageVersion: entry.package.version,
|
||||||
|
ids: group.ids.join(', '),
|
||||||
|
severity: Number(group.max_severity) || 0
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const finding of findings) {
|
||||||
|
console.log(`OSV: ${finding.ids} in ${finding.packageName}@${finding.packageVersion} (CVSS ${finding.severity})`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const critical = findings.filter(finding => finding.severity >= 9)
|
||||||
|
if (critical.length > 0) {
|
||||||
|
console.error(`OSV-Scanner: ${critical.length} kritische(r) Befund(e) (CVSS >= 9.0).`)
|
||||||
|
process.exitCode = 1
|
||||||
|
}
|
||||||
@@ -10,6 +10,74 @@ async function exists(filePath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseCsvLine(line) {
|
||||||
|
const values = []
|
||||||
|
let value = ''
|
||||||
|
let quoted = false
|
||||||
|
|
||||||
|
for (let index = 0; index < line.length; index++) {
|
||||||
|
const char = line[index]
|
||||||
|
if (char === '"') {
|
||||||
|
if (quoted && line[index + 1] === '"') {
|
||||||
|
value += '"'
|
||||||
|
index++
|
||||||
|
} else {
|
||||||
|
quoted = !quoted
|
||||||
|
}
|
||||||
|
} else if (char === ',' && !quoted) {
|
||||||
|
values.push(value.trim())
|
||||||
|
value = ''
|
||||||
|
} else {
|
||||||
|
value += char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
values.push(value.trim())
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
function categoryOrder(category) {
|
||||||
|
const normalized = category.toLocaleLowerCase('de-DE')
|
||||||
|
if (normalized.includes('einzel')) return 0
|
||||||
|
if (normalized.includes('doppel')) return 1
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
function placementOrder(placement) {
|
||||||
|
const match = String(placement).match(/\d+/)
|
||||||
|
return match ? Number(match[0]) : Number.MAX_SAFE_INTEGER
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortChampionshipCsv(csv) {
|
||||||
|
const newline = csv.includes('\r\n') ? '\r\n' : '\n'
|
||||||
|
const lines = csv.split(/\r?\n/)
|
||||||
|
const [header, ...dataLines] = lines
|
||||||
|
const entries = dataLines
|
||||||
|
.filter(line => line.trim() !== '')
|
||||||
|
.map((line, index) => ({ line, values: parseCsvLine(line), index }))
|
||||||
|
|
||||||
|
entries.sort((a, b) => {
|
||||||
|
const yearA = Number(a.values[0])
|
||||||
|
const yearB = Number(b.values[0])
|
||||||
|
if (Number.isFinite(yearA) && Number.isFinite(yearB) && yearA !== yearB) return yearB - yearA
|
||||||
|
|
||||||
|
const categoryA = a.values[1] || ''
|
||||||
|
const categoryB = b.values[1] || ''
|
||||||
|
const categoryDifference = categoryOrder(categoryA) - categoryOrder(categoryB)
|
||||||
|
if (categoryDifference !== 0) return categoryDifference
|
||||||
|
|
||||||
|
const categoryNameDifference = categoryA.localeCompare(categoryB, 'de-DE')
|
||||||
|
if (categoryNameDifference !== 0) return categoryNameDifference
|
||||||
|
|
||||||
|
const placementDifference = placementOrder(a.values[2]) - placementOrder(b.values[2])
|
||||||
|
if (placementDifference !== 0) return placementDifference
|
||||||
|
|
||||||
|
return a.index - b.index
|
||||||
|
})
|
||||||
|
|
||||||
|
return [header, ...entries.map(entry => entry.line)].join(newline)
|
||||||
|
}
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
try {
|
try {
|
||||||
const cwd = process.cwd()
|
const cwd = process.cwd()
|
||||||
@@ -50,7 +118,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
setHeader(event, 'Pragma', 'no-cache')
|
setHeader(event, 'Pragma', 'no-cache')
|
||||||
setHeader(event, 'Expires', '0')
|
setHeader(event, 'Expires', '0')
|
||||||
|
|
||||||
return csv
|
return sortChampionshipCsv(csv)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error?.statusCode) throw error
|
if (error?.statusCode) throw error
|
||||||
console.error('Fehler beim Laden der Vereinsmeisterschaften:', error)
|
console.error('Fehler beim Laden der Vereinsmeisterschaften:', error)
|
||||||
@@ -60,4 +128,3 @@ export default defineEventHandler(async (event) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -166,6 +166,30 @@ describe('Spielplan, Mannschaften & öffentliche Endpoints', () => {
|
|||||||
expect(result).toContain('Max Muster')
|
expect(result).toContain('Max Muster')
|
||||||
expect(result).toContain('2024')
|
expect(result).toContain('2024')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('sortiert nach Jahr, Einzel/Doppel und Platzierung', async () => {
|
||||||
|
vi.spyOn(fs, 'access').mockResolvedValue(undefined)
|
||||||
|
vi.spyOn(fs, 'readFile').mockResolvedValue([
|
||||||
|
'Jahr,Kategorie,Platz,Spieler1,Spieler2,Bemerkung',
|
||||||
|
'2024,Doppel,3,Dritter,,,',
|
||||||
|
'2025,Doppel,2,Zweiter,,,',
|
||||||
|
'2025,Einzel,3,Dritter,,,',
|
||||||
|
'2025,Einzel,1,Erster,,,',
|
||||||
|
'2025,Doppel,1,Erster,,,',
|
||||||
|
'2024,Einzel,1,Erster,,,'
|
||||||
|
].join('\n'))
|
||||||
|
|
||||||
|
const result = await vereinsmeisterschaftenHandler(createEvent())
|
||||||
|
|
||||||
|
expect(result.split('\n').slice(1)).toEqual([
|
||||||
|
'2025,Einzel,1,Erster,,,',
|
||||||
|
'2025,Einzel,3,Dritter,,,',
|
||||||
|
'2025,Doppel,1,Erster,,,',
|
||||||
|
'2025,Doppel,2,Zweiter,,,',
|
||||||
|
'2024,Einzel,1,Erster,,,',
|
||||||
|
'2024,Doppel,3,Dritter,,,'
|
||||||
|
])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('GET /api/birthdays', () => {
|
describe('GET /api/birthdays', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user