Add user follows to following feed

This commit is contained in:
2026-09-15 09:22:25 +02:00
parent 55174684af
commit c4a84ec135
12 changed files with 213 additions and 28 deletions
+103 -15
View File
@@ -2664,6 +2664,13 @@ def render_profile(
or bool(viewer and viewer["is_admin"])
or bool(friendship and friendship["status"] == "accepted")
)
is_following_user = False
if viewer and not is_own_profile and can_view_details and not any(block_status.values()):
with get_db_connection() as connection:
is_following_user = connection.execute(
"SELECT EXISTS (SELECT 1 FROM followed_users WHERE follower_id=%s AND followed_id=%s)",
(viewer["id"], profile["id"]),
).fetchone()[0]
connections = {"incoming": [], "outgoing": [], "friends": [], "blocked": []}
if is_own_profile:
with get_db_connection() as connection:
@@ -2760,6 +2767,7 @@ def render_profile(
badges=badges,
is_own_profile=force_own or is_own_profile,
can_view_details=can_view_details,
is_following_user=is_following_user,
friendship=friendship,
block_status=block_status,
connections=connections,
@@ -3095,6 +3103,44 @@ def send_friend_request(request: Request, username: str):
return RedirectResponse(f"/users/{profile['username']}", status_code=303)
@app.post("/users/{username}/follow")
def follow_user(request: Request, username: str, action: str = Form("follow")):
user = get_current_user(request)
if action not in {"follow", "unfollow"}:
return HTMLResponse(_("Ungültige Aktion."), status_code=400)
with get_db_connection() as connection:
with connection.cursor() as cursor:
cursor.execute("SELECT id, username, profile_visibility, is_admin FROM users WHERE LOWER(username)=LOWER(%s)",
(username,))
profile = cursor.fetchone()
if not profile:
return HTMLResponse(_("Benutzer nicht gefunden."), status_code=404)
if profile[0] == user["id"]:
return HTMLResponse(_("Du kannst dir nicht selbst folgen."), status_code=400)
if action == "unfollow":
cursor.execute("DELETE FROM followed_users WHERE follower_id=%s AND followed_id=%s",
(user["id"], profile[0]))
else:
cursor.execute("""
SELECT EXISTS (SELECT 1 FROM friendships f WHERE f.status='accepted' AND
((f.requester_id=%s AND f.addressee_id=%s) OR
(f.requester_id=%s AND f.addressee_id=%s))) AS is_friend,
EXISTS (SELECT 1 FROM user_blocks b WHERE
(b.blocker_id=%s AND b.blocked_id=%s) OR
(b.blocker_id=%s AND b.blocked_id=%s)) AS is_blocked
""", (user["id"], profile[0], profile[0], user["id"],
user["id"], profile[0], profile[0], user["id"]))
row = cursor.fetchone()
if row[1] or not (profile[2] == "public" or row[0] or user["is_admin"]):
return HTMLResponse(_("Diesem Profil kannst du nicht folgen."), status_code=403)
cursor.execute("""
INSERT INTO followed_users (follower_id, followed_id) VALUES (%s,%s)
ON CONFLICT (follower_id, followed_id) DO NOTHING
""", (user["id"], profile[0]))
connection.commit()
return RedirectResponse(f"/users/{profile[1]}", status_code=303)
@app.post("/users/{username}/block")
def block_user(request: Request, username: str):
user = get_current_user(request)
@@ -3120,6 +3166,10 @@ def block_user(request: Request, username: str):
""",
(user["id"], profile["id"], profile["id"], user["id"]),
)
cursor.execute("""
DELETE FROM followed_users WHERE
(follower_id=%s AND followed_id=%s) OR (follower_id=%s AND followed_id=%s)
""", (user["id"], profile["id"], profile["id"], user["id"]))
connection.commit()
return RedirectResponse(f"/users/{profile['username']}", status_code=303)
@@ -3398,6 +3448,14 @@ def following_page(request: Request):
(user["id"],),
)
followed_venues = [{"id": row[0], "name": row[1], "city": row[2]} for row in cursor.fetchall()]
cursor.execute("""
SELECT u.id, u.username, COALESCE(u.display_name, u.username), u.avatar_path
FROM followed_users fu JOIN users u ON u.id=fu.followed_id
WHERE fu.follower_id=%s
ORDER BY COALESCE(u.display_name, u.username), u.username
""", (user["id"],))
followed_users = [{"id": row[0], "username": row[1], "name": row[2], "avatar_path": row[3]}
for row in cursor.fetchall()]
cursor.execute(
"""
SELECT c.id, c.artist, c.start_datetime, c.venue_id,
@@ -3424,28 +3482,47 @@ def following_page(request: Request):
candidate_bands = {}
for concert_id, band_key, display_name in cursor.fetchall():
candidate_bands.setdefault(concert_id, []).append({"key": band_key, "name": display_name})
cursor.execute("""
SELECT ca.concert_id, u.username, COALESCE(u.display_name, u.username)
FROM concert_attendance ca
JOIN followed_users fu ON fu.followed_id=ca.user_id AND fu.follower_id=%s
JOIN users u ON u.id=ca.user_id
WHERE ca.concert_id=ANY(%s) AND ca.status='attending'
AND (u.profile_visibility='public' OR %s OR EXISTS (
SELECT 1 FROM friendships f WHERE f.status='accepted' AND
((f.requester_id=%s AND f.addressee_id=u.id) OR
(f.requester_id=u.id AND f.addressee_id=%s))))
AND NOT EXISTS (SELECT 1 FROM user_blocks b WHERE
(b.blocker_id=%s AND b.blocked_id=u.id) OR
(b.blocker_id=u.id AND b.blocked_id=%s))
ORDER BY ca.concert_id, COALESCE(u.display_name, u.username), u.username
""", (user["id"], candidate_ids or [0], user["is_admin"], user["id"], user["id"],
user["id"], user["id"]))
attending_followed_users = {}
for concert_id, username, display_name in cursor.fetchall():
attending_followed_users.setdefault(concert_id, []).append(
{"username": username, "name": display_name})
band_names = [band["name"] for band in followed_bands]
venue_ids = {venue["id"] for venue in followed_venues}
events = [
{
"id": row[0], "artist": row[1], "date": row[2].strftime("%d.%m.%Y"),
"time": format_time(row[2]), "venue": ", ".join(filter(None, (row[4], row[5]))),
"matched_band": any(
artist_names_similar(event_band["name"], followed_band)
for event_band in (candidate_bands.get(row[0]) or parse_band_names("", row[1], row[8]))
for followed_band in band_names
),
"matched_venue": row[3] in venue_ids,
}
for row in candidates
if row[3] in venue_ids or any(
events = []
for row in candidates:
matched_band = any(
artist_names_similar(event_band["name"], followed_band)
for event_band in (candidate_bands.get(row[0]) or parse_band_names("", row[1], row[8]))
for followed_band in band_names
)
]
matched_venue = row[3] in venue_ids
going_users = attending_followed_users.get(row[0], [])
if matched_band or matched_venue or going_users:
events.append({
"id": row[0], "artist": row[1], "date": row[2].strftime("%d.%m.%Y"),
"time": format_time(row[2]), "venue": ", ".join(filter(None, (row[4], row[5]))),
"matched_band": matched_band, "matched_venue": matched_venue,
"going_users": going_users,
})
return templates.get_template("following.html").render(
user=user, followed_bands=followed_bands, followed_venues=followed_venues, events=events
user=user, followed_bands=followed_bands, followed_venues=followed_venues,
followed_users=followed_users, events=events
)
@@ -3469,6 +3546,17 @@ def remove_followed_venue(request: Request, venue_id: int = Form(...)):
return RedirectResponse("/following", status_code=303)
@app.post("/following/users/remove")
def remove_followed_user(request: Request, followed_id: int = Form(...)):
user = get_current_user(request)
with get_db_connection() as connection:
with connection.cursor() as cursor:
cursor.execute("DELETE FROM followed_users WHERE follower_id=%s AND followed_id=%s",
(user["id"], followed_id))
connection.commit()
return RedirectResponse("/following", status_code=303)
@app.post("/concerts/{concert_id}/follow-band")
def follow_band(request: Request, concert_id: int, band_key: str = Form(...), action: str = Form("follow")):
user = get_current_user(request)