diff --git a/backend/src/backend/.env b/backend/src/backend/.env
index 6bff5c7..3fcde65 100644
--- a/backend/src/backend/.env
+++ b/backend/src/backend/.env
@@ -1,4 +1,5 @@
BACKEND_URL="http://localhost:8000"
FRONTEND_URL="http://localhost:4321"
SECRET="IamALocalSecretAndMustBeReplacedInProduction"
-ENVIRONMENT="local"
\ No newline at end of file
+ENVIRONMENT="local"
+MAILTRAP_API_TOKEN="FakedyFake"
\ No newline at end of file
diff --git a/backend/src/backend/__pycache__/config.cpython-314.pyc b/backend/src/backend/__pycache__/config.cpython-314.pyc
index d9ad864..25663f5 100644
Binary files a/backend/src/backend/__pycache__/config.cpython-314.pyc and b/backend/src/backend/__pycache__/config.cpython-314.pyc differ
diff --git a/backend/src/backend/config.py b/backend/src/backend/config.py
index e2ab97c..b902cdc 100644
--- a/backend/src/backend/config.py
+++ b/backend/src/backend/config.py
@@ -4,3 +4,4 @@ ENVIRONMENT = os.environ["ENVIRONMENT"]
SECRET_KEY = os.environ["SECRET"]
FRONTEND_URL = os.environ["FRONTEND_URL"]
BACKEND_URL = os.environ["BACKEND_URL"]
+MAILTRAP_API_TOKEN = os.environ["MAILTRAP_API_TOKEN"]
\ No newline at end of file
diff --git a/backend/src/backend/internal/__pycache__/authentication.cpython-314.pyc b/backend/src/backend/internal/__pycache__/authentication.cpython-314.pyc
index 6c8df75..22f024c 100644
Binary files a/backend/src/backend/internal/__pycache__/authentication.cpython-314.pyc and b/backend/src/backend/internal/__pycache__/authentication.cpython-314.pyc differ
diff --git a/backend/src/backend/internal/__pycache__/mail_util.cpython-314.pyc b/backend/src/backend/internal/__pycache__/mail_util.cpython-314.pyc
index e56b7ef..6f0aaaf 100644
Binary files a/backend/src/backend/internal/__pycache__/mail_util.cpython-314.pyc and b/backend/src/backend/internal/__pycache__/mail_util.cpython-314.pyc differ
diff --git a/backend/src/backend/internal/__pycache__/rss_reader.cpython-314.pyc b/backend/src/backend/internal/__pycache__/rss_reader.cpython-314.pyc
index 3fe9348..e1288e6 100644
Binary files a/backend/src/backend/internal/__pycache__/rss_reader.cpython-314.pyc and b/backend/src/backend/internal/__pycache__/rss_reader.cpython-314.pyc differ
diff --git a/backend/src/backend/internal/authentication.py b/backend/src/backend/internal/authentication.py
index 478e03e..ab3c901 100644
--- a/backend/src/backend/internal/authentication.py
+++ b/backend/src/backend/internal/authentication.py
@@ -1,3 +1,4 @@
+from typing import Any
from backend.config import SECRET_KEY
import os
from fastapi.security import HTTPBasicCredentials
@@ -34,10 +35,13 @@ def get_token(credentials = Security(security)) -> str:
return parts[1]
raise HTTPException(status_code=403, detail="Invalid authorization format")
+def decode_token(token: str)-> str | None:
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+ return payload.get("sub")
+
async def get_current_user(token: str = Depends(get_token), session: Session = Depends(get_session)):
try:
- payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
- user_code: str | None = payload.get("sub")
+ user_code: str | None = decode_token(token)
if user_code is None:
raise HTTPException(status_code=401, detail="Invalid token")
except PyJWTError:
diff --git a/backend/src/backend/internal/jinja_templates/welcome.jinja b/backend/src/backend/internal/jinja_templates/welcome.jinja
index d6ad6f6..6322cca 100644
--- a/backend/src/backend/internal/jinja_templates/welcome.jinja
+++ b/backend/src/backend/internal/jinja_templates/welcome.jinja
@@ -12,7 +12,7 @@
Dein persönlicher Code, um alle Funktionen nutzen zu können: {{data.user_code}}
-
+
Daten löschen
diff --git a/backend/src/backend/internal/mail_util.py b/backend/src/backend/internal/mail_util.py
index d8bc083..0cfa910 100644
--- a/backend/src/backend/internal/mail_util.py
+++ b/backend/src/backend/internal/mail_util.py
@@ -1,3 +1,6 @@
+from backend.config import MAILTRAP_API_TOKEN
+from mailtrap import BatchEmailRequest
+from mailtrap import BatchSendEmailParams
from backend.config import ENVIRONMENT
from mailtrap import Mail
import mailtrap as mt
@@ -22,30 +25,44 @@ class AdminApprovalData(ArticleData):
secret: str
-def send_newsletter_admin_approval(data: ArticleData ):
+def create_html_from_template(template_name: str, data):
env = Environment(loader = FileSystemLoader('src/backend/internal/jinja_templates'))
- template = env.get_template('approval.jinja')
- output = template.render(data=data)
+ template = env.get_template(f"{template_name}.jinja")
+ return template.render(data=data)
+
+def send_newsletter_admin_approval(data: ArticleData ):
+ html = create_html_from_template("approval", data)
mail = mt.Mail(
sender=mt.Address(email="newsletter@blauwasser-mausis.de", name="Flaschenpost"),
to=[mt.Address(email="blauwasser-mausis@proton.me")],
subject=f"[Approval] {data.title}",
- text=output,
+ html=html,
)
send_mail(mail)
-
def send_mail(mail: Mail):
if ENVIRONMENT == "local":
print(f"Mail sent: {mail.subject}")
+ print(mail.text)
else:
- client = mt.MailtrapClient(token="")
+ client = mt.MailtrapClient(token=MAILTRAP_API_TOKEN)
client.send(mail)
-def send_batch_mail():
- mt.BatchMail(
- sender=mt.Address(email="newsletter@blauwasser-mausis.de", name="Flaschenpost"),
- subject="Flaschenpost | Blauwasser-Mausis"
-
+def send_batch_mail(mails: list[BatchEmailRequest]):
+ if ENVIRONMENT == "local":
+ for mail in mails:
+ print(f"Mail sent: {mail.subject} to {mail.to}")
+ else:
+ client = mt.MailtrapClient(token=MAILTRAP_API_TOKEN)
+ params = BatchSendEmailParams(
+ base={
+ "from": {
+ "name": "Flaschenpost",
+ "email": "newsletter@blauwasser-mausis.de"
+ },
+ "subject": "Flaschenpost | Blauwasser Mausis",
+ },
+ requests=mails
)
+ client.batch_send(params)
\ No newline at end of file
diff --git a/backend/src/backend/internal/rss_reader.py b/backend/src/backend/internal/rss_reader.py
index 0e2cb24..c7e00d8 100644
--- a/backend/src/backend/internal/rss_reader.py
+++ b/backend/src/backend/internal/rss_reader.py
@@ -1,4 +1,5 @@
+from feedparser import FeedParserDict
from backend.config import FRONTEND_URL
from backend.internal.authentication import create_access_token
from backend.internal.mail_util import AdminApprovalData
@@ -18,15 +19,14 @@ def check_blog_rss_and_send_admin_mail():
description = str(article["summary"])
subtitle = str(article["subtitle"])
link = str(article["link"])
-
secret = create_access_token({"sub": title})
-
+
data = AdminApprovalData(title, subtitle, description, link, secret)
-
send_newsletter_admin_approval(data)
-
- # TODO: create link
- # TODO: create Template with jinja2
- # TODO: send mail to blauwasser-mausis@proton.me
-
-
+
+def find_article_by_title(title: str) -> FeedParserDict | None:
+ feed = feedparser.parse(f"{FRONTEND_URL}/rss.xml")
+ for article in feed.entries:
+ if title == str(article["title"]):
+ return article
+ return None
diff --git a/backend/src/backend/routers/__pycache__/newsletters.cpython-314.pyc b/backend/src/backend/routers/__pycache__/newsletters.cpython-314.pyc
index 8ae8574..6717443 100644
Binary files a/backend/src/backend/routers/__pycache__/newsletters.cpython-314.pyc and b/backend/src/backend/routers/__pycache__/newsletters.cpython-314.pyc differ
diff --git a/backend/src/backend/routers/__pycache__/users.cpython-314.pyc b/backend/src/backend/routers/__pycache__/users.cpython-314.pyc
index a18358a..52ed5ef 100644
Binary files a/backend/src/backend/routers/__pycache__/users.cpython-314.pyc and b/backend/src/backend/routers/__pycache__/users.cpython-314.pyc differ
diff --git a/backend/src/backend/routers/newsletters.py b/backend/src/backend/routers/newsletters.py
index 0b37d7a..170031a 100644
--- a/backend/src/backend/routers/newsletters.py
+++ b/backend/src/backend/routers/newsletters.py
@@ -1,3 +1,16 @@
+from backend.internal.mail_util import create_html_from_template
+from mailtrap import BatchEmailRequest
+from mailtrap import BatchMail
+from backend.internal.mail_util import send_batch_mail
+from starlette.status import HTTP_400_BAD_REQUEST
+from fastapi import HTTPException
+from sqlmodel import select
+from backend.models.user import User
+from sqlmodel import Session
+from backend.internal.database import get_session
+from fastapi import Depends
+from backend.internal.rss_reader import find_article_by_title
+from backend.internal.authentication import decode_token
from fastapi import Form
from fastapi import APIRouter
@@ -12,5 +25,20 @@ router = APIRouter(
"/approve",
status_code=201
)
-def approve_newsletter(secret: str = Form()):
- pass
\ No newline at end of file
+def approve_newsletter(secret: str = Form(), session: Session = Depends(get_session)):
+ title = decode_token(secret)
+ if title is not None:
+ article = find_article_by_title(title)
+ if article is not None:
+ subscribed_users = session.exec(select(User).where(User.subscription == True))
+ mails: list[BatchEmailRequest] = []
+ for user in subscribed_users:
+ html = create_html_from_template("newsletter", user)
+ mail = BatchEmailRequest(to=user.email,html=html)
+ mails.append(mail)
+ send_batch_mail(mails)
+
+ else:
+ raise HTTPException(HTTP_400_BAD_REQUEST)
+
+
\ No newline at end of file
diff --git a/backend/src/backend/routers/users.py b/backend/src/backend/routers/users.py
index 00949a4..9d178ab 100644
--- a/backend/src/backend/routers/users.py
+++ b/backend/src/backend/routers/users.py
@@ -1,3 +1,6 @@
+from backend.internal.mail_util import send_mail
+from backend.internal.mail_util import create_html_from_template
+from mailtrap import Mail
from pyrate_limiter import Duration
from pyrate_limiter import Rate
from pyrate_limiter import Limiter
@@ -28,18 +31,26 @@ router = APIRouter(
status_code=201,
dependencies=[Depends(RateLimiter(limiter=Limiter(Rate(1, Duration.MINUTE))))]
)
-def register_user(new_user: NewUser, session: Session = Depends(get_session)) -> str:
+def register_user(new_user: NewUser, session: Session = Depends(get_session)):
user = User.model_validate(new_user)
try:
session.add(user)
session.commit()
session.refresh(user)
- # TODO: send mail
+
+ mail = Mail(
+ to=user.email,
+ sender="noreply@blauwasser-mausis.de",
+ subject=f"[Registrierung] Willkommen {user.name} | Blauwasser Mausis",
+ html=create_html_from_template("welcome", user)
+ )
+ send_mail(mail)
+
except IntegrityError as e:
session.rollback()
raise HTTPException(HTTP_400_BAD_REQUEST)
- return user.user_code
+
@router.post(
"/login",
@@ -53,6 +64,7 @@ def login(user_code: str = Form(), session: Session = Depends(get_session)) -> s
raise HTTPException(HTTP_401_UNAUTHORIZED)
return create_access_token({"sub": user.user_code})
+
@router.post(
"/forgot",
dependencies=[Depends(RateLimiter(limiter=Limiter(Rate(1, Duration.MINUTE))))]
@@ -62,6 +74,7 @@ def forgot_user_code(email: str = Form(), session: Session = Depends(get_session
if user is not None:
pass # TODO: send Mail
+
@router.get("/me", status_code=201)
def get_current_user_info(current_user: User = Depends(get_current_user)) -> UserInfo:
return UserInfo.model_validate(current_user)
@@ -72,6 +85,7 @@ def delete(current_user: User = Depends(get_current_user), session: Session = De
session.delete(current_user)
session.commit()
+
@router.delete("/me", status_code=201)
def delete(user_code: str = Form(), session: Session = Depends(get_session)) :
user = session.exec(select(User).where(User.user_code == user_code)).first()
@@ -80,14 +94,17 @@ def delete(user_code: str = Form(), session: Session = Depends(get_session)) :
session.delete(user)
session.commit()
+
@router.put("me/subscribe", status_code=201)
-def change_subscription(subscription: bool = Form(), current_user: User = Depends(get_current_user), session: Session = Depends(get_session)):
+def change_subscription(
+ subscription: bool = Form(),
+ current_user: User = Depends(get_current_user),
+ session: Session = Depends(get_session)
+):
current_user.subscription = subscription
session.add(current_user)
session.commit()
# TODO:
-# Template für Willkommen mit delete link
-# change mail <- token, new mail
-# delete all deactivated users after 48h
\ No newline at end of file
+# change mail <- token, new mail
\ No newline at end of file