Compare commits

..

2 commits

Author SHA1 Message Date
c4d64f6ac7 env variables 2026-09-27 13:15:11 +02:00
91c13307aa secret creation 2026-09-27 10:57:03 +02:00
19 changed files with 132 additions and 46 deletions

View file

@ -2,7 +2,9 @@
"cSpell.language": "en,de-DE",
"cSpell.words": [
"Chiara",
"dotenv",
"Hobbithöhle",
"mailtrap",
"Masala",
"Pomorie",
"Reinke"

4
backend/src/backend/.env Normal file
View file

@ -0,0 +1,4 @@
BACKEND_URL="http://localhost:8000"
FRONTEND_URL="http://localhost:4321"
SECRET="IamALocalSecretAndMustBeReplacedInProduction"
ENVIRONMENT="local"

Binary file not shown.

View file

@ -0,0 +1,6 @@
import os
ENVIRONMENT = os.environ["ENVIRONMENT"]
SECRET_KEY = os.environ["SECRET"]
FRONTEND_URL = os.environ["FRONTEND_URL"]
BACKEND_URL = os.environ["BACKEND_URL"]

View file

@ -1,3 +1,5 @@
from backend.config import SECRET_KEY
import os
from fastapi.security import HTTPBasicCredentials
from fastapi import Security
from fastapi.security import HTTPBearer
@ -10,13 +12,11 @@ from backend.models.user import User
from backend.internal.database import get_session
from sqlmodel import Session
from fastapi import HTTPException
from fastapi.security.oauth2 import OAuth2PasswordBearer
from fastapi import Depends
from datetime import datetime
from datetime import timedelta
SECRET_KEY = "secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_DAYS = 360

View file

@ -20,7 +20,7 @@
<span>Abbestellen</span>
</a>
<a id="admin" href="https://blauwasser-mausis.de">Approve</a>
<a id="admin" href="https://backend.blauwasser-mausis.de/?secret={{data.secret}}">Approve</a>
</div>
</body>

View file

@ -8,15 +8,17 @@
<body>
<div>
<h1>Neuer Artikel:</h1>
<h3>England</h3>
<h4>It's gettin' british, my dear!</h4>
<p>Auf geht's nach Milano! Aber Moment mal...der Artikel heißt England?! Da kann irgendwas nicht stimmen. Ich frage mal in der Redaktion nach. Ihr lest währenddessen einfach schonmal weiter.</p>
<h3>{{data.title}}</h3>
<h4>{{data.subtitle}}</h4>
<p>{{data.description}}</p>
<a href=https://blauwasser-mausis.de/blog/bootsuche-england/>
<a href={{data.link}}>
<button>Zum Artikel</button>
</a>
<a id="admin" href="https://blauwasser-mausis.de">Approve</a>
<a href="https://blauwasser-mausis.de/blog/bootsuche-england">
<span>Abbestellen</span>
</a>
</div>
</body>
@ -51,9 +53,4 @@
border-radius: 3px;
border: none;
}
#admin {
margin-top: 64px;
color: red;
}
</style>

View file

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<title>Newsletter</title>
</head>
<body>
<div>
<h1>Willkommen, {{data.name}}</h1>
<p>Du hast dich bei Blauwasser-Mausis.de angemeldet. Solltest du das nicht gewesen sein, dann lösche die Daten einfach über den untenstehenden Link.</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">
<span>Daten löschen</span>
</a>
</div>
</body>
</html>
<style>
* {
font-family: Arial, Helvetica, sans-serif;
}
body {
margin: 32px;
}
div {
display: flex;
align-items: center;
flex-direction: column;
background-color: lightgrey;
padding: 32px 64px;
border-radius: 3px;
}
</style>

View file

