Compare commits

..

No commits in common. "c4d64f6ac7d8e3a6177ecacabbf64e1625928e98" and "c51908ffeb40c64562ff99e43732f9acefc0440a" have entirely different histories.

19 changed files with 46 additions and 132 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -8,17 +8,15 @@
<body> <body>
<div> <div>
<h1>Neuer Artikel:</h1> <h1>Neuer Artikel:</h1>
<h3>{{data.title}}</h3> <h3>England</h3>
<h4>{{data.subtitle}}</h4> <h4>It's gettin' british, my dear!</h4>
<p>{{data.description}}</p> <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>
<a href={{data.link}}> <a href=https://blauwasser-mausis.de/blog/bootsuche-england/>
<button>Zum Artikel</button> <button>Zum Artikel</button>
</a> </a>
<a href="https://blauwasser-mausis.de/blog/bootsuche-england"> <a id="admin" href="https://blauwasser-mausis.de">Approve</a>
<span>Abbestellen</span>
</a>
</div> </div>
</body> </body>
@ -53,4 +51,9 @@
border-radius: 3px; border-radius: 3px;
border: none; border: none;
} }
#admin {
margin-top: 64px;
color: red;
}
</style> </style>

View file

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

View file

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

View file

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

View file

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