diff --git a/fotograf-de-scraper/backend/database.py b/fotograf-de-scraper/backend/database.py
index 2d8264725..0e1e3feee 100644
--- a/fotograf-de-scraper/backend/database.py
+++ b/fotograf-de-scraper/backend/database.py
@@ -36,6 +36,12 @@ class DiscountCode(Base):
assigned_to_email = Column(String, nullable=True)
used_at = Column(DateTime, nullable=True)
+class ReleaseParticipant(Base):
+ __tablename__ = "release_participants"
+ email = Column(String, primary_key=True)
+ first_name = Column(String)
+ last_updated = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
+
Base.metadata.create_all(bind=engine)
def get_db():
diff --git a/fotograf-de-scraper/backend/publish_request_api.py b/fotograf-de-scraper/backend/publish_request_api.py
index e9782d7ce..63ddadce4 100644
--- a/fotograf-de-scraper/backend/publish_request_api.py
+++ b/fotograf-de-scraper/backend/publish_request_api.py
@@ -1,26 +1,38 @@
-from fastapi import APIRouter, Depends, HTTPException, Request, BackgroundTasks
-from pydantic import BaseModel
-from sqlalchemy.orm import Session
-from database import get_db, DiscountCode
-import datetime
-import logging
-from gmail_service import GmailService
-import re
-import time
-import asyncio
-from typing import List, Dict, Optional
+from database import get_db, DiscountCode, ReleaseParticipant
router = APIRouter(prefix="/api/publish-request", tags=["publish-request"])
logger = logging.getLogger("publish-request")
+# Official Project Signature
+SIGNATURE_HTML = """
+
+--
+
+"""
+
class CodesUpload(BaseModel):
codes: str # comma separated
class SendReleaseRequest(BaseModel):
emails: List[Dict[str, str]]
scheduled_time: Optional[str] = None # e.g. "10:00"
+ participants: Optional[List[Dict[str, str]]] = None # [{email, first_name}]
-async def delayed_send(emails: List[Dict[str, str]], scheduled_time: str, db: Session):
+async def delayed_send(emails: List[Dict[str, str]], scheduled_time: str, db_session_factory):
try:
# Calculate delay
now = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=2))) # Berlin Time Approx
@@ -35,21 +47,41 @@ async def delayed_send(emails: List[Dict[str, str]], scheduled_time: str, db: Se
await asyncio.sleep(delay_seconds)
- service = GmailService(db)
- success_count = 0
- for email_data in emails:
- if service.send_email(email_data["to"], email_data["subject"], email_data["body"]):
- success_count += 1
- await asyncio.sleep(1) # Rate limiting
-
- logger.info(f"Scheduled send complete: {success_count}/{len(emails)} success.")
+ # We need a fresh DB session for the background task
+ db = db_session_factory()
+ try:
+ service = GmailService(db)
+ success_count = 0
+ for email_data in emails:
+ if service.send_email(email_data["to"], email_data["subject"], email_data["body"]):
+ success_count += 1
+ await asyncio.sleep(1) # Rate limiting
+
+ logger.info(f"Scheduled send complete: {success_count}/{len(emails)} success.")
+ finally:
+ db.close()
except Exception as e:
logger.exception("Error in delayed_send background task")
@router.post("/send")
async def send_requests(data: SendReleaseRequest, background_tasks: BackgroundTasks, db: Session = Depends(get_db)):
+ # Store participant names for later (webhook)
+ if data.participants:
+ for p in data.participants:
+ email = p.get("email", "").strip().lower()
+ first_name = p.get("first_name", "").strip()
+ if email and first_name:
+ existing = db.query(ReleaseParticipant).filter(ReleaseParticipant.email == email).first()
+ if existing:
+ existing.first_name = first_name
+ else:
+ db.add(ReleaseParticipant(email=email, first_name=first_name))
+ db.commit()
+
if data.scheduled_time:
- background_tasks.add_task(delayed_send, data.emails, data.scheduled_time, db)
+ # Pass a way to get a new session to the background task
+ from database import SessionLocal
+ background_tasks.add_task(delayed_send, data.emails, data.scheduled_time, SessionLocal)
return {"status": "scheduled", "message": f"Versand für {data.scheduled_time} geplant."}
# Immediate send
@@ -113,9 +145,12 @@ async def handle_webhook(request: Request, db: Session = Depends(get_db)):
free_code = db.query(DiscountCode).filter(DiscountCode.is_used == 0).first()
if not free_code:
logger.error("NO FREE DISCOUNT CODES LEFT!")
- # Fallback logic: Notify admin?
return {"status": "error", "message": "No codes available"}
+ # Look up participant name
+ participant = db.query(ReleaseParticipant).filter(ReleaseParticipant.email == email).first()
+ first_name = participant.first_name if participant else "Ihr Lieben"
+
# Mark as used
free_code.is_used = 1
free_code.assigned_to_email = email
@@ -126,23 +161,16 @@ async def handle_webhook(request: Request, db: Session = Depends(get_db)):
service = GmailService(db)
subject = "Dankeschön für Eure Freigabe & Euer Rabattcode"
- # HTML Signature from frontend
- SIGNATURE_HTML = """
-
-
-
Liebe Grüße,
Euer Team von Kinderfotos Erding
-
Zierl Fotografen GmbH
- Anton-Bruckner-Straße 5
85435 Erding
-
www.kinderfotos-erding.de
-
- """
+ # Image provided by user
+ INSTRUCTIONS_IMAGE_URL = "https://mail.google.com/mail/u/2?ui=2&ik=719adaa3c5&attid=0.1&permmsgid=msg-a:r7482671925923393616&th=196e322c399dbc7f&view=fimg&fur=ip&permmsgid=msg-a:r7482671925923393616&sz=s0-l75-ft&attbid=ANGjdJ9_U6ayMFgwbupt4HalTKO867IHx6N70eNbPfQmTLNzRXilJxI-n8a1gjM8xVcP5HEOgaVxfp3FnJPzTYEEYhK4gSU-Il_0a6OtzFYscp55_W4iyxuxjyPvK4&disp=emb&realattid=ii_maspzxv50&zw"
body_html = f"""
- Hallo,
- vielen Dank für Eure Unterstützung und das Ausfüllen der Freigabe!
- Als kleines Dankeschön hier Euer 25 € Rabattcode für Eure Bestellung:
- {free_code.code}
- Bitte wartet mit Eurer Bestellung, falls Ihr noch nicht bestellt habt, bis Ihr diesen Code an der Kasse einlösen könnt.
+ Hallo {first_name},
+ Vielen Dank nochmal für die Freigabe zur Veröffentlichung, das ist super nett von Euch!
+ Hier ist euer Gutscheincode über 25 Euro: {free_code.code}
+ Um den Gutschein einzugeben, musst du auf den Preis des Warenkorbs drücken (über dem Button zur Kasse gehen):
+ 
+ Liebe Grüße,
das Team von Kinderfotos Erding
{SIGNATURE_HTML}
"""
diff --git a/fotograf-de-scraper/frontend/src/App.tsx b/fotograf-de-scraper/frontend/src/App.tsx
index 3bbfb0b15..cd5d51045 100644
--- a/fotograf-de-scraper/frontend/src/App.tsx
+++ b/fotograf-de-scraper/frontend/src/App.tsx
@@ -99,7 +99,7 @@ function App() {
};
const handleSendRelease = async () => {
- if (!reminderResult || !isGmailAuthenticated) return;
+ if (!isGmailAuthenticated) return;
setIsSendingRelease(true);
setReleaseMessage("Bereite Senden vor...");
@@ -124,7 +124,8 @@ function App() {
return {
to: to,
subject: subject,
- body: body
+ body: body,
+ first_name: firstName // Extra field for participant mapping
};
}).filter(e => e.to);
@@ -135,8 +136,9 @@ function App() {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
- emails: emailsToSend,
- scheduled_time: scheduledTime || null
+ emails: emailsToSend.map(e => ({ to: e.to, subject: e.subject, body: e.body })),
+ scheduled_time: scheduledTime || null,
+ participants: emailsToSend.map(e => ({ email: e.to, first_name: e.first_name }))
})
});
const data = await response.json();