@ -1,10 +1,9 @@
from backend.config import ENVIRONMENT
from mailtrap import Mail
import mailtrap as mt
import os
from jinja2 import Environment, FileSystemLoader
class Newsletter():
class ArticleData():
def __init__(self, title, subtitle, description, link):
self.title=title
self.subtitle=subtitle
@ -16,28 +15,37 @@ class Newsletter():
description: str
link: str
def send_newsletter_admin_approval(data: Newsletter ):
class AdminApprovalData(ArticleData):
def __init__(self,title, subtitle, description, link, secret):
ArticleData.__init__(self, title, subtitle, description, link)
self.secret = secret
secret: str
def send_newsletter_admin_approval(data: ArticleData ):
env = Environment(loader = FileSystemLoader('src/backend/internal/jinja_templates'))
template = env.get_template('approval.jinja')
output = template.render(data=data)
with open("src/backend/internal/jinja_templates/output.html", "w") as f:
f.write(output)
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="Jinja2 Template",
text=output,
)
send_mail(mail)
def send_mail(mail: Mail):
if ENVIRONMENT == "local":
print(f"Mail sent: {mail.subject}")
else:
client = mt.MailtrapClient(token="<YOUR_API_TOKEN>")
client.send(mail)
def send_batch_mail():
pass
mt.BatchMail(
sender=mt.Address(email="newsletter@blauwasser-mausis.de", name="Flaschenpost"),
subject="Flaschenpost | Blauwasser-Mausis"
)

View file

@ -1,25 +1,27 @@
from backend.internal.mail_util import Newsletter
from backend.config import FRONTEND_URL
from backend.internal.authentication import create_access_token
from backend.internal.mail_util import AdminApprovalData
from backend.internal.mail_util import send_newsletter_admin_approval
from datetime import timedelta
from datetime import datetime
import feedparser
FEED_URL="http://localhost:4321/rss.xml"
def check_blog_rss_and_send_admin_mail():
feed = feedparser.parse(FEED_URL)
feed = feedparser.parse(f"{FRONTEND_URL}/rss.xml")
for article in feed.entries:
date_string = str(article["published"])
date_object = datetime.strptime(date_string, '%a, %d %b %Y %H:%M:%S %Z')
if date_object > (datetime.now() - timedelta(days=15)):
if date_object > (datetime.now() - timedelta(days=16)):
title = str(article["title"])
description = str(article["summary"])
subtitle = str(article["subtitle"])
link = str(article["link"])
data = Newsletter(title, subtitle, description, link)
secret = create_access_token({"sub": title})
data = AdminApprovalData(title, subtitle, description, link, secret)
send_newsletter_admin_approval(data)

View file

@ -1,13 +1,15 @@
from dotenv import load_dotenv
load_dotenv()
from backend.internal.rss_reader import check_blog_rss_and_send_admin_mail
from .routers import users, comments
from .routers import users, comments, newsletters
from backend.internal.database import create_db_and_tables
from fastapi import APIRouter
from fastapi import FastAPI
from fastapi_crons import Crons, get_cron_router
from fastapi_crons import Crons
# TODO: Newsletter
# TODO: env
# TODO: TLS
# TODO: Deploy
@ -27,7 +29,4 @@ router = APIRouter()
app.include_router(users.router)
app.include_router(comments.router)
check_blog_rss_and_send_admin_mail()
app.include_router(newsletters.router)

View file

@ -0,0 +1,16 @@
from fastapi import Form
from fastapi import APIRouter
router = APIRouter(
prefix="/newsletter",
tags=[""],
dependencies=[],
responses={404: {"description": "Not found"}},
)
@router.post(
"/approve",
status_code=201
)
def approve_newsletter(secret: str = Form()):
pass

View file

@ -18,13 +18,16 @@ from fastapi import APIRouter
from fastapi_limiter.depends import RateLimiter
router = APIRouter(
prefix="",
tags=[""],
prefix="/users",
dependencies=[],
responses={404: {"description": "Not found"}},
)
@router.post("/users", status_code=201)
@router.post(
"/",
status_code=201,
dependencies=[Depends(RateLimiter(limiter=Limiter(Rate(1, Duration.MINUTE))))]
)
def register_user(new_user: NewUser, session: Session = Depends(get_session)) -> str:
user = User.model_validate(new_user)
@ -51,7 +54,7 @@ def login(user_code: str = Form(), session: Session = Depends(get_session)) -> s
return create_access_token({"sub": user.user_code})
@router.post(
"users/forgot",
"/forgot",
dependencies=[Depends(RateLimiter(limiter=Limiter(Rate(1, Duration.MINUTE))))]
)
def forgot_user_code(email: str = Form(), session: Session = Depends(get_session)):
@ -69,6 +72,14 @@ 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()
if user is not None:
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)):
current_user.subscription = subscription
@ -77,5 +88,6 @@ def change_subscription(subscription: bool = Form(), current_user: User = Depend
# TODO:
# Template für Willkommen mit delete link
# change mail <- token, new mail
# delete all deactivated users after 48h