DGSVO Compliance update 1 #30
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,32 +422,62 @@ 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 || {}
|
||||||
|
const body = {}
|
||||||
|
if (p.edit_metadata) {
|
||||||
|
body.original_filename = modalDraft.display_name
|
||||||
|
body.copyright_notice = modalDraft.copyright_notice
|
||||||
|
body.tags = parseTagsInput(modalDraft.tags_input)
|
||||||
|
}
|
||||||
|
if (p.change_visibility) {
|
||||||
|
body.visibility = modalDraft.visibility
|
||||||
|
if (modalDraft.visibility === 'club' || (modalDraft.visibility === 'private' && isPlatformAdmin)) {
|
||||||
|
const cid = Number(modalDraft.club_id)
|
||||||
|
if (!cid) {
|
||||||
|
alert('Bitte einen Verein wählen.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body.club_id = cid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!Object.keys(body).length) return
|
||||||
setBusy(true)
|
setBusy(true)
|
||||||
try {
|
try {
|
||||||
const body = {}
|
await api.patchMediaAsset(modal.id, body)
|
||||||
if (p.edit_metadata) {
|
closeModal()
|
||||||
body.original_filename = modalDraft.display_name
|
await loadItems()
|
||||||
body.copyright_notice = modalDraft.copyright_notice
|
} catch (e) {
|
||||||
body.tags = parseTagsInput(modalDraft.tags_input)
|
const parsed = parseApiErrorCode(e.message)
|
||||||
}
|
if (parsed && (parsed.code === 'RIGHTS_SCOPE_INSUFFICIENT' || parsed.code === 'LEGACY_REDECLARATION_REQUIRED')) {
|
||||||
if (p.change_visibility) {
|
setPendingPatchBody(body)
|
||||||
body.visibility = modalDraft.visibility
|
setRightsUpgradeMode(parsed.code === 'LEGACY_REDECLARATION_REQUIRED' ? 'redeclaration' : 'promotion')
|
||||||
if (modalDraft.visibility === 'club' || (modalDraft.visibility === 'private' && isPlatformAdmin)) {
|
setRightsUpgradeDialogOpen(true)
|
||||||
const cid = Number(modalDraft.club_id)
|
return
|
||||||
if (!cid) {
|
|
||||||
alert('Bitte einen Verein wählen.')
|
|
||||||
setBusy(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
body.club_id = cid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Object.keys(body).length) {
|
|
||||||
await api.patchMediaAsset(modal.id, body)
|
|
||||||
}
|
}
|
||||||
|
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">
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user