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 = """ +

+--
+
+ + + + + + + +
+ Kinderfotos Erding Logo + +

Kinderfotos Erding | www.kinderfotos-erding.de

+

Gartenstr. 10 | 85445 Oberding | 08122-8470867

+
+
+""" + 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):

+

Anleitung Gutschein einlösen

+

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();