[34588f42] Feat: Freigabe-Anfrage mit Gutschein-Webhook integriert
- Datenbank um 'DiscountCode' Modell erweitert.
- Neue Backend API-Routen für Upload von Gutscheincodes, Abfrage der Verfügbarkeit und Webhook-Listener (Google Forms) zur automatischen Dankes-E-Mail erstellt.
- Frontend (App.tsx) um ein neues Tool ('Anfrage Veröffentlichung') erweitert, das anhand der CSV-Daten Platzhalter (<Name>, <Kind>, <Kindergarten>) personalisiert und Mails via Gmail versendet.
- Google Forms Webhook Script (google_forms_webhook.js) als Kopiervorlage erstellt.
This commit is contained in:
@@ -28,6 +28,14 @@ class GmailToken(Base):
|
||||
token_json = Column(String) # Stores the full credentials JSON
|
||||
updated_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
|
||||
|
||||
class DiscountCode(Base):
|
||||
__tablename__ = "discount_codes"
|
||||
id = Column(Integer, primary_key=True)
|
||||
code = Column(String, unique=True, index=True)
|
||||
is_used = Column(Integer, default=0) # 0 for false, 1 for true
|
||||
assigned_to_email = Column(String, nullable=True)
|
||||
used_at = Column(DateTime, nullable=True)
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
def get_db():
|
||||
|
||||
@@ -87,7 +87,10 @@ load_dotenv()
|
||||
# Ensure DB is created
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
import publish_request_api
|
||||
|
||||
app = FastAPI(title="Fotograf.de Scraper & ERP API")
|
||||
app.include_router(publish_request_api.router)
|
||||
|
||||
# Configure CORS
|
||||
app.add_middleware(
|
||||
|
||||
116
fotograf-de-scraper/backend/publish_request_api.py
Normal file
116
fotograf-de-scraper/backend/publish_request_api.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
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
|
||||
|
||||
router = APIRouter(prefix="/api/publish-request", tags=["publish-request"])
|
||||
logger = logging.getLogger("publish-request")
|
||||
|
||||
class CodesUpload(BaseModel):
|
||||
codes: str # comma separated
|
||||
|
||||
@router.get("/stats")
|
||||
def get_stats(db: Session = Depends(get_db)):
|
||||
total = db.query(DiscountCode).count()
|
||||
used = db.query(DiscountCode).filter(DiscountCode.is_used == 1).count()
|
||||
available = total - used
|
||||
return {"total": total, "used": used, "available": available}
|
||||
|
||||
@router.post("/codes")
|
||||
def upload_codes(data: CodesUpload, db: Session = Depends(get_db)):
|
||||
codes_list = [c.strip() for c in data.codes.split(",") if c.strip()]
|
||||
added = 0
|
||||
for code in set(codes_list):
|
||||
existing = db.query(DiscountCode).filter(DiscountCode.code == code).first()
|
||||
if not existing:
|
||||
new_code = DiscountCode(code=code, is_used=0)
|
||||
db.add(new_code)
|
||||
added += 1
|
||||
db.commit()
|
||||
return {"status": "success", "added": added}
|
||||
|
||||
class WebhookData(BaseModel):
|
||||
email: str
|
||||
|
||||
@router.post("/webhook")
|
||||
async def handle_webhook(request: Request, db: Session = Depends(get_db)):
|
||||
# Try to parse JSON from Google Forms webhook
|
||||
try:
|
||||
data = await request.json()
|
||||
except:
|
||||
raise HTTPException(status_code=400, detail="Invalid JSON")
|
||||
|
||||
# We expect {"email": "..."} or similar from the Google Apps Script
|
||||
email = data.get("email") or data.get("Email")
|
||||
if not email:
|
||||
logger.error(f"Webhook received without email: {data}")
|
||||
return {"status": "error", "message": "Email not found in webhook payload"}
|
||||
|
||||
email = email.strip().lower()
|
||||
|
||||
# Check if this email already got a code
|
||||
already_assigned = db.query(DiscountCode).filter(DiscountCode.assigned_to_email == email).first()
|
||||
if already_assigned:
|
||||
logger.info(f"Email {email} already received code {already_assigned.code}")
|
||||
return {"status": "success", "message": "Already sent"}
|
||||
|
||||
# Get a free code
|
||||
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"}
|
||||
|
||||
# Mark as used
|
||||
free_code.is_used = 1
|
||||
free_code.assigned_to_email = email
|
||||
free_code.used_at = datetime.datetime.utcnow()
|
||||
db.commit()
|
||||
|
||||
# Send Thank You Email with GmailService
|
||||
service = GmailService(db)
|
||||
subject = "Dankeschön für Eure Freigabe & Euer Rabattcode"
|
||||
|
||||
# HTML Signature from frontend
|
||||
SIGNATURE_HTML = """
|
||||
<br><br><br>
|
||||
<div style="font-family: 'Verdana', sans-serif; font-size: 11px; color: #3D3C3F;">
|
||||
<p><strong>Liebe Grüße,</strong><br>Euer Team von Kinderfotos Erding</p>
|
||||
<p><strong>Zierl Fotografen GmbH</strong><br>
|
||||
Anton-Bruckner-Straße 5<br>85435 Erding</p>
|
||||
<p><a href="https://www.kinderfotos-erding.de" style="color: #3D3C3F; text-decoration: none;">www.kinderfotos-erding.de</a></p>
|
||||
</div>
|
||||
"""
|
||||
|
||||
body_html = f"""
|
||||
<p>Hallo,</p>
|
||||
<p>vielen Dank für Eure Unterstützung und das Ausfüllen der Freigabe!</p>
|
||||
<p>Als kleines Dankeschön hier Euer 25 € Rabattcode für Eure Bestellung:</p>
|
||||
<p><strong style="font-size: 18px; color: #4F46E5; padding: 10px; border: 1px dashed #4F46E5; display: inline-block;">{free_code.code}</strong></p>
|
||||
<p>Bitte wartet mit Eurer Bestellung, falls Ihr noch nicht bestellt habt, bis Ihr diesen Code an der Kasse einlösen könnt.</p>
|
||||
{SIGNATURE_HTML}
|
||||
"""
|
||||
|
||||
try:
|
||||
success = service.send_email(email, subject, body_html)
|
||||
if success:
|
||||
logger.info(f"Successfully sent code {free_code.code} to {email}")
|
||||
return {"status": "success", "message": "Email sent"}
|
||||
else:
|
||||
logger.error(f"Failed to send email to {email}")
|
||||
free_code.is_used = 0
|
||||
free_code.assigned_to_email = None
|
||||
free_code.used_at = None
|
||||
db.commit()
|
||||
return {"status": "error", "message": "Failed to send email"}
|
||||
except Exception as e:
|
||||
logger.exception("Error sending webhook email")
|
||||
free_code.is_used = 0
|
||||
free_code.assigned_to_email = None
|
||||
free_code.used_at = None
|
||||
db.commit()
|
||||
return {"status": "error", "message": str(e)}
|
||||
Reference in New Issue
Block a user