Compare commits

..
16 Commits
Author SHA1 Message Date
ben cfbd561763 lol 2026-08-20 17:19:22 +02:00
ben fcb6690af5 lol 2026-08-20 16:59:27 +02:00
Ben de Roo 08e02dd556 ja 2026-08-20 16:51:58 +02:00
ben a980812137 msg 2026-08-20 12:23:30 +02:00
ben e61750f314 kaas is toch zo lekker 2026-08-20 09:06:47 +02:00
ben af1c147fd0 kaas is toch zo lekker 2026-08-20 09:04:41 +02:00
ben 792ce850b6 ik hou van kaas 2026-08-19 14:17:30 +02:00
ben fae25adb99 ik hou van kaas 2026-08-19 12:18:53 +02:00
ben 8fae1ffb3d commit 2026-08-19 12:10:02 +02:00
ben bfab69cbb4 ik hou van kaas 2026-08-19 11:58:37 +02:00
ben 468c34ff92 ik hou van kaas 2026-08-19 11:56:44 +02:00
ben 8fe812e8e6 fixed bugs 2026-08-19 09:38:31 +02:00
ben bf601846db new frontend
ik ben zo cool
2026-08-19 09:33:48 +02:00
ben 1822dea7e3 Clean up gitignore, stop tracking build artifacts 2026-08-19 09:32:32 +02:00
ben 63bfaf9619 update 2026-08-18 19:14:30 +02:00
ben bbb784c3f1 ik hou zo veel van kaaaas
jammie
2026-08-18 19:05:38 +02:00
16 changed files with 959 additions and 248 deletions
+27 -6
View File
@@ -1,6 +1,27 @@
.env # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
__pycache__/
*.py[cod] # dependencies
database/calendar.db /node_modules
database/calendar.db-journal
.vscode # next.js
/.next/
/out/
# production
/build
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -1
View File
@@ -102,8 +102,9 @@ def get_current_user(
algorithms=[ALGORITHM] algorithms=[ALGORITHM]
) )
return payload["user_id"] print("DEBUG JWT payload:", payload)
return payload["user_id"]
def get_user(user_id): def get_user(user_id):
return fetch_one( return fetch_one(
+216 -125
View File
@@ -1,17 +1,19 @@
import hashlib import hashlib
import secrets
import sqlite3
from datetime import datetime, timedelta, timezone
from pathlib import Path
import os import os
import secrets
import smtplib import smtplib
import sqlite3
from datetime import datetime, timedelta, timezone
from email.message import EmailMessage from email.message import EmailMessage
from pathlib import Path
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() load_dotenv()
DB_NAME = Path(__file__).parent.parent / "database" / "calendar.db" DB_NAME = Path(__file__).parent.parent / "database" / "calendar.db"
@@ -27,8 +29,198 @@ def hash_token(token: str) -> str:
token.encode("utf-8") token.encode("utf-8")
).hexdigest() ).hexdigest()
def send_email(
email: str,
subject: str,
plain_text: str,
html: str,
):
smtp_host = os.getenv("SMTP_HOST")
smtp_port = int(os.getenv("SMTP_PORT", "587"))
smtp_username = os.getenv("SMTP_USERNAME")
smtp_password = os.getenv("SMTP_PASSWORD")
smtp_from = os.getenv("SMTP_FROM")
def update_email(user_id: int, new_email: str): if not all([
smtp_host,
smtp_username,
smtp_password,
smtp_from,
]):
raise RuntimeError("SMTP configuration is incomplete")
message = EmailMessage()
message["From"] = smtp_from
message["To"] = email
message["Subject"] = subject
# Plain-text fallback
message.set_content(plain_text)
# HTML version
message.add_alternative(
html,
subtype="html",
)
print(f"Sending email to {email}")
with smtplib.SMTP(
smtp_host,
smtp_port,
) as smtp:
print("SMTP connected")
smtp.starttls()
print("TLS started")
smtp.login(
smtp_username,
smtp_password,
)
print("SMTP login successful")
smtp.send_message(message)
print("Email sent")
def send_verification_email(
email: str,
token: str,
):
verification_url = (
"https://ben.de-roo.org"
f"/api/verify-email?token={token}"
)
plain_text = f"""Hello,
You requested to use this email address for your Calendar account.
Verify your email address using this link:
{verification_url}
This link expires after 24 hours.
If you did not request this, you can ignore this email.
"""
html = f"""\
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<p>Hello,</p>
<p>
You requested to use this email address
for your Calendar account.
</p>
<p>
<a href="{verification_url}">
https://ben.de-roo.org/api/verify
</a>
</p>
<p>
This link expires after 24 hours.
</p>
<p>
If you did not request this,
you can ignore this email.
</p>
</body>
</html>
"""
send_email(
email=email,
subject="Verify your Calendar email address",
plain_text=plain_text,
html=html,
)
def send_password_reset_email(
email: str,
token: str,
):
password_reset_url = (
"https://ben.de-roo.org"
f"/api/update-password_page?token={token}"
)
plain_text = f"""Hello,
You requested to reset your Calendar password.
Reset your password using this link:
{password_reset_url}
This link expires after 1 hour.
If you did not request this, you can ignore this email.
"""
html = f"""\
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<p>Hello,</p>
<p>
You requested to reset your Calendar password.
</p>
<p>
<a href="{password_reset_url}">
https://ben.de-roo.org/api/update-password_page
</a>
</p>
<p>
This link expires after 1 hour.
</p>
<p>
If you did not request this,
you can ignore this email.
</p>
</body>
</html>
"""
send_email(
email=email,
subject="Reset your password",
plain_text=plain_text,
html=html,
)
def update_email(
user_id: int,
new_email: str,
):
new_email = new_email.strip().lower() new_email = new_email.strip().lower()
conn = get_connection() conn = get_connection()
@@ -57,7 +249,10 @@ def update_email(user_id: int, new_email: str):
WHERE email = ? WHERE email = ?
AND id != ? AND id != ?
""", """,
(new_email, user_id), (
new_email,
user_id,
),
) )
if cur.fetchone() is not None: if cur.fetchone() is not None:
@@ -73,7 +268,10 @@ def update_email(user_id: int, new_email: str):
email_verified = 0 email_verified = 0
WHERE id = ? WHERE id = ?
""", """,
(new_email, user_id), (
new_email,
user_id,
),
) )
cur.execute( cur.execute(
@@ -104,7 +302,9 @@ def update_email(user_id: int, new_email: str):
conn.close() conn.close()
def create_verification(user_id: int): def create_verification(
user_id: int,
):
token = secrets.token_urlsafe(32) token = secrets.token_urlsafe(32)
token_hash = hash_token(token) token_hash = hash_token(token)
@@ -149,7 +349,9 @@ def create_verification(user_id: int):
conn.close() conn.close()
def verify_email(token: str): def verify_email(
token: str,
):
token_hash = hash_token(token) token_hash = hash_token(token)
conn = get_connection() conn = get_connection()
@@ -181,6 +383,7 @@ def verify_email(token: str):
) )
if datetime.now(timezone.utc) >= expires_at: if datetime.now(timezone.utc) >= expires_at:
cur.execute( cur.execute(
""" """
DELETE FROM email_verifications DELETE FROM email_verifications
@@ -222,121 +425,9 @@ def verify_email(token: str):
finally: finally:
conn.close() conn.close()
def send_verification_email(email: str, token: str): def create_password_reset(
smtp_host = os.getenv("SMTP_HOST") user_id: int,
smtp_port = int(os.getenv("SMTP_PORT", "587")) ):
smtp_username = os.getenv("SMTP_USERNAME")
smtp_password = os.getenv("SMTP_PASSWORD")
smtp_from = os.getenv("SMTP_FROM")
if not all([
smtp_host,
smtp_username,
smtp_password,
smtp_from,
]):
raise RuntimeError("SMTP configuration is incomplete")
verification_url = (
f"https://ben.de-roo.org/api/verify-email?token={token}"
)
message = EmailMessage()
message["From"] = smtp_from
message["To"] = email
message["Subject"] = "Verify your Calendar email address"
message.set_content(
f"""Hello,
You requested to use this email address for your Calendar account.
Verify your email address using this link:
{verification_url}
This link expires after 24 hours.
If you did not request this, you can ignore this email.
"""
)
print(f"Sending verification email to {email}")
with smtplib.SMTP(smtp_host, smtp_port) as smtp:
print("SMTP connected")
smtp.starttls()
print("TLS started")
smtp.login(
smtp_username,
smtp_password,
)
print("SMTP login successful")
smtp.send_message(message)
print("Verification email sent")
def send_password_reset_email(email: str, token: str):
smtp_host = os.getenv("SMTP_HOST")
smtp_port = int(os.getenv("SMTP_PORT", "587"))
smtp_username = os.getenv("SMTP_USERNAME")
smtp_password = os.getenv("SMTP_PASSWORD")
smtp_from = os.getenv("SMTP_FROM")
if not all([
smtp_host,
smtp_username,
smtp_password,
smtp_from,
]):
raise RuntimeError("SMTP configuration is incomplete")
password_reset_url = (
f"https://ben.de-roo.org/api/update-password?token={token}"
)
message = EmailMessage()
message["From"] = smtp_from
message["To"] = email
message["Subject"] = "Reset your password"
message.set_content(
f"""Hello,
You requested to use this email address for reseting your Calendar password.
Reset your password using this link:
{password_reset_url}
This link expires after 24 hours.
If you did not request this, you can ignore this email.
"""
)
print(f"Sending password reset email to {email}")
with smtplib.SMTP(smtp_host, smtp_port) as smtp:
print("SMTP connected")
smtp.starttls()
print("TLS started")
smtp.login(
smtp_username,
smtp_password,
)
print("SMTP login successful")
smtp.send_message(message)
print("Password reset email sent")
def create_password_reset(user_id: int):
token = secrets.token_urlsafe(32) token = secrets.token_urlsafe(32)
token_hash = hash_token(token) token_hash = hash_token(token)
+1 -1
View File
@@ -1,4 +1,4 @@
from database import execute, fetch_one from database import execute, fetch_one, fetch_all
def respond_to_event(event_id, user_id, status): def respond_to_event(event_id, user_id, status):
+655 -57
View File
@@ -339,8 +339,13 @@ def api_update_member_role(
data: MemberRoleUpdate, data: MemberRoleUpdate,
current_user: int = Depends(get_current_user), current_user: int = Depends(get_current_user),
): ):
current_role = get_role(room_id, current_user) role_levels = {
target_role = get_role(room_id, user_id) "member": 1,
"admin": 2,
"owner": 3,
}
current_role = get_role(room_id, current_user) or 0
target_role = get_role(room_id, user_id) or 0
if current_role is None: if current_role is None:
raise HTTPException( raise HTTPException(
@@ -1077,57 +1082,333 @@ def api_update_email(
@app.get( @app.get(
"/verify-email", "/verify-email",
response_class=HTMLResponse, response_class=HTMLResponse,
summary="Verify email address",
) )
def api_verify_email(token: str = Query(...)): def api_verify_email(
token: str = Query(...),
):
result = verify_email(token) result = verify_email(token)
if result["status"] == "failed": if result["status"] == "failed":
if result["reason"] == "token_expired": if result["reason"] == "token_expired":
return """ return HTMLResponse(
<!DOCTYPE html> content="""
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Verification expired</title>
</head>
<body>
<h1>Verification expired</h1>
<p>This verification link has expired.</p>
</body>
</html>
"""
return """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Verification failed</title>
</head>
<body>
<h1>Verification failed</h1>
<p>This verification link is invalid.</p>
</body>
</html>
"""
return """
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email verified</title>
<title>Verification expired</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: #0b0d10;
color: #e8eaed;
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
}
.card {
width: 100%;
max-width: 460px;
padding: 40px;
background: #15181d;
border: 1px solid #2a2f36;
border-radius: 14px;
text-align: center;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
}
.icon {
width: 64px;
height: 64px;
margin: 0 auto 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #2b2115;
color: #f0a83b;
font-size: 30px;
}
h1 {
margin: 0 0 12px;
font-size: 26px;
}
p {
margin: 0;
color: #9da4ae;
line-height: 1.6;
}
.footer {
margin-top: 28px;
padding-top: 20px;
border-top: 1px solid #272c33;
color: #6f7782;
font-size: 13px;
}
</style>
</head> </head>
<body> <body>
<div class="box"> <main class="card">
<h1>Email verified</h1> <div class="icon">!</div>
<p>Your email address has been successfully verified.</p>
<p>You may now close this tab.</p> <h1>Verification expired</h1>
<p>
This email verification link has expired.
Please request a new verification email.
</p>
<div class="footer">
Calendar API
</div> </div>
</main>
</body> </body>
</html> </html>
""" """,
status_code=410,
)
return HTMLResponse(
content="""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verification failed</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: #0b0d10;
color: #e8eaed;
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
}
.card {
width: 100%;
max-width: 460px;
padding: 40px;
background: #15181d;
border: 1px solid #2a2f36;
border-radius: 14px;
text-align: center;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
}
.icon {
width: 64px;
height: 64px;
margin: 0 auto 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #2a1719;
color: #ef6461;
font-size: 30px;
}
h1 {
margin: 0 0 12px;
font-size: 26px;
}
p {
margin: 0;
color: #9da4ae;
line-height: 1.6;
}
.footer {
margin-top: 28px;
padding-top: 20px;
border-top: 1px solid #272c33;
color: #6f7782;
font-size: 13px;
}
</style>
</head>
<body>
<main class="card">
<div class="icon">×</div>
<h1>Verification failed</h1>
<p>
This verification link is invalid or has already been used.
</p>
<div class="footer">
Calendar API
</div>
</main>
</body>
</html>
""",
status_code=400,
)
return HTMLResponse(
content="""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email verified</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: #0b0d10;
color: #e8eaed;
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
}
.card {
width: 100%;
max-width: 460px;
padding: 40px;
background: #15181d;
border: 1px solid #2a2f36;
border-radius: 14px;
text-align: center;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
}
.icon {
width: 64px;
height: 64px;
margin: 0 auto 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #14271d;
color: #4ade80;
font-size: 30px;
}
h1 {
margin: 0 0 12px;
font-size: 26px;
}
p {
margin: 8px 0 0;
color: #9da4ae;
line-height: 1.6;
}
.footer {
margin-top: 28px;
padding-top: 20px;
border-top: 1px solid #272c33;
color: #6f7782;
font-size: 13px;
}
</style>
</head>
<body>
<main class="card">
<div class="icon">✓</div>
<h1>Email verified</h1>
<p>
Your email address has been successfully verified.
</p>
<p>
You may now close this tab.
</p>
<div class="footer">
Calendar API
</div>
</main>
</body>
</html>
""",
)
@app.post( @app.post(
"/forgot-password", "/forgot-password",
@@ -1142,47 +1423,194 @@ def api_forgot_password(
@app.get( @app.get(
"/update-password_page", "/update-password_page",
response_class=HTMLResponse, response_class=HTMLResponse,
summary="Password reset page",
) )
def update_password_page(token: str = Query(...)): def update_password_page(
return f""" token: str = Query(...),
):
return HTMLResponse(
content=f"""
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reset password</title> <title>Reset password</title>
<style>
* {{
box-sizing: border-box;
}}
body {{
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: #0b0d10;
color: #e8eaed;
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
}}
.card {{
width: 100%;
max-width: 460px;
padding: 40px;
background: #15181d;
border: 1px solid #2a2f36;
border-radius: 14px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
}}
h1 {{
margin: 0 0 10px;
font-size: 28px;
}}
.description {{
margin: 0 0 30px;
color: #9da4ae;
line-height: 1.6;
}}
label {{
display: block;
margin-bottom: 8px;
color: #c7ccd3;
font-size: 14px;
font-weight: 600;
}}
input {{
width: 100%;
padding: 13px 14px;
border: 1px solid #343a43;
border-radius: 8px;
background: #0f1216;
color: #e8eaed;
font-size: 15px;
outline: none;
}}
input:focus {{
border-color: #6b7280;
}}
button {{
width: 100%;
margin-top: 22px;
padding: 13px 16px;
border: 0;
border-radius: 8px;
background: #e8eaed;
color: #111318;
font-size: 15px;
font-weight: 600;
cursor: pointer;
}}
button:hover {{
background: #ffffff;
}}
.requirements {{
margin-top: 10px;
color: #6f7782;
font-size: 13px;
}}
.footer {{
margin-top: 28px;
padding-top: 20px;
border-top: 1px solid #272c33;
color: #6f7782;
font-size: 13px;
text-align: center;
}}
</style>
</head> </head>
<body> <body>
<main class="card">
<h1>Reset password</h1> <h1>Reset password</h1>
<form method="post" action="/api/update-password"> <p class="description">
Enter a new password for your account.
</p>
<form
method="post"
action="/update-password"
>
<input <input
type="hidden" type="hidden"
name="token" name="token"
value="{token}" value="{token}"
> >
<label> <label for="new_password">
New password: New password
</label>
<input <input
id="new_password"
type="password" type="password"
name="new_password" name="new_password"
minlength="8" minlength="8"
autocomplete="new-password"
required required
> >
</label>
<div class="requirements">
Minimum 8 characters.
</div>
<button type="submit"> <button type="submit">
Reset password Update password
</button> </button>
</form> </form>
<div class="footer">
Calendar API
</div>
</main>
</body> </body>
</html> </html>
""" """,
)
@app.post( @app.post(
"/update-password", "/update-password",
response_class=HTMLResponse, response_class=HTMLResponse,
summary="Reset password",
) )
def update_password( def update_password(
token: str = Form(...), token: str = Form(...),
@@ -1194,49 +1622,219 @@ def update_password(
) )
if result["status"] == "failed": if result["status"] == "failed":
if result["reason"] == "token_expired": if result["reason"] == "token_expired":
return """ return HTMLResponse(
content="""
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Reset expired</title> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reset link expired</title>
<style>
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: #0b0d10;
color: #e8eaed;
font-family: system-ui, sans-serif;
}
.card {
width: 100%;
max-width: 460px;
padding: 40px;
background: #15181d;
border: 1px solid #2a2f36;
border-radius: 14px;
text-align: center;
box-shadow: 0 20px 60px rgba(0,0,0,.45);
}
h1 {
margin-bottom: 12px;
}
p {
color: #9da4ae;
line-height: 1.6;
}
</style>
</head> </head>
<body> <body>
<main class="card">
<h1>Reset link expired</h1> <h1>Reset link expired</h1>
<p>This password reset link has expired.</p>
<p>
This password reset link has expired.
Please request a new password reset.
</p>
</main>
</body> </body>
</html> </html>
""" """,
status_code=410,
)
return """ return HTMLResponse(
content="""
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reset failed</title> <title>Reset failed</title>
<style>
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: #0b0d10;
color: #e8eaed;
font-family: system-ui, sans-serif;
}
.card {
width: 100%;
max-width: 460px;
padding: 40px;
background: #15181d;
border: 1px solid #2a2f36;
border-radius: 14px;
text-align: center;
box-shadow: 0 20px 60px rgba(0,0,0,.45);
}
h1 {
margin-bottom: 12px;
}
p {
color: #9da4ae;
line-height: 1.6;
}
</style>
</head> </head>
<body> <body>
<main class="card">
<h1>Reset failed</h1> <h1>Reset failed</h1>
<p>This password reset link is invalid.</p>
<p>
This password reset link is invalid or has already been used.
</p>
</main>
</body> </body>
</html> </html>
""" """,
status_code=400,
)
return """ return HTMLResponse(
content="""
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password updated</title> <title>Password updated</title>
<style>
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: #0b0d10;
color: #e8eaed;
font-family: system-ui, sans-serif;
}
.card {
width: 100%;
max-width: 460px;
padding: 40px;
background: #15181d;
border: 1px solid #2a2f36;
border-radius: 14px;
text-align: center;
box-shadow: 0 20px 60px rgba(0,0,0,.45);
}
.icon {
width: 64px;
height: 64px;
margin: 0 auto 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #14271d;
color: #4ade80;
font-size: 30px;
}
h1 {
margin: 0 0 12px;
}
p {
margin: 0;
color: #9da4ae;
line-height: 1.6;
}
</style>
</head> </head>
<body> <body>
<main class="card">
<div class="icon">✓</div>
<h1>Password updated</h1> <h1>Password updated</h1>
<p>Your password has been successfully changed.</p>
<p>You may now close this tab.</p> <p>
Your password has been successfully changed.
</p>
<p>
You may now close this tab.
</p>
</main>
</body> </body>
</html> </html>
""" """,
)
# easter egg # easter egg
+4 -6
View File
@@ -8,7 +8,7 @@ ROLE_LEVELS = {
} }
def get_role(room_id: int, user_id: int) -> str | None: def get_role(room_id: int, user_id: int) -> int | None:
member = fetch_one( member = fetch_one(
""" """
SELECT role SELECT role
@@ -22,8 +22,7 @@ def get_role(room_id: int, user_id: int) -> str | None:
if member is None: if member is None:
return None return None
return member["role"] return ROLE_LEVELS.get(member["role"])
def has_role( def has_role(
room_id: int, room_id: int,
@@ -36,12 +35,11 @@ def has_role(
Role hierarchy: Role hierarchy:
owner > admin > member owner > admin > member
""" """
user_role = get_role(room_id, user_id) user_level = get_role(room_id, user_id)
if user_role is None: if user_level is None:
return False return False
user_level = ROLE_LEVELS.get(user_role, 0)
required_level = ROLE_LEVELS.get(required_role, 0) required_level = ROLE_LEVELS.get(required_role, 0)
if required_level == 0: if required_level == 0:
View File
+2
View File
@@ -4,3 +4,5 @@ python-dotenv
fastapi fastapi
pydantic pydantic
uvicorn uvicorn
email-validator
python-multipart