Wiki: Bildrechte-Bestätigung bei Foto-Einreichung (Checkbox + DB + Mod-Badge), SW by-v363

This commit is contained in:
rene 2026-04-25 09:35:06 +02:00
parent de73c7901e
commit d603b7bae1
6 changed files with 42 additions and 7 deletions

View file

@ -532,6 +532,8 @@ def _migrate(conn_factory):
("dogs", "rasse_id", "INTEGER"), ("dogs", "rasse_id", "INTEGER"),
# Pflege: Schere vs. Trimmen unterscheiden # Pflege: Schere vs. Trimmen unterscheiden
("pflege_tipps", "fell_pflege_art", "TEXT"), ("pflege_tipps", "fell_pflege_art", "TEXT"),
# Wiki-Foto-Einreichungen: Bildrechte-Bestätigung
("wiki_foto_submissions", "rights_confirmed", "INTEGER NOT NULL DEFAULT 0"),
] ]
with conn_factory() as conn: with conn_factory() as conn:
for table, column, col_type in migrations: for table, column, col_type in migrations:

View file

@ -173,6 +173,7 @@ async def mod_fotos(user=Depends(require_moderator)):
try: try:
rows = conn.execute(""" rows = conn.execute("""
SELECT s.id, s.rasse_slug, s.foto_url, s.created_at, SELECT s.id, s.rasse_slug, s.foto_url, s.created_at,
COALESCE(s.rights_confirmed, 0) AS rights_confirmed,
u.name AS user_name, u.name AS user_name,
r.name AS rasse_name, r.foto_url AS aktuell_foto r.name AS rasse_name, r.foto_url AS aktuell_foto
FROM wiki_foto_submissions s FROM wiki_foto_submissions s

View file

@ -4,7 +4,7 @@ import os
import shutil import shutil
import time import time
import logging import logging
from fastapi import APIRouter, Depends, HTTPException, Query, Request, UploadFile, File from fastapi import APIRouter, Depends, Form, HTTPException, Query, Request, UploadFile, File
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from pydantic import BaseModel from pydantic import BaseModel
from database import db from database import db
@ -290,6 +290,7 @@ async def quiz_result(
async def submit_foto( async def submit_foto(
slug: str, slug: str,
file: UploadFile = File(...), file: UploadFile = File(...),
rights_confirmed: int = Form(0),
user = Depends(get_current_user), user = Depends(get_current_user),
): ):
with db() as conn: with db() as conn:
@ -299,6 +300,9 @@ async def submit_foto(
if not rasse: if not rasse:
raise HTTPException(404, "Rasse nicht gefunden.") raise HTTPException(404, "Rasse nicht gefunden.")
if not rights_confirmed:
raise HTTPException(400, "Bildrechte-Bestätigung fehlt.")
# Dateiformat prüfen # Dateiformat prüfen
ct = file.content_type or "" ct = file.content_type or ""
if not ct.startswith("image/"): if not ct.startswith("image/"):
@ -336,9 +340,9 @@ async def submit_foto(
) )
conn.execute(""" conn.execute("""
INSERT INTO wiki_foto_submissions (user_id, rasse_id, foto_url) INSERT INTO wiki_foto_submissions (user_id, rasse_id, foto_url, rights_confirmed)
VALUES (?,?,?) VALUES (?,?,?,?)
""", (user["id"], rasse["id"], local_url)) """, (user["id"], rasse["id"], local_url, 1))
logger.info(f"Foto-Einreichung: {rasse['name']} von User {user['id']}") logger.info(f"Foto-Einreichung: {rasse['name']} von User {user['id']}")
return {"ok": True, "foto_url": local_url} return {"ok": True, "foto_url": local_url}

View file

@ -172,9 +172,16 @@ window.Page_moderation = (() => {
${_esc(f.rasse_name || f.rasse_slug)} ${_esc(f.rasse_name || f.rasse_slug)}
</div> </div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted); <div style="font-size:var(--text-xs);color:var(--c-text-muted);
margin-bottom:var(--space-3)"> margin-bottom:var(--space-2)">
von ${_esc(f.user_name)} von ${_esc(f.user_name)}
</div> </div>
<div style="margin-bottom:var(--space-3)">
${f.rights_confirmed
? `<span style="font-size:10px;font-weight:700;padding:2px 8px;border-radius:20px;
background:#dcfce7;color:#166534"> Bildrechte bestätigt</span>`
: `<span style="font-size:10px;font-weight:700;padding:2px 8px;border-radius:20px;
background:#fef9c3;color:#92400e"> Keine Bestätigung</span>`}
</div>
${f.aktuell_foto ? ` ${f.aktuell_foto ? `
<img src="${_esc(f.aktuell_foto)}" alt="Aktuell" <img src="${_esc(f.aktuell_foto)}" alt="Aktuell"
style="width:100%;height:80px;object-fit:cover; style="width:100%;height:80px;object-fit:cover;

View file

@ -847,11 +847,23 @@ window.Page_wiki = (() => {
<div id="wiki-foto-preview" style="margin-top:var(--space-3);display:none"> <div id="wiki-foto-preview" style="margin-top:var(--space-3);display:none">
<img id="wiki-foto-preview-img" style="max-width:100%;max-height:200px;border-radius:var(--radius-md);object-fit:contain"> <img id="wiki-foto-preview-img" style="max-width:100%;max-height:200px;border-radius:var(--radius-md);object-fit:contain">
</div> </div>
<div style="margin-top:var(--space-4);padding:var(--space-3);
background:var(--c-surface-2);border-radius:var(--radius-md);
border-left:3px solid var(--c-warning)">
<label style="display:flex;gap:var(--space-3);align-items:flex-start;cursor:pointer">
<input type="checkbox" id="wiki-foto-rights" style="margin-top:3px;flex-shrink:0">
<span style="font-size:var(--text-sm);line-height:1.5">
Ich bestätige, dass ich die <strong>uneingeschränkten Bildrechte</strong> an diesem Foto besitze
und banyaro.app das dauerhaft gültige, kostenlose Recht einräume, es zu veröffentlichen und zu
verwenden. Fotos aus dem Internet oder von Dritten dürfen nicht eingereicht werden.
</span>
</label>
</div>
</form> </form>
`; `;
const footer = ` const footer = `
<button type="button" class="btn btn-secondary flex-1" id="wiki-foto-cancel">Abbrechen</button> <button type="button" class="btn btn-secondary flex-1" id="wiki-foto-cancel">Abbrechen</button>
<button type="submit" form="wiki-foto-form" class="btn btn-primary flex-1" id="wiki-foto-submit"> <button type="submit" form="wiki-foto-form" class="btn btn-primary flex-1" id="wiki-foto-submit" disabled>
${UI.icon('paper-plane-tilt')} Einreichen ${UI.icon('paper-plane-tilt')} Einreichen
</button> </button>
`; `;
@ -859,6 +871,10 @@ window.Page_wiki = (() => {
UI.modal.open({ title: 'Foto vorschlagen', body, footer }); UI.modal.open({ title: 'Foto vorschlagen', body, footer });
document.getElementById('wiki-foto-cancel')?.addEventListener('click', UI.modal.close); document.getElementById('wiki-foto-cancel')?.addEventListener('click', UI.modal.close);
document.getElementById('wiki-foto-rights')?.addEventListener('change', e => {
document.getElementById('wiki-foto-submit').disabled = !e.target.checked;
});
document.getElementById('wiki-foto-input')?.addEventListener('change', e => { document.getElementById('wiki-foto-input')?.addEventListener('change', e => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (!file) return; if (!file) return;
@ -874,6 +890,10 @@ window.Page_wiki = (() => {
const input = document.getElementById('wiki-foto-input'); const input = document.getElementById('wiki-foto-input');
const file = input?.files?.[0]; const file = input?.files?.[0];
if (!file) return; if (!file) return;
if (!document.getElementById('wiki-foto-rights')?.checked) {
UI.toast('Bitte Bildrechte bestätigen.', 'danger');
return;
}
const btn = document.getElementById('wiki-foto-submit'); const btn = document.getElementById('wiki-foto-submit');
btn.disabled = true; btn.disabled = true;
@ -882,6 +902,7 @@ window.Page_wiki = (() => {
try { try {
const fd = new FormData(); const fd = new FormData();
fd.append('file', file); fd.append('file', file);
fd.append('rights_confirmed', '1');
const token = localStorage.getItem('by_token'); const token = localStorage.getItem('by_token');
const resp = await fetch(`/api/wiki/rassen/${encodeURIComponent(slug)}/foto`, { const resp = await fetch(`/api/wiki/rassen/${encodeURIComponent(slug)}/foto`, {
method: 'POST', method: 'POST',

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache Offline-Cache + Push Notifications + Tile-Cache
============================================================ */ ============================================================ */
const CACHE_VERSION = 'by-v362'; const CACHE_VERSION = 'by-v363';
const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten