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"),
# Pflege: Schere vs. Trimmen unterscheiden
("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:
for table, column, col_type in migrations:

View file

@ -173,6 +173,7 @@ async def mod_fotos(user=Depends(require_moderator)):
try:
rows = conn.execute("""
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,
r.name AS rasse_name, r.foto_url AS aktuell_foto
FROM wiki_foto_submissions s

View file

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

View file

@ -172,9 +172,16 @@ window.Page_moderation = (() => {
${_esc(f.rasse_name || f.rasse_slug)}
</div>
<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)}
</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 ? `
<img src="${_esc(f.aktuell_foto)}" alt="Aktuell"
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">
<img id="wiki-foto-preview-img" style="max-width:100%;max-height:200px;border-radius:var(--radius-md);object-fit:contain">
</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>
`;
const footer = `
<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
</button>
`;
@ -859,6 +871,10 @@ window.Page_wiki = (() => {
UI.modal.open({ title: 'Foto vorschlagen', body, footer });
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 => {
const file = e.target.files?.[0];
if (!file) return;
@ -874,6 +890,10 @@ window.Page_wiki = (() => {
const input = document.getElementById('wiki-foto-input');
const file = input?.files?.[0];
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');
btn.disabled = true;
@ -882,6 +902,7 @@ window.Page_wiki = (() => {
try {
const fd = new FormData();
fd.append('file', file);
fd.append('rights_confirmed', '1');
const token = localStorage.getItem('by_token');
const resp = await fetch(`/api/wiki/rassen/${encodeURIComponent(slug)}/foto`, {
method: 'POST',

View file

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