added lots of stuff
This commit is contained in:
parent
c4d64f6ac7
commit
ab1497b488
14 changed files with 101 additions and 33 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
BACKEND_URL="http://localhost:8000"
|
BACKEND_URL="http://localhost:8000"
|
||||||
FRONTEND_URL="http://localhost:4321"
|
FRONTEND_URL="http://localhost:4321"
|
||||||
SECRET="IamALocalSecretAndMustBeReplacedInProduction"
|
SECRET="IamALocalSecretAndMustBeReplacedInProduction"
|
||||||
ENVIRONMENT="local"
|
ENVIRONMENT="local"
|
||||||
|
MAILTRAP_API_TOKEN="FakedyFake"
|
||||||
Binary file not shown.
|
|
@ -4,3 +4,4 @@ ENVIRONMENT = os.environ["ENVIRONMENT"]
|
||||||
SECRET_KEY = os.environ["SECRET"]
|
SECRET_KEY = os.environ["SECRET"]
|
||||||
FRONTEND_URL = os.environ["FRONTEND_URL"]
|
FRONTEND_URL = os.environ["FRONTEND_URL"]
|
||||||
BACKEND_URL = os.environ["BACKEND_URL"]
|
BACKEND_URL = os.environ["BACKEND_URL"]
|
||||||
|
MAILTRAP_API_TOKEN = os.environ["MAILTRAP_API_TOKEN"]
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,3 +1,4 @@
|
||||||
|
from typing import Any
|
||||||
from backend.config import SECRET_KEY
|
from backend.config import SECRET_KEY
|
||||||
import os
|
import os
|
||||||
from fastapi.security import HTTPBasicCredentials
|
from fastapi.security import HTTPBasicCredentials
|
||||||
|
|
@ -34,10 +35,13 @@ def get_token(credentials = Security(security)) -> str:
|
||||||
return parts[1]
|
return parts[1]
|
||||||
raise HTTPException(status_code=403, detail="Invalid authorization format")
|
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)):
|
async def get_current_user(token: str = Depends(get_token), session: Session = Depends(get_session)):
|
||||||
try:
|
try:
|
||||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
user_code: str | None = decode_token(token)
|
||||||
user_code: str | None = payload.get("sub")
|
|
||||||
if user_code is None:
|
if user_code is None:
|
||||||
raise HTTPException(status_code=401, detail="Invalid token")
|
raise HTTPException(status_code=401, detail="Invalid token")
|
||||||
except PyJWTError:
|
except PyJWTError:
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
<p>Dein persönlicher Code, um alle Funktionen nutzen zu können: {{data.user_code}}</p>
|
<p>Dein persönlicher Code, um alle Funktionen nutzen zu können: {{data.user_code}}</p>
|
||||||
|
|
||||||
<a href="https://backend.blauwasser-mausis.de/bootsuche-england">
|
<a href="https://blauwasser-mausis.de/">
|
||||||
<span>Daten löschen</span>
|
<span>Daten löschen</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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 backend.config import ENVIRONMENT
|
||||||
from mailtrap import Mail
|
from mailtrap import Mail
|
||||||
import mailtrap as mt
|
import mailtrap as mt
|
||||||
|
|
@ -22,30 +25,44 @@ class AdminApprovalData(ArticleData):
|
||||||
|
|
||||||
secret: str
|
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'))
|
env = Environment(loader = FileSystemLoader('src/backend/internal/jinja_templates'))
|
||||||
template = env.get_template('approval.jinja')
|
template = env.get_template(f"{template_name}.jinja")
|
||||||
output = template.render(data=data)
|
return template.render(data=data)
|
||||||
|
|
||||||
|
def send_newsletter_admin_approval(data: ArticleData ):
|
||||||
|
html = create_html_from_template("approval", data)
|
||||||
|
|
||||||
mail = mt.Mail(
|
mail = mt.Mail(
|
||||||
sender=mt.Address(email="newsletter@blauwasser-mausis.de", name="Flaschenpost"),
|
sender=mt.Address(email="newsletter@blauwasser-mausis.de", name="Flaschenpost"),
|
||||||
to=[mt.Address(email="blauwasser-mausis@proton.me")],
|
to=[mt.Address(email="blauwasser-mausis@proton.me")],
|
||||||
subject=f"[Approval] {data.title}",
|
subject=f"[Approval] {data.title}",
|
||||||
text=output,
|
html=html,
|
||||||
)
|
)
|
||||||
send_mail(mail)
|
send_mail(mail)
|
||||||
|
|
||||||
|
|
||||||
def send_mail(mail: Mail):
|
def send_mail(mail: Mail):
|
||||||
if ENVIRONMENT == "local":
|
if ENVIRONMENT == "local":
|
||||||
print(f"Mail sent: {mail.subject}")
|
print(f"Mail sent: {mail.subject}")
|
||||||
|
print(mail.text)
|
||||||
else:
|
else:
|
||||||
client = mt.MailtrapClient(token="<YOUR_API_TOKEN>")
|
client = mt.MailtrapClient(token=MAILTRAP_API_TOKEN)
|
||||||
client.send(mail)
|
client.send(mail)
|
||||||
|
|
||||||
def send_batch_mail():
|
def send_batch_mail(mails: list[BatchEmailRequest]):
|
||||||
mt.BatchMail(
|
if ENVIRONMENT == "local":
|
||||||
sender=mt.Address(email="newsletter@blauwasser-mausis.de", name="Flaschenpost"),
|
for mail in mails:
|
||||||
subject="Flaschenpost | Blauwasser-Mausis"
|
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)
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
|
|
||||||
|
from feedparser import FeedParserDict
|
||||||
from backend.config import FRONTEND_URL
|
from backend.config import FRONTEND_URL
|
||||||
from backend.internal.authentication import create_access_token
|
from backend.internal.authentication import create_access_token
|
||||||
from backend.internal.mail_util import AdminApprovalData
|
from backend.internal.mail_util import AdminApprovalData
|
||||||
|
|
@ -18,15 +19,14 @@ def check_blog_rss_and_send_admin_mail():
|
||||||
description = str(article["summary"])
|
description = str(article["summary"])
|
||||||
subtitle = str(article["subtitle"])
|
subtitle = str(article["subtitle"])
|
||||||
link = str(article["link"])
|
link = str(article["link"])
|
||||||
|
|
||||||
secret = create_access_token({"sub": title})
|
secret = create_access_token({"sub": title})
|
||||||
|
|
||||||
data = AdminApprovalData(title, subtitle, description, link, secret)
|
data = AdminApprovalData(title, subtitle, description, link, secret)
|
||||||
|
|
||||||
send_newsletter_admin_approval(data)
|
send_newsletter_admin_approval(data)
|
||||||
|
|
||||||
# TODO: create link
|
def find_article_by_title(title: str) -> FeedParserDict | None:
|
||||||
# TODO: create Template with jinja2
|
feed = feedparser.parse(f"{FRONTEND_URL}/rss.xml")
|
||||||
# TODO: send mail to blauwasser-mausis@proton.me
|
for article in feed.entries:
|
||||||
|
if title == str(article["title"]):
|
||||||
|
return article
|
||||||
|
return None
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -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 Form
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
|
@ -12,5 +25,20 @@ router = APIRouter(
|
||||||
"/approve",
|
"/approve",
|
||||||
status_code=201
|
status_code=201
|
||||||
)
|
)
|
||||||
def approve_newsletter(secret: str = Form()):
|
def approve_newsletter(secret: str = Form(), session: Session = Depends(get_session)):
|
||||||
pass
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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 Duration
|
||||||
from pyrate_limiter import Rate
|
from pyrate_limiter import Rate
|
||||||
from pyrate_limiter import Limiter
|
from pyrate_limiter import Limiter
|
||||||
|
|
@ -28,18 +31,26 @@ router = APIRouter(
|
||||||
status_code=201,
|
status_code=201,
|
||||||
dependencies=[Depends(RateLimiter(limiter=Limiter(Rate(1, Duration.MINUTE))))]
|
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)
|
user = User.model_validate(new_user)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
session.add(user)
|
session.add(user)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(user)
|
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:
|
except IntegrityError as e:
|
||||||
session.rollback()
|
session.rollback()
|
||||||
raise HTTPException(HTTP_400_BAD_REQUEST)
|
raise HTTPException(HTTP_400_BAD_REQUEST)
|
||||||
return user.user_code
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/login",
|
"/login",
|
||||||
|
|
@ -53,6 +64,7 @@ def login(user_code: str = Form(), session: Session = Depends(get_session)) -> s
|
||||||
raise HTTPException(HTTP_401_UNAUTHORIZED)
|
raise HTTPException(HTTP_401_UNAUTHORIZED)
|
||||||
return create_access_token({"sub": user.user_code})
|
return create_access_token({"sub": user.user_code})
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/forgot",
|
"/forgot",
|
||||||
dependencies=[Depends(RateLimiter(limiter=Limiter(Rate(1, Duration.MINUTE))))]
|
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:
|
if user is not None:
|
||||||
pass # TODO: send Mail
|
pass # TODO: send Mail
|
||||||
|
|
||||||
|
|
||||||
@router.get("/me", status_code=201)
|
@router.get("/me", status_code=201)
|
||||||
def get_current_user_info(current_user: User = Depends(get_current_user)) -> UserInfo:
|
def get_current_user_info(current_user: User = Depends(get_current_user)) -> UserInfo:
|
||||||
return UserInfo.model_validate(current_user)
|
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.delete(current_user)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/me", status_code=201)
|
@router.delete("/me", status_code=201)
|
||||||
def delete(user_code: str = Form(), session: Session = Depends(get_session)) :
|
def delete(user_code: str = Form(), session: Session = Depends(get_session)) :
|
||||||
user = session.exec(select(User).where(User.user_code == user_code)).first()
|
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.delete(user)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
@router.put("me/subscribe", status_code=201)
|
@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
|
current_user.subscription = subscription
|
||||||
session.add(current_user)
|
session.add(current_user)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# Template für Willkommen mit delete link
|
# change mail <- token, new mail
|
||||||
# change mail <- token, new mail
|
|
||||||
# delete all deactivated users after 48h
|
|
||||||
Loading…
Add table
Reference in a new issue