Add private event invitations
This commit is contained in:
+191
-37
@@ -78,6 +78,7 @@ INITIAL_ADMIN_EMAIL = os.environ.get("INITIAL_ADMIN_EMAIL")
|
||||
|
||||
BADGE_DEFINITIONS = (
|
||||
("founder", "Gründer", "⚔️", None, "Von Anfang an dabei und Pingu Concerts mit aufgebaut", "special"),
|
||||
("admin", "Admin", "🛡️", None, "Verantwortung für Pingu Concerts", "special"),
|
||||
("beta_tester", "Beta Tester", "🧪", None, "In der Beta dabei", "beta"),
|
||||
("first_gig", "Erster Gig", "🎸", 1, "Dein erstes besuchtes Konzert", "attendance"),
|
||||
("regular", "Stammgast", "🤘", 5, "5 Konzerte besucht", "attendance"),
|
||||
@@ -299,6 +300,24 @@ def ensure_schema():
|
||||
ALTER TABLE concerts ADD COLUMN IF NOT EXISTS flyer_url TEXT
|
||||
""",
|
||||
"""
|
||||
ALTER TABLE concerts ADD COLUMN IF NOT EXISTS visibility VARCHAR(20) NOT NULL DEFAULT 'public'
|
||||
CHECK (visibility IN ('public', 'friends', 'private'))
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS event_invitations (
|
||||
concert_id INTEGER NOT NULL REFERENCES concerts(id) ON DELETE CASCADE,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
invited_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
viewed_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (concert_id, user_id)
|
||||
)
|
||||
""",
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS idx_event_invitations_user
|
||||
ON event_invitations (user_id, viewed_at)
|
||||
""",
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS friendships (
|
||||
id SERIAL PRIMARY KEY,
|
||||
requester_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
@@ -511,7 +530,8 @@ def user_from_row(row):
|
||||
"is_admin": bool(row[3]),
|
||||
"pending_friend_count": row[4],
|
||||
"unread_message_count": row[5],
|
||||
"notification_count": row[4] + row[5],
|
||||
"event_invitation_count": row[6],
|
||||
"notification_count": row[4] + row[5] + row[6],
|
||||
}
|
||||
|
||||
|
||||
@@ -533,7 +553,9 @@ def get_current_user(request: Request):
|
||||
(SELECT COUNT(*) FROM friendships
|
||||
WHERE addressee_id = users.id AND status = 'pending'),
|
||||
(SELECT COUNT(*) FROM direct_messages
|
||||
WHERE recipient_id = users.id AND read_at IS NULL)
|
||||
WHERE recipient_id = users.id AND read_at IS NULL),
|
||||
(SELECT COUNT(*) FROM event_invitations
|
||||
WHERE user_id = users.id AND viewed_at IS NULL)
|
||||
FROM sessions
|
||||
JOIN users
|
||||
ON users.id = sessions.user_id
|
||||
@@ -700,7 +722,8 @@ def load_concert(concert_id: int):
|
||||
concerts.parent_event_id,
|
||||
parent_event.artist,
|
||||
parent_event.event_type,
|
||||
concerts.flyer_url
|
||||
concerts.flyer_url,
|
||||
concerts.visibility
|
||||
FROM concerts
|
||||
LEFT JOIN venues
|
||||
ON concerts.venue_id = venues.id
|
||||
@@ -729,6 +752,7 @@ def load_concert(concert_id: int):
|
||||
"ticket_price": row[6],
|
||||
"flyer_path": row[7],
|
||||
"flyer_url": row[21],
|
||||
"visibility": row[22],
|
||||
"created_by": row[8],
|
||||
"is_past": is_past,
|
||||
"date": start_datetime.strftime("%d.%m.%Y"),
|
||||
@@ -786,8 +810,12 @@ def can_delete_concert(user, concert) -> bool:
|
||||
return user["is_admin"] or user["id"] == concert["created_by"]
|
||||
|
||||
|
||||
def can_manage_event_access(user, concert) -> bool:
|
||||
return bool(user and (user["is_admin"] or user["id"] == concert["created_by"]))
|
||||
|
||||
|
||||
def serialize_concert_card(row):
|
||||
concert_id, artist, start_datetime, end_datetime, venue, city, event_type, parent_event_id = row
|
||||
concert_id, artist, start_datetime, end_datetime, venue, city, event_type, parent_event_id, visibility, is_invited = row
|
||||
venue_text = venue or "Veranstaltungsort unbekannt"
|
||||
if city:
|
||||
venue_text += f", {city}"
|
||||
@@ -802,6 +830,8 @@ def serialize_concert_card(row):
|
||||
"event_type": event_type,
|
||||
"event_type_label": EVENT_TYPES[event_type],
|
||||
"parent_event_id": parent_event_id,
|
||||
"visibility": visibility,
|
||||
"is_invited": bool(is_invited),
|
||||
"children": [],
|
||||
"_start_datetime": start_datetime,
|
||||
"_end_datetime": end_datetime,
|
||||
@@ -830,18 +860,38 @@ def build_event_overview(rows):
|
||||
return upcoming, past
|
||||
|
||||
|
||||
def load_event_overview(search_query: str = ""):
|
||||
def load_event_overview(user, search_query: str = ""):
|
||||
query = """
|
||||
SELECT concerts.id, concerts.artist, concerts.start_datetime,
|
||||
concerts.end_datetime, venues.name, venues.city,
|
||||
concerts.event_type, concerts.parent_event_id
|
||||
concerts.event_type, concerts.parent_event_id,
|
||||
concerts.visibility,
|
||||
EXISTS (SELECT 1 FROM event_invitations ei
|
||||
WHERE ei.concert_id = concerts.id AND ei.user_id = %s)
|
||||
FROM concerts
|
||||
LEFT JOIN venues ON concerts.venue_id = venues.id
|
||||
WHERE %s
|
||||
OR concerts.visibility = 'public'
|
||||
OR concerts.created_by = %s
|
||||
OR (
|
||||
concerts.visibility = 'friends' AND EXISTS (
|
||||
SELECT 1 FROM friendships f
|
||||
WHERE f.status = 'accepted'
|
||||
AND ((f.requester_id = concerts.created_by AND f.addressee_id = %s)
|
||||
OR (f.addressee_id = concerts.created_by AND f.requester_id = %s))
|
||||
)
|
||||
)
|
||||
OR (
|
||||
concerts.visibility = 'private' AND EXISTS (
|
||||
SELECT 1 FROM event_invitations ei
|
||||
WHERE ei.concert_id = concerts.id AND ei.user_id = %s
|
||||
)
|
||||
)
|
||||
ORDER BY concerts.start_datetime ASC
|
||||
"""
|
||||
with get_db_connection() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(query)
|
||||
cursor.execute(query, (user["id"], user["is_admin"], user["id"], user["id"], user["id"], user["id"]))
|
||||
upcoming, past = build_event_overview(cursor.fetchall())
|
||||
|
||||
term = search_query.strip().casefold()
|
||||
@@ -858,6 +908,49 @@ def load_event_overview(search_query: str = ""):
|
||||
)
|
||||
|
||||
|
||||
def can_view_event(user, concert) -> bool:
|
||||
if user["is_admin"] or concert["visibility"] == "public" or concert["created_by"] == user["id"]:
|
||||
return True
|
||||
with get_db_connection() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
if concert["visibility"] == "private":
|
||||
cursor.execute(
|
||||
"SELECT 1 FROM event_invitations WHERE concert_id = %s AND user_id = %s",
|
||||
(concert["id"], user["id"]),
|
||||
)
|
||||
else:
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT 1 FROM friendships WHERE status = 'accepted'
|
||||
AND ((requester_id = %s AND addressee_id = %s)
|
||||
OR (addressee_id = %s AND requester_id = %s))
|
||||
""",
|
||||
(concert["created_by"], user["id"], concert["created_by"], user["id"]),
|
||||
)
|
||||
return cursor.fetchone() is not None
|
||||
|
||||
|
||||
def get_invitable_users(exclude_user_id: int):
|
||||
with get_db_connection() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT id, username, COALESCE(display_name, username)
|
||||
FROM users WHERE id <> %s
|
||||
ORDER BY COALESCE(display_name, username), username
|
||||
""",
|
||||
(exclude_user_id,),
|
||||
)
|
||||
return [{"id": row[0], "username": row[1], "display_name": row[2]} for row in cursor.fetchall()]
|
||||
|
||||
|
||||
def get_event_invitee_ids(concert_id: int):
|
||||
with get_db_connection() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("SELECT user_id FROM event_invitations WHERE concert_id = %s", (concert_id,))
|
||||
return {row[0] for row in cursor.fetchall()}
|
||||
|
||||
|
||||
def search_users(search_query: str):
|
||||
term = search_query.strip()
|
||||
if not term:
|
||||
@@ -1055,10 +1148,18 @@ def load_profile(username: str):
|
||||
return None
|
||||
|
||||
cursor.execute(
|
||||
"SELECT badge_code FROM user_badges WHERE user_id = %s",
|
||||
"SELECT badge_code, awarded_at FROM user_badges WHERE user_id = %s",
|
||||
(row[0],),
|
||||
)
|
||||
earned_codes = {badge_row[0] for badge_row in cursor.fetchall()}
|
||||
badge_rows = cursor.fetchall()
|
||||
earned_codes = {badge_row[0] for badge_row in badge_rows}
|
||||
badge_awarded_at = {badge_row[0]: badge_row[1] for badge_row in badge_rows}
|
||||
|
||||
is_founder = row[1].casefold() == "kai"
|
||||
if is_founder:
|
||||
earned_codes.add("founder")
|
||||
if row[7]:
|
||||
earned_codes.add("admin")
|
||||
|
||||
return {
|
||||
"id": row[0],
|
||||
@@ -1071,7 +1172,9 @@ def load_profile(username: str):
|
||||
"instagram_handle": row[5].rstrip("/").rsplit("/", 1)[-1] if row[5] else None,
|
||||
"profile_visibility": row[6],
|
||||
"is_admin": bool(row[7]),
|
||||
"is_founder": is_founder,
|
||||
"earned_codes": earned_codes,
|
||||
"badge_awarded_at": badge_awarded_at,
|
||||
}
|
||||
|
||||
|
||||
@@ -1567,7 +1670,6 @@ def update_user_role(
|
||||
request: Request,
|
||||
user_id: int,
|
||||
role: str = Form(...),
|
||||
founder_badge: str = Form(""),
|
||||
):
|
||||
user = require_admin(request)
|
||||
|
||||
@@ -1587,20 +1689,6 @@ def update_user_role(
|
||||
"UPDATE users SET is_admin = %s WHERE id = %s",
|
||||
(role == "admin", user_id),
|
||||
)
|
||||
if founder_badge == "on":
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO user_badges (user_id, badge_code)
|
||||
VALUES (%s, 'founder')
|
||||
ON CONFLICT (user_id, badge_code) DO NOTHING
|
||||
""",
|
||||
(user_id,),
|
||||
)
|
||||
else:
|
||||
cursor.execute(
|
||||
"DELETE FROM user_badges WHERE user_id = %s AND badge_code = 'founder'",
|
||||
(user_id,),
|
||||
)
|
||||
connection.commit()
|
||||
|
||||
return RedirectResponse("/admin/users", status_code=303)
|
||||
@@ -1981,11 +2069,12 @@ def logout(request: Request):
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
def home(request: Request, q: str = ""):
|
||||
upcoming_concerts, _past_concerts = load_event_overview(q)
|
||||
user = get_current_user(request)
|
||||
upcoming_concerts, _past_concerts = load_event_overview(user, q)
|
||||
|
||||
template = templates.get_template("index.html")
|
||||
return template.render(
|
||||
user=get_current_user(request),
|
||||
user=user,
|
||||
upcoming_concerts=upcoming_concerts,
|
||||
past_concerts=[],
|
||||
archive=False,
|
||||
@@ -1996,10 +2085,11 @@ def home(request: Request, q: str = ""):
|
||||
|
||||
@app.get("/events/past", response_class=HTMLResponse)
|
||||
def past_events(request: Request, q: str = ""):
|
||||
_upcoming_concerts, past_concerts = load_event_overview(q)
|
||||
user = get_current_user(request)
|
||||
_upcoming_concerts, past_concerts = load_event_overview(user, q)
|
||||
template = templates.get_template("index.html")
|
||||
return template.render(
|
||||
user=get_current_user(request),
|
||||
user=user,
|
||||
upcoming_concerts=[],
|
||||
past_concerts=past_concerts,
|
||||
archive=True,
|
||||
@@ -2102,9 +2192,11 @@ def render_profile(
|
||||
"category": category,
|
||||
"earned": code in profile["earned_codes"],
|
||||
"image_path": badge_assets.get(code),
|
||||
"sort_key": (0, datetime.min) if code == "founder" else (1, datetime.min) if code == "admin" else (2, profile["badge_awarded_at"].get(code) or datetime.max),
|
||||
}
|
||||
for code, name, icon, threshold, description, category in BADGE_DEFINITIONS
|
||||
]
|
||||
badges.sort(key=lambda badge: badge["sort_key"])
|
||||
|
||||
template = templates.get_template("profile.html")
|
||||
return HTMLResponse(
|
||||
@@ -2416,7 +2508,8 @@ def new_concert(request: Request):
|
||||
return login_redirect("/concerts/new")
|
||||
|
||||
template = templates.get_template("new_concert.html")
|
||||
return template.render(user=user, linkable_events=get_linkable_events())
|
||||
return template.render(user=user, linkable_events=get_linkable_events(),
|
||||
invitable_users=get_invitable_users(user["id"]))
|
||||
|
||||
|
||||
# ============================================================
|
||||
@@ -2429,13 +2522,21 @@ def new_concert(request: Request):
|
||||
)
|
||||
def concert_detail(request: Request, concert_id: int):
|
||||
concert = load_concert(concert_id)
|
||||
user = get_current_user(request)
|
||||
|
||||
if not concert:
|
||||
if not concert or not can_view_event(user, concert):
|
||||
return HTMLResponse(
|
||||
"<h1>Veranstaltung nicht gefunden</h1>",
|
||||
status_code=404
|
||||
)
|
||||
|
||||
with get_db_connection() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"UPDATE event_invitations SET viewed_at = CURRENT_TIMESTAMP WHERE concert_id = %s AND user_id = %s AND viewed_at IS NULL",
|
||||
(concert_id, user["id"]),
|
||||
)
|
||||
connection.commit()
|
||||
user = get_current_user(request)
|
||||
|
||||
with get_db_connection() as connection:
|
||||
@@ -2561,6 +2662,8 @@ async def create_concert(
|
||||
artist: str = Form(...),
|
||||
event_type: str = Form("concert"),
|
||||
parent_event_id: str = Form(""),
|
||||
visibility: str = Form("public"),
|
||||
invited_user_ids: list[int] = Form(default=[]),
|
||||
venue_id: str = Form(""),
|
||||
venue_name: str = Form(""),
|
||||
city: str = Form(""),
|
||||
@@ -2588,6 +2691,10 @@ async def create_concert(
|
||||
return HTMLResponse("<h1>Bei Festivals ist ein Enddatum erforderlich.</h1>", status_code=400)
|
||||
if event_type != "festival":
|
||||
end_datetime = ""
|
||||
if visibility not in {"public", "friends", "private"}:
|
||||
return HTMLResponse("<h1>Ungültige Sichtbarkeit.</h1>", status_code=400)
|
||||
if event_type != "other":
|
||||
visibility = "public"
|
||||
if end_datetime and datetime.fromisoformat(end_datetime) < datetime.fromisoformat(start_datetime):
|
||||
return HTMLResponse("<h1>Das Enddatum darf nicht vor dem Beginn liegen.</h1>", status_code=400)
|
||||
try:
|
||||
@@ -2639,10 +2746,11 @@ async def create_concert(
|
||||
ticket_price,
|
||||
flyer_path,
|
||||
flyer_url,
|
||||
visibility,
|
||||
created_by
|
||||
)
|
||||
VALUES (
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s
|
||||
)
|
||||
RETURNING id
|
||||
""",
|
||||
@@ -2658,11 +2766,22 @@ async def create_concert(
|
||||
ticket_price or None,
|
||||
flyer_path,
|
||||
normalized_flyer_url,
|
||||
visibility,
|
||||
user["id"],
|
||||
),
|
||||
)
|
||||
|
||||
concert_id = cursor.fetchone()[0]
|
||||
if visibility == "private" and invited_user_ids:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO event_invitations (concert_id, user_id, invited_by)
|
||||
SELECT %s, id, %s FROM users
|
||||
WHERE id = ANY(%s) AND id <> %s
|
||||
ON CONFLICT (concert_id, user_id) DO NOTHING
|
||||
""",
|
||||
(concert_id, user["id"], invited_user_ids, user["id"]),
|
||||
)
|
||||
|
||||
connection.commit()
|
||||
|
||||
@@ -2690,7 +2809,7 @@ def edit_concert_page(request: Request, concert_id: int):
|
||||
|
||||
concert = load_concert(concert_id)
|
||||
|
||||
if not concert:
|
||||
if not concert or not can_view_event(user, concert):
|
||||
return HTMLResponse(
|
||||
"<h1>Veranstaltung nicht gefunden</h1>",
|
||||
status_code=404
|
||||
@@ -2710,6 +2829,9 @@ def edit_concert_page(request: Request, concert_id: int):
|
||||
can_edit_title=can_edit_title(user, concert),
|
||||
can_edit_details=can_edit_details(user, concert),
|
||||
can_delete=can_delete_concert(user, concert),
|
||||
invitable_users=get_invitable_users(user["id"]),
|
||||
invited_user_ids=get_event_invitee_ids(concert_id),
|
||||
can_manage_access=can_manage_event_access(user, concert),
|
||||
)
|
||||
|
||||
|
||||
@@ -2720,6 +2842,8 @@ async def edit_concert(
|
||||
artist: str = Form(""),
|
||||
event_type: str = Form("concert"),
|
||||
parent_event_id: str = Form(""),
|
||||
visibility: str = Form("public"),
|
||||
invited_user_ids: list[int] = Form(default=[]),
|
||||
venue_id: str = Form(""),
|
||||
venue_name: str = Form(""),
|
||||
city: str = Form(""),
|
||||
@@ -2743,7 +2867,7 @@ async def edit_concert(
|
||||
|
||||
concert = load_concert(concert_id)
|
||||
|
||||
if not concert:
|
||||
if not concert or not can_view_event(user, concert):
|
||||
return HTMLResponse(
|
||||
"<h1>Veranstaltung nicht gefunden</h1>",
|
||||
status_code=404
|
||||
@@ -2768,15 +2892,25 @@ async def edit_concert(
|
||||
next_flyer_url = concert["flyer_url"]
|
||||
next_venue_id = concert["venue"]["id"]
|
||||
next_event_type = concert["event_type"]
|
||||
next_visibility = concert["visibility"]
|
||||
next_parent_event_id = concert["parent_event"]["id"] if concert["parent_event"] else None
|
||||
|
||||
if can_edit_details(user, concert):
|
||||
if not can_manage_event_access(user, concert):
|
||||
event_type = concert["event_type"]
|
||||
if event_type not in EVENT_TYPES:
|
||||
return HTMLResponse("<h1>Ungültige Veranstaltungskategorie.</h1>", status_code=400)
|
||||
if event_type == "festival" and not end_datetime:
|
||||
return HTMLResponse("<h1>Bei Festivals ist ein Enddatum erforderlich.</h1>", status_code=400)
|
||||
if event_type != "festival":
|
||||
end_datetime = ""
|
||||
if can_manage_event_access(user, concert):
|
||||
if visibility not in {"public", "friends", "private"}:
|
||||
return HTMLResponse("<h1>Ungültige Sichtbarkeit.</h1>", status_code=400)
|
||||
if event_type != "other":
|
||||
visibility = "public"
|
||||
else:
|
||||
visibility = concert["visibility"]
|
||||
effective_start = start_datetime or concert["start_local"]
|
||||
if end_datetime and datetime.fromisoformat(end_datetime) < datetime.fromisoformat(effective_start):
|
||||
return HTMLResponse("<h1>Das Enddatum darf nicht vor dem Beginn liegen.</h1>", status_code=400)
|
||||
@@ -2820,6 +2954,7 @@ async def edit_concert(
|
||||
next_start = start_datetime or concert["start_datetime"]
|
||||
next_end = end_datetime or None
|
||||
next_event_type = event_type
|
||||
next_visibility = visibility
|
||||
next_description = description or None
|
||||
next_ticket_url = ticket_url or None
|
||||
next_ticket_price = ticket_price or None
|
||||
@@ -2843,6 +2978,7 @@ async def edit_concert(
|
||||
ticket_price = %s,
|
||||
flyer_path = %s,
|
||||
flyer_url = %s
|
||||
, visibility = %s
|
||||
WHERE id = %s
|
||||
""",
|
||||
(
|
||||
@@ -2857,9 +2993,26 @@ async def edit_concert(
|
||||
next_ticket_price,
|
||||
next_flyer,
|
||||
next_flyer_url,
|
||||
next_visibility,
|
||||
concert_id,
|
||||
),
|
||||
)
|
||||
if can_manage_event_access(user, concert) and next_visibility == "private":
|
||||
cursor.execute(
|
||||
"DELETE FROM event_invitations WHERE concert_id = %s AND NOT (user_id = ANY(%s))",
|
||||
(concert_id, invited_user_ids or [0]),
|
||||
)
|
||||
if invited_user_ids:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO event_invitations (concert_id, user_id, invited_by)
|
||||
SELECT %s, id, %s FROM users WHERE id = ANY(%s) AND id <> %s
|
||||
ON CONFLICT (concert_id, user_id) DO NOTHING
|
||||
""",
|
||||
(concert_id, user["id"], invited_user_ids, user["id"]),
|
||||
)
|
||||
elif can_manage_event_access(user, concert):
|
||||
cursor.execute("DELETE FROM event_invitations WHERE concert_id = %s", (concert_id,))
|
||||
connection.commit()
|
||||
|
||||
return RedirectResponse(
|
||||
@@ -2877,7 +3030,7 @@ def delete_concert(request: Request, concert_id: int):
|
||||
|
||||
concert = load_concert(concert_id)
|
||||
|
||||
if not concert:
|
||||
if not concert or not can_view_event(user, concert):
|
||||
return HTMLResponse(
|
||||
"<h1>Veranstaltung nicht gefunden</h1>",
|
||||
status_code=404
|
||||
@@ -2918,7 +3071,8 @@ def set_attendance(
|
||||
return login_redirect(f"/concerts/{concert_id}")
|
||||
if status not in {"attending", "maybe", "ticket_search", "ticket_offer"}:
|
||||
return HTMLResponse("<h1>Ungültige Auswahl</h1>", status_code=400)
|
||||
if not load_concert(concert_id):
|
||||
concert = load_concert(concert_id)
|
||||
if not concert or not can_view_event(user, concert):
|
||||
return HTMLResponse("<h1>Veranstaltung nicht gefunden</h1>", status_code=404)
|
||||
|
||||
with get_db_connection() as connection:
|
||||
@@ -2952,7 +3106,7 @@ def add_comment(
|
||||
|
||||
concert = load_concert(concert_id)
|
||||
|
||||
if not concert:
|
||||
if not concert or not can_view_event(user, concert):
|
||||
return HTMLResponse(
|
||||
"<h1>Veranstaltung nicht gefunden</h1>",
|
||||
status_code=404
|
||||
@@ -3006,7 +3160,7 @@ async def add_photo(
|
||||
|
||||
concert = load_concert(concert_id)
|
||||
|
||||
if not concert:
|
||||
if not concert or not can_view_event(user, concert):
|
||||
return HTMLResponse(
|
||||
"<h1>Veranstaltung nicht gefunden</h1>",
|
||||
status_code=404
|
||||
|
||||
Reference in New Issue
Block a user