diff --git a/app/locales/en.json b/app/locales/en.json index c126770..fadd82a 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -550,5 +550,8 @@ "Die Push-Registrierung ist momentan nicht möglich. Bitte versuche es später erneut.": "Push registration is currently unavailable. Please try again later.", "FCM-Testtoken anzeigen (nur Debug-App)": "Show FCM test token (debug app only)", "Für Android-Benachrichtigungen speichern wir die Gerätekennung, den FCM-Registrierungstoken, die App-Version und die Zuordnung zur aktuellen Anmeldung. Beim Abmelden wird die Zuordnung gelöscht. Firebase verarbeitet die für die Push-Zustellung erforderlichen Gerätedaten.": "For Android notifications, we store the device identifier, FCM registration token, app version and link to the current login session. Logging out deletes the link. Firebase processes the device data required to deliver push notifications.", - "Bugmeldungen werden mit Benutzername und User-ID an unser internes Gitea-Ticketsystem übertragen. Technische Zusatzinformationen werden nur auf Wunsch mitgesendet. Lokale Versandkennungen zur Vermeidung doppelter Meldungen laufen nach 24 Stunden ab.": "Bug reports are sent to our internal Gitea ticket system with your username and user ID. Additional technical information is sent only if you choose to include it. Local submission identifiers used to prevent duplicate reports expire after 24 hours." + "Bugmeldungen werden mit Benutzername und User-ID an unser internes Gitea-Ticketsystem übertragen. Technische Zusatzinformationen werden nur auf Wunsch mitgesendet. Lokale Versandkennungen zur Vermeidung doppelter Meldungen laufen nach 24 Stunden ab.": "Bug reports are sent to our internal Gitea ticket system with your username and user ID. Additional technical information is sent only if you choose to include it. Local submission identifiers used to prevent duplicate reports expire after 24 hours.", + "Freunde einladen": "Invite friends", + "Noch keine bestätigten Freunde vorhanden.": "No confirmed friends yet.", + "Es können nur bestätigte Freunde eingeladen werden.": "Only confirmed friends can be invited." } diff --git a/app/main.py b/app/main.py index e1dad58..e2b04b1 100644 --- a/app/main.py +++ b/app/main.py @@ -1027,11 +1027,7 @@ def can_edit_concert(user, concert) -> bool: def can_delete_concert(user, concert) -> bool: - if not user: - return False - if concert["is_past"]: - return user["is_admin"] - return user["is_admin"] or user["id"] == concert["created_by"] + return bool(user and (user["is_admin"] or user["id"] == concert["created_by"])) def can_manage_event_access(user, concert) -> bool: @@ -1167,20 +1163,42 @@ def can_view_event(user, concert) -> bool: return cursor.fetchone() is not None -def get_invitable_users(exclude_user_id: int): +def get_invitable_users(user_id: int): + """Only confirmed, unblocked friends of the event owner can be newly invited.""" 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 + SELECT u.id, u.username, COALESCE(u.display_name, u.username) + FROM users u WHERE u.id <> %s + AND EXISTS ( + SELECT 1 FROM friendships f WHERE f.status = 'accepted' + AND ((f.requester_id = %s AND f.addressee_id = u.id) + OR (f.addressee_id = %s AND f.requester_id = u.id)) + ) + AND NOT EXISTS ( + SELECT 1 FROM user_blocks b + WHERE (b.blocker_id = %s AND b.blocked_id = u.id) + OR (b.blocked_id = %s AND b.blocker_id = u.id) + ) + ORDER BY COALESCE(u.display_name, u.username), u.username """, - (exclude_user_id,), + (user_id, user_id, user_id, user_id, user_id), ) return [{"id": row[0], "username": row[1], "display_name": row[2]} for row in cursor.fetchall()] +def resolve_invitee_selection(owner_id: int, selected_ids, concert_id=None): + eligible = {member["id"] for member in get_invitable_users(owner_id)} + selected = set(selected_ids) + if not selected <= eligible: + raise ValueError(_("Es können nur bestätigte Freunde eingeladen werden.")) + if concert_id is not None: + # Existing invitations outside the picker must not disappear on unrelated edits. + selected |= get_event_invitee_ids(concert_id) - eligible + return sorted(selected) + + def get_event_invitee_ids(concert_id: int): with get_db_connection() as connection: with connection.cursor() as cursor: @@ -4063,6 +4081,11 @@ async def create_concert( return HTMLResponse(_("