Kommentarsystem: Antworten, Pflichtfelder, Like-Benachrichtigung
- Antworten auf Kommentare (Threading mit parent_id) - Pflichtfeld E-Mail ODER Handynummer bei Beiträgen und Kommentaren - Benachrichtigung bei Antworten auf eigene Kommentare (E-Mail/WhatsApp) - Like-Benachrichtigung an Beitragsersteller (E-Mail/WhatsApp) - Verschachtelte Kommentardarstellung mit Antwort-Button - Web-Submit-Overlay um Phone-Feld + Validierung erweitert - DB-Migration: comments.parent_id/email/phone, submissions.phone
This commit is contained in:
@@ -2219,8 +2219,11 @@ def public_frontend():
|
|||||||
</div>
|
</div>
|
||||||
<label>Dein Name (optional)</label>
|
<label>Dein Name (optional)</label>
|
||||||
<input type="text" id="subName" placeholder="Vorname oder Spitzname" maxlength="100">
|
<input type="text" id="subName" placeholder="Vorname oder Spitzname" maxlength="100">
|
||||||
<label>E-Mail für Benachrichtigungen (optional)</label>
|
<label>E-Mail für Benachrichtigungen</label>
|
||||||
<input type="email" id="subEmail" placeholder="deine@email.de" maxlength="200">
|
<input type="email" id="subEmail" placeholder="deine@email.de" maxlength="200">
|
||||||
|
<label>Handynummer für WhatsApp-Benachrichtigungen</label>
|
||||||
|
<input type="tel" id="subPhone" placeholder="+49 170 1234567" maxlength="50">
|
||||||
|
<p style="font-size:0.7rem;color:#999;margin:-0.3rem 0 0.5rem 0;">🔔 E-Mail oder Handynummer erforderlich, damit wir dich bei Antworten & Likes benachrichtigen können.</p>
|
||||||
<label>Nachricht (optional, reicht auch ohne Text)</label>
|
<label>Nachricht (optional, reicht auch ohne Text)</label>
|
||||||
<textarea id="subText" placeholder="Was möchtest du mitteilen? ..." maxlength="20000"></textarea>
|
<textarea id="subText" placeholder="Was möchtest du mitteilen? ..." maxlength="20000"></textarea>
|
||||||
<label>📎 Medien aufnehmen oder auswählen</label>
|
<label>📎 Medien aufnehmen oder auswählen</label>
|
||||||
@@ -2285,6 +2288,7 @@ def public_frontend():
|
|||||||
sSend.addEventListener('click', async () => {
|
sSend.addEventListener('click', async () => {
|
||||||
const name = document.getElementById('subName').value.trim();
|
const name = document.getElementById('subName').value.trim();
|
||||||
const email = document.getElementById('subEmail').value.trim();
|
const email = document.getElementById('subEmail').value.trim();
|
||||||
|
const phone = document.getElementById('subPhone').value.trim();
|
||||||
const text = document.getElementById('subText').value.trim();
|
const text = document.getElementById('subText').value.trim();
|
||||||
const fileInput = document.getElementById('subPhoto');
|
const fileInput = document.getElementById('subPhoto');
|
||||||
const file = fileInput.files[0] || document.getElementById('subVideo').files[0] || document.getElementById('subAudio').files[0] || document.getElementById('subGallery').files[0];
|
const file = fileInput.files[0] || document.getElementById('subVideo').files[0] || document.getElementById('subAudio').files[0] || document.getElementById('subGallery').files[0];
|
||||||
@@ -2297,6 +2301,12 @@ def public_frontend():
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!email && !phone) {
|
||||||
|
sStatus.textContent = '⚠️ Bitte E-Mail oder Handynummer angeben, damit wir dich benachrichtigen können.';
|
||||||
|
sStatus.style.color = '#dc3545';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sSend.disabled = true;
|
sSend.disabled = true;
|
||||||
sSend.textContent = '⏳ Wird gesendet...';
|
sSend.textContent = '⏳ Wird gesendet...';
|
||||||
sStatus.textContent = '';
|
sStatus.textContent = '';
|
||||||
@@ -2304,6 +2314,7 @@ def public_frontend():
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
if (name) formData.append('name', name);
|
if (name) formData.append('name', name);
|
||||||
if (email) formData.append('email', email);
|
if (email) formData.append('email', email);
|
||||||
|
if (phone) formData.append('phone', phone);
|
||||||
if (text) formData.append('text', text);
|
if (text) formData.append('text', text);
|
||||||
if (file) formData.append('media', file);
|
if (file) formData.append('media', file);
|
||||||
if (halle) formData.append('halle', halle);
|
if (halle) formData.append('halle', halle);
|
||||||
@@ -2326,6 +2337,7 @@ def public_frontend():
|
|||||||
sStatus.style.color = '#20228a';
|
sStatus.style.color = '#20228a';
|
||||||
document.getElementById('subName').value = '';
|
document.getElementById('subName').value = '';
|
||||||
document.getElementById('subEmail').value = '';
|
document.getElementById('subEmail').value = '';
|
||||||
|
document.getElementById('subPhone').value = '';
|
||||||
document.getElementById('subText').value = '';
|
document.getElementById('subText').value = '';
|
||||||
fileInput.value = '';
|
fileInput.value = '';
|
||||||
document.getElementById('subVideo').value = '';
|
document.getElementById('subVideo').value = '';
|
||||||
@@ -3033,12 +3045,17 @@ def api_submit():
|
|||||||
halle = (request.form.get('halle') or '').strip()[:20]
|
halle = (request.form.get('halle') or '').strip()[:20]
|
||||||
spiel_titel = (request.form.get('spiel_titel') or '').strip()[:200]
|
spiel_titel = (request.form.get('spiel_titel') or '').strip()[:200]
|
||||||
email = (request.form.get('email') or '').strip()[:200]
|
email = (request.form.get('email') or '').strip()[:200]
|
||||||
|
phone = (request.form.get('phone') or '').strip()[:50]
|
||||||
|
|
||||||
# Require at least text or a file
|
# Require at least text or a file
|
||||||
file = request.files.get('media')
|
file = request.files.get('media')
|
||||||
if not text and (not file or not file.filename):
|
if not text and (not file or not file.filename):
|
||||||
return jsonify({'error': 'Bitte Text oder eine Datei (Foto/Video/Audio) angeben.'}), 400
|
return jsonify({'error': 'Bitte Text oder eine Datei (Foto/Video/Audio) angeben.'}), 400
|
||||||
|
|
||||||
|
# Require email OR phone for notifications
|
||||||
|
if not email and not phone:
|
||||||
|
return jsonify({'error': 'Bitte E-Mail-Adresse oder Handynummer angeben, damit wir dich bei Antworten benachrichtigen können.'}), 400
|
||||||
|
|
||||||
media_path = None
|
media_path = None
|
||||||
msg_type = 'text'
|
msg_type = 'text'
|
||||||
|
|
||||||
@@ -3113,9 +3130,9 @@ def api_submit():
|
|||||||
|
|
||||||
db.execute(
|
db.execute(
|
||||||
"""INSERT INTO submissions
|
"""INSERT INTO submissions
|
||||||
(channel, sender_id, sender_name, type, content, media_path, status, halle, category, spiel_titel, email)
|
(channel, sender_id, sender_name, type, content, media_path, status, halle, category, spiel_titel, email, phone)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, 'new', ?, ?, ?, ?)""",
|
VALUES (?, ?, ?, ?, ?, ?, 'new', ?, ?, ?, ?, ?)""",
|
||||||
('web', sender_id, sender_name, msg_type, text or None, media_path, halle_valid, category, spiel_titel or None, email or None),
|
('web', sender_id, sender_name, msg_type, text or None, media_path, halle_valid, category, spiel_titel or None, email or None, phone or None),
|
||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
sub_id = db.execute("SELECT last_insert_rowid()").fetchone()[0]
|
sub_id = db.execute("SELECT last_insert_rowid()").fetchone()[0]
|
||||||
@@ -3214,11 +3231,20 @@ def public_detail(sub_id: int):
|
|||||||
|
|
||||||
# Comments
|
# Comments
|
||||||
comment_rows = db.execute(
|
comment_rows = db.execute(
|
||||||
"SELECT author_name, content, created_at FROM comments WHERE submission_id = ? ORDER BY created_at ASC",
|
"SELECT id, author_name, content, created_at, parent_id FROM comments WHERE submission_id = ? ORDER BY created_at ASC",
|
||||||
(sub_id,),
|
(sub_id,),
|
||||||
).fetchall()
|
).fetchall()
|
||||||
comments_html = ""
|
|
||||||
|
# Build nested structure: top-level + children
|
||||||
|
top_level = [c for c in comment_rows if not c["parent_id"]]
|
||||||
|
children = {}
|
||||||
for c in comment_rows:
|
for c in comment_rows:
|
||||||
|
if c["parent_id"]:
|
||||||
|
children.setdefault(c["parent_id"], []).append(c)
|
||||||
|
|
||||||
|
def render_comments(comments, indent=0):
|
||||||
|
html = ""
|
||||||
|
for c in comments:
|
||||||
c_author = (c["author_name"] or "Anonym").strip()
|
c_author = (c["author_name"] or "Anonym").strip()
|
||||||
c_date = c["created_at"] or ""
|
c_date = c["created_at"] or ""
|
||||||
try:
|
try:
|
||||||
@@ -3226,10 +3252,18 @@ def public_detail(sub_id: int):
|
|||||||
c_date = cd.strftime("%d.%m.%y %H:%M")
|
c_date = cd.strftime("%d.%m.%y %H:%M")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
comments_html += f"""<div class="comment-item">
|
margin = "margin-left:1.5rem;" if indent > 0 else ""
|
||||||
|
html += f"""<div class="comment-item" style="{margin}">
|
||||||
<div class="comment-header"><strong class="comment-author">{c_author}</strong> <span class="comment-date">{c_date}</span></div>
|
<div class="comment-header"><strong class="comment-author">{c_author}</strong> <span class="comment-date">{c_date}</span></div>
|
||||||
<p class="comment-text">{c["content"]}</p>
|
<p class="comment-text">{c["content"]}</p>
|
||||||
|
<button class="reply-btn" onclick="replyTo({c['id']}, '{c_author}')">↩ Antworten</button>
|
||||||
</div>"""
|
</div>"""
|
||||||
|
# Recursively render children
|
||||||
|
if c["id"] in children:
|
||||||
|
html += render_comments(children[c["id"]], indent + 1)
|
||||||
|
return html
|
||||||
|
|
||||||
|
comments_html = render_comments(top_level)
|
||||||
|
|
||||||
return f"""<!DOCTYPE html>
|
return f"""<!DOCTYPE html>
|
||||||
<html lang="de">
|
<html lang="de">
|
||||||
@@ -3274,6 +3308,12 @@ def public_detail(sub_id: int):
|
|||||||
.comment-submit-btn:hover {{ background:#161e6e; }}
|
.comment-submit-btn:hover {{ background:#161e6e; }}
|
||||||
.comment-submit-btn:active {{ transform:scale(0.97); }}
|
.comment-submit-btn:active {{ transform:scale(0.97); }}
|
||||||
.comment-submit-btn:disabled {{ opacity:0.6; pointer-events:none; }}
|
.comment-submit-btn:disabled {{ opacity:0.6; pointer-events:none; }}
|
||||||
|
.reply-btn {{ background:none; border:1px solid #ccc; border-radius:6px; padding:0.2rem 0.6rem; font-size:0.72rem; color:#666; cursor:pointer; margin-top:0.3rem; font-family:inherit; transition:all 0.15s; }}
|
||||||
|
.reply-btn:hover {{ border-color:#20228a; color:#20228a; background:rgba(32,34,138,0.04); }}
|
||||||
|
.reply-info {{ font-size:0.75rem; color:#20228a; margin-bottom:0.3rem; font-weight:600; display:none; }}
|
||||||
|
.comment-email, .comment-phone {{ padding:0.6rem 0.9rem; border:1.5px solid #ddd; border-radius:10px; font-size:0.85rem; font-family:inherit; }}
|
||||||
|
.comment-email:focus, .comment-phone:focus {{ outline:none; border-color:#20228a; }}
|
||||||
|
.comment-field-hint {{ font-size:0.7rem; color:#999; margin-top:-0.3rem; margin-bottom:0.3rem; }}
|
||||||
.detail-content {{ font-size:1.05rem; line-height:1.7; color:#333; white-space:pre-wrap; word-break:break-word; }}
|
.detail-content {{ font-size:1.05rem; line-height:1.7; color:#333; white-space:pre-wrap; word-break:break-word; }}
|
||||||
.detail-media {{ margin-top:1.5rem; }}
|
.detail-media {{ margin-top:1.5rem; }}
|
||||||
.site-footer {{ text-align:center; padding:2rem; border-top:1px solid #eee; margin-top:3rem; }}
|
.site-footer {{ text-align:center; padding:2rem; border-top:1px solid #eee; margin-top:3rem; }}
|
||||||
@@ -3315,8 +3355,13 @@ def public_detail(sub_id: int):
|
|||||||
<h3 class="comments-heading">💬 Kommentare ({len(comment_rows)})</h3>
|
<h3 class="comments-heading">💬 Kommentare ({len(comment_rows)})</h3>
|
||||||
{comments_html}
|
{comments_html}
|
||||||
<form id="commentForm" class="comment-form" onsubmit="return submitComment(event, {sub_id})">
|
<form id="commentForm" class="comment-form" onsubmit="return submitComment(event, {sub_id})">
|
||||||
|
<div id="replyInfo" class="reply-info">↩ Antwort an <span id="replyTarget"></span> <a href="#" onclick="cancelReply()" style="color:#c62828;font-size:0.7rem;">× abbrechen</a></div>
|
||||||
|
<input type="hidden" name="parent_id" id="commentParentId" value="">
|
||||||
<input type="text" name="author" placeholder="Dein Name (optional)" maxlength="80" class="comment-input">
|
<input type="text" name="author" placeholder="Dein Name (optional)" maxlength="80" class="comment-input">
|
||||||
<textarea name="content" placeholder="Schreib einen Kommentar..." maxlength="2000" required class="comment-textarea"></textarea>
|
<textarea name="content" placeholder="Schreib einen Kommentar..." maxlength="2000" required class="comment-textarea"></textarea>
|
||||||
|
<div class="comment-field-hint">🔔 Damit wir dich bei Antworten benachrichtigen können (E-Mail oder Nummer erforderlich):</div>
|
||||||
|
<input type="email" name="email" placeholder="E-Mail-Adresse" maxlength="200" class="comment-email">
|
||||||
|
<input type="tel" name="phone" placeholder="Handynummer (für WhatsApp-Benachrichtigung)" maxlength="50" class="comment-phone">
|
||||||
<button type="submit" class="comment-submit-btn">Kommentar senden</button>
|
<button type="submit" class="comment-submit-btn">Kommentar senden</button>
|
||||||
</form>
|
</form>
|
||||||
<div id="commentStatus" style="display:none;margin-top:0.5rem;padding:0.5rem;border-radius:8px;font-size:0.85rem;"></div>
|
<div id="commentStatus" style="display:none;margin-top:0.5rem;padding:0.5rem;border-radius:8px;font-size:0.85rem;"></div>
|
||||||
@@ -3362,6 +3407,19 @@ def public_detail(sub_id: int):
|
|||||||
.catch(function() {{ btn.disabled = false; }});
|
.catch(function() {{ btn.disabled = false; }});
|
||||||
}});
|
}});
|
||||||
}})();
|
}})();
|
||||||
|
function replyTo(commentId, authorName) {{
|
||||||
|
document.getElementById('commentParentId').value = commentId;
|
||||||
|
document.getElementById('replyTarget').textContent = authorName;
|
||||||
|
document.getElementById('replyInfo').style.display = 'block';
|
||||||
|
document.getElementById('commentForm').content.focus();
|
||||||
|
window.scrollTo({{ top: document.getElementById('commentForm').offsetTop - 100, behavior: 'smooth' }});
|
||||||
|
}}
|
||||||
|
function cancelReply() {{
|
||||||
|
document.getElementById('commentParentId').value = '';
|
||||||
|
document.getElementById('replyTarget').textContent = '';
|
||||||
|
document.getElementById('replyInfo').style.display = 'none';
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
function submitComment(event, subId) {{
|
function submitComment(event, subId) {{
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
var form = document.getElementById('commentForm');
|
var form = document.getElementById('commentForm');
|
||||||
@@ -3369,14 +3427,26 @@ function submitComment(event, subId) {{
|
|||||||
var btn = form.querySelector('.comment-submit-btn');
|
var btn = form.querySelector('.comment-submit-btn');
|
||||||
var author = form.author.value.trim();
|
var author = form.author.value.trim();
|
||||||
var content = form.content.value.trim();
|
var content = form.content.value.trim();
|
||||||
|
var email = form.email.value.trim();
|
||||||
|
var phone = form.phone.value.trim();
|
||||||
|
var parentId = form.parent_id.value;
|
||||||
if (!content) return false;
|
if (!content) return false;
|
||||||
|
if (!email && !phone) {{
|
||||||
|
status.style.display = 'block';
|
||||||
|
status.style.background = '#fff3e0';
|
||||||
|
status.style.color = '#e65100';
|
||||||
|
status.textContent = '⚠️ Bitte E-Mail oder Handynummer angeben, damit wir dich benachrichtigen können.';
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
btn.textContent = 'Wird gesendet...';
|
btn.textContent = 'Wird gesendet...';
|
||||||
status.style.display = 'none';
|
status.style.display = 'none';
|
||||||
|
var body = {{author: author, content: content, email: email, phone: phone}};
|
||||||
|
if (parentId) body.parent_id = parseInt(parentId);
|
||||||
fetch('/api/comment/' + subId, {{
|
fetch('/api/comment/' + subId, {{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {{'Content-Type': 'application/json'}},
|
headers: {{'Content-Type': 'application/json'}},
|
||||||
body: JSON.stringify({{author: author, content: content}})
|
body: JSON.stringify(body)
|
||||||
}})
|
}})
|
||||||
.then(function(r) {{ return r.json(); }})
|
.then(function(r) {{ return r.json(); }})
|
||||||
.then(function(data) {{
|
.then(function(data) {{
|
||||||
@@ -3386,6 +3456,7 @@ function submitComment(event, subId) {{
|
|||||||
status.style.color = '#2e7d32';
|
status.style.color = '#2e7d32';
|
||||||
status.textContent = '✅ Kommentar gesendet!';
|
status.textContent = '✅ Kommentar gesendet!';
|
||||||
form.reset();
|
form.reset();
|
||||||
|
cancelReply();
|
||||||
// Reload after short delay to show the new comment
|
// Reload after short delay to show the new comment
|
||||||
setTimeout(function() {{ location.reload(); }}, 1200);
|
setTimeout(function() {{ location.reload(); }}, 1200);
|
||||||
}} else {{
|
}} else {{
|
||||||
@@ -3417,7 +3488,7 @@ function submitComment(event, subId) {{
|
|||||||
def api_like(sub_id: int):
|
def api_like(sub_id: int):
|
||||||
db = get_db()
|
db = get_db()
|
||||||
row = db.execute(
|
row = db.execute(
|
||||||
"SELECT id, likes FROM submissions WHERE id = ? AND status='published' AND deleted_at IS NULL",
|
"SELECT id, likes, channel, sender_id, sender_name, email, phone, spiel_titel FROM submissions WHERE id = ? AND status='published' AND deleted_at IS NULL",
|
||||||
(sub_id,),
|
(sub_id,),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
if not row:
|
if not row:
|
||||||
@@ -3431,6 +3502,40 @@ def api_like(sub_id: int):
|
|||||||
db.commit()
|
db.commit()
|
||||||
new_likes = db.execute("SELECT likes FROM submissions WHERE id = ?", (sub_id,)).fetchone()[0]
|
new_likes = db.execute("SELECT likes FROM submissions WHERE id = ?", (sub_id,)).fetchone()[0]
|
||||||
|
|
||||||
|
# Send notification to submission author
|
||||||
|
base = request.host_url.rstrip("/")
|
||||||
|
link = f"{base}/beitrag/{sub_id}"
|
||||||
|
post_label = row["spiel_titel"] or row["sender_name"] or f"Beitrag #{sub_id}"
|
||||||
|
|
||||||
|
# WhatsApp notification
|
||||||
|
if row["channel"] == "whatsapp" and row["sender_id"]:
|
||||||
|
try:
|
||||||
|
msg = f"❤️ *Dein Beitrag wurde geliked!*\n\n{post_label}\n\n👍 {new_likes} Likes\n👉 {link}"
|
||||||
|
send_whatsapp_message(row["sender_id"], msg)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if row["phone"]:
|
||||||
|
try:
|
||||||
|
msg = f"❤️ *Dein Beitrag wurde geliked!*\n\n{post_label}\n\n👍 {new_likes} Likes\n👉 {link}"
|
||||||
|
send_whatsapp_message(row["phone"], msg)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Email notification
|
||||||
|
if row["email"]:
|
||||||
|
try:
|
||||||
|
subject = f"❤️ Dein BSN-Beitrag wurde geliked ({new_likes} Likes)"
|
||||||
|
body = (
|
||||||
|
f"Hallo,\n\n"
|
||||||
|
f"Dein Beitrag \"{post_label}\" hat jetzt {new_likes} Likes.\n\n"
|
||||||
|
f"👉 {link}\n\n"
|
||||||
|
f"Viele Grüße,\n"
|
||||||
|
f"Boardgame Social / brettspiel-news.de\n"
|
||||||
|
)
|
||||||
|
_send_email(row["email"], subject, body)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
resp = jsonify({"likes": new_likes or 0, "already": False})
|
resp = jsonify({"likes": new_likes or 0, "already": False})
|
||||||
resp.set_cookie(f"liked_{sub_id}", "1", max_age=365*24*60*60, httponly=True, samesite="Lax")
|
resp.set_cookie(f"liked_{sub_id}", "1", max_age=365*24*60*60, httponly=True, samesite="Lax")
|
||||||
return resp
|
return resp
|
||||||
@@ -3440,7 +3545,7 @@ def api_like(sub_id: int):
|
|||||||
def api_comment(sub_id: int):
|
def api_comment(sub_id: int):
|
||||||
db = get_db()
|
db = get_db()
|
||||||
row = db.execute(
|
row = db.execute(
|
||||||
"SELECT id, channel, sender_id, sender_name, email FROM submissions WHERE id = ? AND status='published' AND deleted_at IS NULL",
|
"SELECT id, channel, sender_id, sender_name, email, phone FROM submissions WHERE id = ? AND status='published' AND deleted_at IS NULL",
|
||||||
(sub_id,),
|
(sub_id,),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
if not row:
|
if not row:
|
||||||
@@ -3449,20 +3554,81 @@ def api_comment(sub_id: int):
|
|||||||
data = request.get_json(force=True, silent=True) or {}
|
data = request.get_json(force=True, silent=True) or {}
|
||||||
author = (data.get("author") or "").strip()[:80]
|
author = (data.get("author") or "").strip()[:80]
|
||||||
content = (data.get("content") or "").strip()[:2000]
|
content = (data.get("content") or "").strip()[:2000]
|
||||||
|
email = (data.get("email") or "").strip()[:200]
|
||||||
|
phone = (data.get("phone") or "").strip()[:50]
|
||||||
|
parent_id = data.get("parent_id")
|
||||||
|
if parent_id is not None:
|
||||||
|
try:
|
||||||
|
parent_id = int(parent_id)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
parent_id = None
|
||||||
|
|
||||||
if not content:
|
if not content:
|
||||||
return jsonify({"error": "Kommentar darf nicht leer sein"}), 400
|
return jsonify({"error": "Kommentar darf nicht leer sein"}), 400
|
||||||
|
|
||||||
|
# Require email OR phone for notification
|
||||||
|
if not email and not phone:
|
||||||
|
return jsonify({"error": "Bitte E-Mail-Adresse oder Handynummer angeben, damit wir dich bei Antworten benachrichtigen können."}), 400
|
||||||
|
|
||||||
db.execute(
|
db.execute(
|
||||||
"INSERT INTO comments (submission_id, author_name, content) VALUES (?, ?, ?)",
|
"INSERT INTO comments (submission_id, author_name, content, parent_id, email, phone) VALUES (?, ?, ?, ?, ?, ?)",
|
||||||
(sub_id, author or None, content),
|
(sub_id, author or None, content, parent_id, email or None, phone or None),
|
||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
comment_id = db.execute("SELECT last_insert_rowid()").fetchone()[0]
|
||||||
|
|
||||||
# Build notification link
|
# Build notification link
|
||||||
base = request.host_url.rstrip("/")
|
base = request.host_url.rstrip("/")
|
||||||
link = f"{base}/beitrag/{sub_id}"
|
link = f"{base}/beitrag/{sub_id}"
|
||||||
|
|
||||||
# WhatsApp notification (only for WhatsApp submissions)
|
# 1. Notify parent comment author (if replying to a comment)
|
||||||
|
if parent_id:
|
||||||
|
parent = db.execute(
|
||||||
|
"SELECT author_name, email, phone FROM comments WHERE id = ? AND submission_id = ?",
|
||||||
|
(parent_id, sub_id),
|
||||||
|
).fetchone()
|
||||||
|
if parent:
|
||||||
|
p_author = parent["author_name"] or "Jemand"
|
||||||
|
# WhatsApp to parent comment author
|
||||||
|
if parent["phone"]:
|
||||||
|
try:
|
||||||
|
msg = (
|
||||||
|
f"💬 *Antwort auf deinen Kommentar!*\n\n"
|
||||||
|
f"{'*' + (author or 'Jemand') + '* hat geantwortet' if author else 'Jemand hat geantwortet'}:\n"
|
||||||
|
f"_{content[:200]}{'...' if len(content) > 200 else ''}_\n\n"
|
||||||
|
f"👉 {link}"
|
||||||
|
)
|
||||||
|
send_whatsapp_message(parent["phone"], msg)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Email to parent comment author
|
||||||
|
if parent["email"]:
|
||||||
|
try:
|
||||||
|
subject = f"💬 Antwort auf deinen BSN-Kommentar"
|
||||||
|
body = (
|
||||||
|
f"Hallo,\n\n"
|
||||||
|
f"{author or 'Jemand'} hat auf deinen Kommentar geantwortet:\n\n"
|
||||||
|
f"\"{content[:300]}{'...' if len(content) > 300 else ''}\"\n\n"
|
||||||
|
f"👉 {link}\n\n"
|
||||||
|
f"Viele Grüße,\n"
|
||||||
|
f"Boardgame Social / brettspiel-news.de\n"
|
||||||
|
)
|
||||||
|
_send_email(parent["email"], subject, body)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 2. Notify submission author (unless they're replying to themselves)
|
||||||
|
notify_submission_author = True
|
||||||
|
if parent_id:
|
||||||
|
parent = db.execute(
|
||||||
|
"SELECT author_name, email, phone FROM comments WHERE id = ? AND submission_id = ?",
|
||||||
|
(parent_id, sub_id),
|
||||||
|
).fetchone()
|
||||||
|
if parent and (parent["email"] == row["email"] or parent["phone"] == row["phone"]):
|
||||||
|
notify_submission_author = False # Conversation with themselves
|
||||||
|
|
||||||
|
if notify_submission_author:
|
||||||
|
# WhatsApp notification
|
||||||
if row["channel"] == "whatsapp" and row["sender_id"]:
|
if row["channel"] == "whatsapp" and row["sender_id"]:
|
||||||
try:
|
try:
|
||||||
msg = (
|
msg = (
|
||||||
@@ -3473,10 +3639,9 @@ def api_comment(sub_id: int):
|
|||||||
)
|
)
|
||||||
send_whatsapp_message(row["sender_id"], msg)
|
send_whatsapp_message(row["sender_id"], msg)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # silently ignore notification failures
|
pass
|
||||||
|
# Email to submission author
|
||||||
# Email notification (only for web submissions with email)
|
if row["email"]:
|
||||||
if row["channel"] == "web" and row["email"]:
|
|
||||||
try:
|
try:
|
||||||
subject = f"💬 Neuer Kommentar zu deinem BSN-Beitrag #{sub_id}"
|
subject = f"💬 Neuer Kommentar zu deinem BSN-Beitrag #{sub_id}"
|
||||||
body = (
|
body = (
|
||||||
@@ -3489,9 +3654,22 @@ def api_comment(sub_id: int):
|
|||||||
)
|
)
|
||||||
_send_email(row["email"], subject, body)
|
_send_email(row["email"], subject, body)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # silently ignore notification failures
|
pass
|
||||||
|
|
||||||
return jsonify({"ok": True}), 201
|
# WhatsApp to submission author via phone
|
||||||
|
if row["phone"]:
|
||||||
|
try:
|
||||||
|
msg = (
|
||||||
|
f"💬 *Neuer Kommentar zu deinem Beitrag!*\n\n"
|
||||||
|
f"{'*' + (author or 'Jemand') + '* schreibt' if author else 'Jemand hat kommentiert'}:\n"
|
||||||
|
f"_{content[:200]}{'...' if len(content) > 200 else ''}_\n\n"
|
||||||
|
f"👉 {link}"
|
||||||
|
)
|
||||||
|
send_whatsapp_message(row["phone"], msg)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return jsonify({"ok": True, "comment_id": comment_id}), 201
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user