DGSVO Compliance update 1 #30

Merged
Lars merged 48 commits from develop into main 2026-05-12 06:34:15 +02:00
5 changed files with 91 additions and 31 deletions
Showing only changes of commit 6586d3b68b - Show all commits

View File

@ -1,6 +1,6 @@
# Shinkan Jinkendo Version Information # Shinkan Jinkendo Version Information
APP_VERSION = "0.8.78" APP_VERSION = "0.8.80"
BUILD_DATE = "2026-05-11" BUILD_DATE = "2026-05-11"
DB_SCHEMA_VERSION = "20260511048" DB_SCHEMA_VERSION = "20260511048"
@ -31,6 +31,20 @@ MODULE_VERSIONS = {
} }
CHANGELOG = [ CHANGELOG = [
{
"version": "0.8.80",
"date": "2026-05-11",
"changes": [
"Fix P-06: Bei Sichtbarkeits-Promotion (private→club, club→official) oeffnet das Frontend bei RIGHTS_SCOPE_INSUFFICIENT / LEGACY_REDECLARATION_REQUIRED automatisch den Einwilligungsdialog (promotion/redeclaration-Modus) statt eines alert()-Fehlers; PATCH wird nach Bestaetigung mit P-06-Feldern wiederholt.",
],
},
{
"version": "0.8.79",
"date": "2026-05-11",
"changes": [
"Fix: profiles.username -> profiles.name in Journal-Endpoint (500er behoben); RightsDeclarationDialog: doppelter Abbrechen-Button entfernt (Header-X statt Text); Playwright P-01: Platzhalter-Pruefung optional (echtes Impressum deployed); Playwright P-06c: Selector-Konflikt durch UI-Fix beseitigt.",
],
},
{ {
"version": "0.8.78", "version": "0.8.78",
"date": "2026-05-11", "date": "2026-05-11",

View File

@ -103,8 +103,8 @@ export default function RightsDeclarationDialog({
<h3 id="rights-decl-title" className="admin-modal-sheet__title"> <h3 id="rights-decl-title" className="admin-modal-sheet__title">
{titleMap[mode] || titleMap.upload} {titleMap[mode] || titleMap.upload}
</h3> </h3>
<button type="button" className="btn btn-secondary admin-modal-sheet__close" onClick={handleCancel}> <button type="button" className="admin-modal-sheet__close" onClick={handleCancel} aria-label="Schließen">
Abbrechen ×
</button> </button>
</div> </div>

View File

@ -283,9 +283,13 @@ export default function MediaLibraryPage() {
const [uploadClubId, setUploadClubId] = useState('') const [uploadClubId, setUploadClubId] = useState('')
const [uploadBusy, setUploadBusy] = useState(false) const [uploadBusy, setUploadBusy] = useState(false)
const [uploadSummary, setUploadSummary] = useState('') const [uploadSummary, setUploadSummary] = useState('')
// P-06: Rechte-Dialog // P-06: Rechte-Dialog (Upload)
const [rightsDialogOpen, setRightsDialogOpen] = useState(false) const [rightsDialogOpen, setRightsDialogOpen] = useState(false)
const [pendingUploadFiles, setPendingUploadFiles] = useState(null) const [pendingUploadFiles, setPendingUploadFiles] = useState(null)
// P-06: Rechte-Dialog (Sichtbarkeits-Promotion)
const [rightsUpgradeDialogOpen, setRightsUpgradeDialogOpen] = useState(false)
const [rightsUpgradeMode, setRightsUpgradeMode] = useState('promotion')
const [pendingPatchBody, setPendingPatchBody] = useState(null)
const [journalModal, setJournalModal] = useState(null) const [journalModal, setJournalModal] = useState(null)
const [journalLoading, setJournalLoading] = useState(false) const [journalLoading, setJournalLoading] = useState(false)
const mediaListFetchSeqRef = useRef(0) const mediaListFetchSeqRef = useRef(0)
@ -418,11 +422,17 @@ export default function MediaLibraryPage() {
} }
} }
function parseApiErrorCode(msg) {
try {
const d = JSON.parse(msg)
if (d && d.code) return d
} catch { /* not JSON */ }
return null
}
const saveModal = async () => { const saveModal = async () => {
if (!modal || !modalDraft) return if (!modal || !modalDraft) return
const p = modal.permissions || {} const p = modal.permissions || {}
setBusy(true)
try {
const body = {} const body = {}
if (p.edit_metadata) { if (p.edit_metadata) {
body.original_filename = modalDraft.display_name body.original_filename = modalDraft.display_name
@ -435,15 +445,39 @@ export default function MediaLibraryPage() {
const cid = Number(modalDraft.club_id) const cid = Number(modalDraft.club_id)
if (!cid) { if (!cid) {
alert('Bitte einen Verein wählen.') alert('Bitte einen Verein wählen.')
setBusy(false)
return return
} }
body.club_id = cid body.club_id = cid
} }
} }
if (Object.keys(body).length) { if (!Object.keys(body).length) return
setBusy(true)
try {
await api.patchMediaAsset(modal.id, body) await api.patchMediaAsset(modal.id, body)
closeModal()
await loadItems()
} catch (e) {
const parsed = parseApiErrorCode(e.message)
if (parsed && (parsed.code === 'RIGHTS_SCOPE_INSUFFICIENT' || parsed.code === 'LEGACY_REDECLARATION_REQUIRED')) {
setPendingPatchBody(body)
setRightsUpgradeMode(parsed.code === 'LEGACY_REDECLARATION_REQUIRED' ? 'redeclaration' : 'promotion')
setRightsUpgradeDialogOpen(true)
return
} }
alert(e.message || String(e))
} finally {
setBusy(false)
}
}
const doSaveWithRightsDecl = async (decl) => {
setRightsUpgradeDialogOpen(false)
if (!modal || !pendingPatchBody) return
const body = { ...pendingPatchBody, ...decl }
setPendingPatchBody(null)
setBusy(true)
try {
await api.patchMediaAsset(modal.id, body)
closeModal() closeModal()
await loadItems() await loadItems()
} catch (e) { } catch (e) {
@ -589,6 +623,14 @@ export default function MediaLibraryPage() {
targetVisibility={uploadVis} targetVisibility={uploadVis}
mode="upload" mode="upload"
/> />
<RightsDeclarationDialog
open={rightsUpgradeDialogOpen}
onCancel={() => { setRightsUpgradeDialogOpen(false); setPendingPatchBody(null) }}
onConfirm={doSaveWithRightsDecl}
targetVisibility={pendingPatchBody?.visibility || modal?.visibility || 'club'}
isPromotion={true}
mode={rightsUpgradeMode}
/>
<div className="media-library__container"> <div className="media-library__container">
<header className="media-library__hero"> <header className="media-library__hero">
<div className="media-library__hero-row"> <div className="media-library__hero-row">

View File

@ -1,6 +1,6 @@
// Shinkan Jinkendo Frontend Version // Shinkan Jinkendo Frontend Version
export const APP_VERSION = "0.8.78" export const APP_VERSION = "0.8.80"
export const BUILD_DATE = "2026-05-11" export const BUILD_DATE = "2026-05-11"
export const PAGE_VERSIONS = { export const PAGE_VERSIONS = {
@ -20,7 +20,7 @@ export const PAGE_VERSIONS = {
TrainingCoachPage: "1.0.0", TrainingCoachPage: "1.0.0",
AdminCatalogsPage: "2.2.0", AdminCatalogsPage: "2.2.0",
TrainerContextsPage: "1.0.0", TrainerContextsPage: "1.0.0",
MediaLibraryPage: "1.3.0", // P-06: Superadmin Medienjournal-Modal MediaLibraryPage: "1.4.0", // P-06: Rechte-Dialog bei Sichtbarkeits-Promotion
ExerciseInlineFileMediaModal: "1.1.0", // P-06: RightsDeclarationDialog vor Upload ExerciseInlineFileMediaModal: "1.1.0", // P-06: RightsDeclarationDialog vor Upload
ExerciseInlineEmbedModal: "1.1.0", // P-06: RightsDeclarationDialog vor Embed-Upload ExerciseInlineEmbedModal: "1.1.0", // P-06: RightsDeclarationDialog vor Embed-Upload
} }

View File

@ -204,19 +204,23 @@ for (const route of LEGAL_ROUTES) {
// Seite ist erreichbar (kein Redirect zur Login-Seite) // Seite ist erreichbar (kein Redirect zur Login-Seite)
expect(page.url()).toContain(route.path); expect(page.url()).toContain(route.path);
// Platzhalterhinweis sichtbar
const hinweis = await page.getByText('MUSTER / PLATZHALTER').first();
await expect(hinweis).toBeVisible();
// Seitentitel korrekt // Seitentitel korrekt
await expect(page.getByRole('heading', { level: 1 })).toContainText(route.label); await expect(page.getByRole('heading', { level: 1 })).toContainText(route.label);
// Platzhalterhinweis sichtbar nur wenn noch kein echtes Dokument veröffentlicht wurde
const hasPlaceholder = await page.getByText('MUSTER / PLATZHALTER').first().isVisible().catch(() => false);
if (hasPlaceholder) {
console.log(`✓ P-01: ${route.label} Platzhalter sichtbar`);
} else {
console.log(`✓ P-01: ${route.label} echtes Dokument veröffentlicht (kein Platzhalter)`);
}
// Reload funktioniert // Reload funktioniert
await page.reload(); await page.reload();
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
expect(page.url()).toContain(route.path); expect(page.url()).toContain(route.path);
console.log(`✓ P-01: ${route.label} ohne Auth erreichbar, Platzhalter sichtbar, Reload OK`); console.log(`✓ P-01: ${route.label} ohne Auth erreichbar, Reload OK`);
}); });
} }