diff --git a/Dockerfile b/Dockerfile
index 5707b44..623ddef 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -16,7 +16,8 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY backend/ .
# Media-Verzeichnis
-RUN mkdir -p /data/media/dogs /data/media/diary /data/media/poison
+RUN mkdir -p /data/media/dogs /data/media/diary /data/media/poison \
+ /data/media/breeds/gallery /data/media/breeds/submissions
EXPOSE 8000
diff --git a/backend/routes/achievements.py b/backend/routes/achievements.py
index dae91ff..1c0c1e8 100644
--- a/backend/routes/achievements.py
+++ b/backend/routes/achievements.py
@@ -80,6 +80,18 @@ CATEGORIES = [
("diamant", 365, "Ein ganzes Jahr"),
],
},
+ {
+ "id": "wiki_fotos",
+ "name": "Wiki-Fotos",
+ "emoji": "📸",
+ "metrik": "wiki_fotos",
+ "einheit": " Foto(s)",
+ "stufen": [
+ ("bronze", 1, "Erster Klick"),
+ ("silber", 3, "Foto-Fan"),
+ ("gold", 10, "Wiki-Fotograf"),
+ ],
+ },
]
# Flat-Liste aller Badge-IDs für DB-Kompatibilität
@@ -129,19 +141,21 @@ def check_and_award(user_id: int, conn):
COALESCE((SELECT SUM(w.walked_km) FROM route_walks w WHERE w.user_id=?), 0),
1) AS total_km,
(SELECT COUNT(*) FROM routes r WHERE r.user_id=? AND r.is_valid=1) AS routen,
- (SELECT COUNT(*) FROM user_map_pois p WHERE p.user_id=?) AS pois
+ (SELECT COUNT(*) FROM user_map_pois p WHERE p.user_id=?) AS pois,
+ (SELECT COUNT(*) FROM wiki_foto_submissions WHERE user_id=? AND status='approved') AS wiki_fotos
FROM (SELECT 1)
- """, (user_id, user_id, user_id, user_id)).fetchone()
+ """, (user_id, user_id, user_id, user_id, user_id)).fetchone()
streak_row = conn.execute(
"SELECT current_streak FROM users WHERE id=?", (user_id,)
).fetchone()
metrics = {
- "total_km": stats["total_km"] if stats else 0,
- "routen": stats["routen"] if stats else 0,
- "pois": stats["pois"] if stats else 0,
- "streak": (streak_row["current_streak"] if streak_row else 0),
+ "total_km": stats["total_km"] if stats else 0,
+ "routen": stats["routen"] if stats else 0,
+ "pois": stats["pois"] if stats else 0,
+ "streak": (streak_row["current_streak"] if streak_row else 0),
+ "wiki_fotos": stats["wiki_fotos"] if stats else 0,
}
earned = {r["badge_id"] for r in
@@ -183,6 +197,7 @@ async def my_achievements(user=Depends(get_current_user)):
1) AS total_km,
(SELECT COUNT(*) FROM routes r WHERE r.user_id=? AND r.is_valid=1) AS routen,
(SELECT COUNT(*) FROM user_map_pois p WHERE p.user_id=?) AS pois,
+ (SELECT COUNT(*) FROM wiki_foto_submissions WHERE user_id=? AND status='approved') AS wiki_fotos,
ROUND(
COALESCE((SELECT SUM(r.distanz_km) FROM routes r WHERE r.user_id=? AND r.is_valid=1), 0) +
COALESCE((SELECT SUM(w.walked_km) FROM route_walks w WHERE w.user_id=?), 0),
@@ -190,7 +205,7 @@ async def my_achievements(user=Depends(get_current_user)):
+ (SELECT COUNT(*) FROM user_map_pois p WHERE p.user_id=?)*5
+ (SELECT COUNT(*) FROM routes r WHERE r.user_id=? AND r.is_valid=1)*10 AS punkte
FROM (SELECT 1)
- """, (uid, uid, uid, uid, uid, uid, uid, uid)).fetchone()
+ """, (uid, uid, uid, uid, uid, uid, uid, uid, uid)).fetchone()
streak_row = conn.execute(
"SELECT current_streak, max_streak FROM users WHERE id=?", (uid,)
@@ -215,10 +230,11 @@ async def my_achievements(user=Depends(get_current_user)):
""", (stats["punkte"] if stats else 0,)).fetchone()
metrics = {
- "total_km": stats["total_km"] if stats else 0,
- "routen": stats["routen"] if stats else 0,
- "pois": stats["pois"] if stats else 0,
- "streak": (streak_row["current_streak"] if streak_row else 0),
+ "total_km": stats["total_km"] if stats else 0,
+ "routen": stats["routen"] if stats else 0,
+ "pois": stats["pois"] if stats else 0,
+ "streak": (streak_row["current_streak"] if streak_row else 0),
+ "wiki_fotos": stats["wiki_fotos"] if stats else 0,
}
# Kategorien mit aktuellem Tier + Fortschritt aufbauen
diff --git a/backend/routes/wiki.py b/backend/routes/wiki.py
index ecb7819..83093d7 100644
--- a/backend/routes/wiki.py
+++ b/backend/routes/wiki.py
@@ -12,9 +12,10 @@ from auth import get_current_user, get_current_user_optional
from ratelimit import check as rl_check, block_ip
logger = logging.getLogger(__name__)
-MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")
-BREEDS_DIR = os.path.join(MEDIA_DIR, "breeds")
-SUBMIT_DIR = os.path.join(BREEDS_DIR, "submissions")
+MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")
+BREEDS_DIR = os.path.join(MEDIA_DIR, "breeds")
+SUBMIT_DIR = os.path.join(BREEDS_DIR, "submissions")
+GALLERY_DIR = os.path.join(BREEDS_DIR, "gallery")
router = APIRouter()
@@ -119,7 +120,10 @@ async def get_rassen(
with db() as conn:
rows = conn.execute(f"""
SELECT id, name, gruppe, groesse, aktivitaet, erfahrung,
- foto_url, slug, kinder_geeignet, wohnung_geeignet
+ foto_url, slug, kinder_geeignet, wohnung_geeignet,
+ (SELECT s.foto_url FROM wiki_foto_submissions s
+ WHERE s.rasse_id = wiki_rassen.id AND s.status='approved'
+ ORDER BY s.reviewed_at DESC LIMIT 1) AS user_foto
FROM wiki_rassen
{where}
ORDER BY name ASC
@@ -166,8 +170,18 @@ async def get_rasse(rasse_slug: str, request: Request):
(rasse_slug,),
).fetchall()
+ user_fotos = conn.execute("""
+ SELECT s.foto_url, u.name AS user_name, s.created_at
+ FROM wiki_foto_submissions s
+ JOIN users u ON u.id = s.user_id
+ WHERE s.rasse_id = ? AND s.status = 'approved'
+ ORDER BY s.reviewed_at DESC
+ LIMIT 10
+ """, (rasse["id"],)).fetchall()
+
result = dict(rasse)
- result["berichte"] = [dict(r) for r in rows]
+ result["berichte"] = [dict(r) for r in rows]
+ result["user_fotos"] = [dict(r) for r in user_fotos]
return result
@@ -400,47 +414,61 @@ async def review_submission(sub_id: int, data: ReviewModel, user=Depends(get_cur
).fetchone()
if data.action == "approve":
- # Ziel-Dateiname aus external_id ableiten
- ext_id = rasse["external_id"] if rasse else None
- if ext_id and str(ext_id).startswith("wd_"):
- qid = str(ext_id).replace("wd_", "")
- dest_name = f"{qid}.jpg"
- elif ext_id:
- dest_name = f"{ext_id}.jpg"
- else:
- dest_name = f"{rasse['slug']}.jpg"
-
- os.makedirs(BREEDS_DIR, exist_ok=True)
- src = sub["foto_url"].replace("/media/", MEDIA_DIR + "/", 1)
- dest = os.path.join(BREEDS_DIR, dest_name)
+ # Ins gallery-Verzeichnis verschieben
+ os.makedirs(GALLERY_DIR, exist_ok=True)
+ src = sub["foto_url"].replace("/media/", MEDIA_DIR + "/", 1)
+ dest_name = f"{rasse['slug']}_{sub_id}.jpg"
+ dest = os.path.join(GALLERY_DIR, dest_name)
try:
shutil.copy2(src, dest)
except Exception as e:
raise HTTPException(500, f"Datei konnte nicht kopiert werden: {e}")
- new_url = f"/media/breeds/{dest_name}"
- conn.execute(
- "UPDATE wiki_rassen SET foto_url=? WHERE id=?",
- (new_url, rasse["id"])
- )
+ new_url = f"/media/breeds/gallery/{dest_name}"
+
+ # Nur als Hauptbild setzen wenn noch keins vorhanden
+ if not rasse["foto_url"]:
+ conn.execute(
+ "UPDATE wiki_rassen SET foto_url=? WHERE id=?",
+ (new_url, rasse["id"])
+ )
+
conn.execute("""
UPDATE wiki_foto_submissions
- SET status='approved', reviewed_by=?, reviewed_at=datetime('now')
+ SET status='approved', foto_url=?, reviewed_by=?, reviewed_at=datetime('now')
WHERE id=?
- """, (user["id"], sub_id))
+ """, (new_url, user["id"], sub_id))
# Push-Notification an Einreicher
try:
from routes.push import send_push_to_user
send_push_to_user(sub["user_id"], {
"title": "Foto freigeschalten!",
- "body": f"Dein Foto wurde im Wiki veröffentlicht.",
+ "body": "Dein Foto wurde im Wiki veröffentlicht.",
"type": "wiki_foto_approved",
"data": {"page": "wiki"},
})
except Exception:
pass
+ # Badge-Check
+ try:
+ from routes.achievements import check_and_award
+ with db() as conn2:
+ new_badges = check_and_award(sub["user_id"], conn2)
+ if new_badges:
+ try:
+ send_push_to_user(sub["user_id"], {
+ "title": "\U0001f3c5 Neues Badge!",
+ "body": f"Du hast '{new_badges[0]['name']}' verdient!",
+ "type": "badge_earned",
+ "data": {"page": "achievements"},
+ })
+ except Exception:
+ pass
+ except Exception:
+ pass
+
else: # reject
conn.execute("""
UPDATE wiki_foto_submissions
diff --git a/backend/static/js/app.js b/backend/static/js/app.js
index 22217d2..ae4c5ab 100644
--- a/backend/static/js/app.js
+++ b/backend/static/js/app.js
@@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
-const APP_VER = '347'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
+const APP_VER = '351'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {
diff --git a/backend/static/js/pages/wiki.js b/backend/static/js/pages/wiki.js
index 658fe96..2011037 100644
--- a/backend/static/js/pages/wiki.js
+++ b/backend/static/js/pages/wiki.js
@@ -371,10 +371,11 @@ window.Page_wiki = (() => {
const _DOG_SILHOUETTE = ``;
function _breedCardHtml(r) {
- const photoHtml = r.foto_url
- ? ``
+ const fotoUrl = r.foto_url || r.user_foto || '';
+ const photoHtml = fotoUrl
+ ? `
`
: '';
- const fallbackHtml = `