diff --git a/Makefile b/Makefile index 8bc1dc6..2427674 100644 --- a/Makefile +++ b/Makefile @@ -138,7 +138,8 @@ release: check-ssh @git checkout main @git merge develop --no-ff -m "Release v$(VERSION)" @sed -i '' 's/"version": "[^"]*"/"version": "$(VERSION)"/' backend/static/manifest.json - @git add backend/static/manifest.json + @sed -i '' "s/const APP_VERSION = '[^']*'/const APP_VERSION = '$(VERSION)'/" backend/static/js/app.js + @git add backend/static/manifest.json backend/static/js/app.js @git commit --amend --no-edit @git tag "v$(VERSION)" @git push $(GIT_REMOTE) main --tags diff --git a/backend/auth.py b/backend/auth.py index d923c70..942a3f1 100644 --- a/backend/auth.py +++ b/backend/auth.py @@ -87,7 +87,7 @@ def get_current_user( user_id = int(payload["sub"]) with db() as conn: row = conn.execute( - "SELECT id, email, name, rolle, is_premium, is_moderator, is_banned, ban_reason, is_social_media, notes_ki_enabled, breeder_status, is_founder, is_partner FROM users WHERE id=?", + "SELECT id, email, name, rolle, is_premium, is_moderator, is_banned, ban_reason, is_social_media, notes_ki_enabled, breeder_status, is_founder, is_partner, founder_number FROM users WHERE id=?", (user_id,) ).fetchone() diff --git a/backend/routes/admin.py b/backend/routes/admin.py index 8be436c..09a4127 100644 --- a/backend/routes/admin.py +++ b/backend/routes/admin.py @@ -296,6 +296,7 @@ async def list_users( rows = conn.execute(f""" SELECT u.id, u.name, {_email_col}, u.rolle, u.is_premium, u.is_moderator, u.is_banned, u.ban_reason, + u.is_founder, u.is_partner, u.founder_number, u.created_at, u.last_login, (SELECT COUNT(*) FROM dogs d WHERE d.user_id=u.id) AS dog_count, (SELECT COUNT(*) FROM forum_threads t WHERE t.user_id=u.id AND t.is_deleted=0) AS thread_count, diff --git a/backend/routes/friends.py b/backend/routes/friends.py index df7c96a..cac5f4b 100644 --- a/backend/routes/friends.py +++ b/backend/routes/friends.py @@ -32,6 +32,7 @@ async def list_friends(user=Depends(get_current_user)): u.name AS friend_name, u.bio, u.wohnort, u.erfahrung, u.social_link, u.profil_sichtbarkeit, u.avatar_url, + u.is_founder, u.is_partner, u.founder_number, {dogs_sq} AS dogs_json FROM friendships f JOIN users u ON u.id = CASE WHEN f.requester_id=? THEN f.addressee_id ELSE f.requester_id END @@ -92,6 +93,7 @@ async def search_users(q: str = "", user=Depends(get_current_user)): SELECT u.id, u.name, u.bio, u.wohnort, u.erfahrung, u.social_link, u.profil_sichtbarkeit, u.avatar_url, + u.is_founder, u.is_partner, u.founder_number, (SELECT json_group_array(json_object('name', d.name, 'rasse', d.rasse)) FROM dogs d WHERE d.user_id=u.id AND d.is_public=1) AS dogs_json FROM users u diff --git a/backend/static/js/app.js b/backend/static/js/app.js index c05a7a5..9d526bd 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,8 +3,8 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '522'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen -const APP_VERSION = '1.0.0'; // ← semantische Version, wird bei make release gesetzt +const APP_VER = '533'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VERSION = '1.1.2'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; const App = (() => { diff --git a/backend/static/js/pages/admin.js b/backend/static/js/pages/admin.js index 4706cee..69ed773 100644 --- a/backend/static/js/pages/admin.js +++ b/backend/static/js/pages/admin.js @@ -19,7 +19,7 @@ window.Page_admin = (() => { { id: 'analytics', label: 'Analytics', icon: 'target' }, { id: 'system', label: 'System', icon: 'gear' }, { id: 'jobs', label: 'Jobs', icon: 'clock' }, - { id: 'partner', label: 'Partner & Codes', icon: 'handshake' }, + { id: 'partner', label: 'Partner', icon: 'handshake' }, { id: 'audit', label: 'Audit-Log', icon: 'clipboard-text' }, ]; @@ -1792,13 +1792,23 @@ window.Page_admin = (() => { // TAB: AUDIT-LOG // ------------------------------------------------------------------ async function _renderPartner(el) { - const [codes] = await Promise.all([ - API.get('/api/admin/partner/codes'), - ]); + const codes = (await API.get('/admin/partner/codes')) || []; el.innerHTML = `
1. Partner-Code erstellen — Erstelle einen Code (z. B. HUNDEBLOG) für einen Influencer oder Partner. Der Code wird an die Person weitergegeben.
2. Registrierung mit Code — Wenn sich ein neuer User mit diesem Code registriert, wird er automatisch als Gründer markiert (Platz #1–100, lebenslang kostenlos). Du siehst in der Tabelle wie viele Einlösungen jeder Code hat.
+3. Partner-Status vergeben — Den Influencer selbst suchst du unten bei «Nutzer-Status» und setzt Partner-Badge (blaues Badge im Profil) und Gründer-Lizenz. So ist auch er als Gründer #X sichtbar.
+Max. 100 Gründer — Ist die Zahl bei einem Code leer, ist sie unbegrenzt. Die globale Grenze über alle Codes hinweg sind 100 Gründer-Plätze.
+Freunde werben — Jeder eingeloggte User hat einen persönlichen Einladungslink (Einstellungen → Freunde werben). Bei 10 geworbenen Usern gibt es 20 % Rabatt, bei 20 → 30 %, bei 50 → 50 % — lebenslang, sobald Bezahlfunktionen aktiv sind.
+Kein User gefunden.
`; return; } grantResult.innerHTML = users.map(u => `✓ ${div.dataset.name} ausgewählt
`; + // Aktuellen Status in Checkboxen setzen + const form = el.querySelector('#adm-partner-grant'); + if (form) { + form.querySelector('[name="is_founder"]').checked = div.dataset.founder === '1'; + form.querySelector('[name="is_partner"]').checked = div.dataset.partner === '1'; + } + grantResult.innerHTML = `✓ ${div.dataset.name} ausgewählt${div.dataset.founder==='1' ? ' · ⭐ Gründer' : ''}${div.dataset.partner==='1' ? ' · 🤝 Partner' : ''}
`; }); }); - } catch { grantResult.innerHTML = ''; } + } catch(e) { + grantResult.innerHTML = `${e.message || 'Suchfehler'}
`; + } }, 400); }); @@ -1985,13 +2005,14 @@ window.Page_admin = (() => { const isFounder = e.target.querySelector('[name="is_founder"]').checked ? 1 : 0; const isPartner = e.target.querySelector('[name="is_partner"]').checked ? 1 : 0; await UI.asyncButton(btn, async () => { - const result = await API.post(`/api/admin/partner/users/${_grantUserId}/grant`, { + const result = await API.post(`/admin/partner/users/${_grantUserId}/grant`, { is_founder: isFounder, is_partner: isPartner, }); + if (!result) throw new Error('Keine Antwort vom Server.'); UI.toast.success(`Status für ${result.name} gesetzt.`); grantResult.innerHTML = `✓ Gründer: ${result.is_founder ? 'Ja' : 'Nein'} | Partner: ${result.is_partner ? 'Ja' : 'Nein'}
`; - }); + }).catch(e => UI.toast.error(e.message || 'Fehler beim Speichern.')); }); } diff --git a/backend/static/js/pages/friends.js b/backend/static/js/pages/friends.js index 837474c..3f2e690 100644 --- a/backend/static/js/pages/friends.js +++ b/backend/static/js/pages/friends.js @@ -610,10 +610,23 @@ window.Page_friends = (() => { margin-bottom:var(--space-4)">${parts.join('')}