fix: profile shows only own reviews, federation display name in community

- diary API accepts user_id filter, profile page passes it
- movie social feed joins ap_remote_actors for handle resolution
- COALESCE prefers actor handle for remote reviews, email for local
This commit is contained in:
2026-07-10 00:19:23 +02:00
parent 206ad44e82
commit 2e2adef5e0
8 changed files with 19 additions and 11 deletions

View File

@@ -343,7 +343,8 @@ impl DiaryRepository for PostgresDiaryRepository {
to_char(r.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS created_at, to_char(r.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS created_at,
r.remote_actor_url, r.remote_actor_url,
r.watch_medium, r.watch_medium,
COALESCE(u.email, a.handle, r.remote_actor_url) AS user_email CASE WHEN r.remote_actor_url IS NOT NULL THEN COALESCE(a.handle, r.remote_actor_url)
ELSE COALESCE(u.email, r.user_id) END AS user_email
FROM reviews r FROM reviews r
INNER JOIN movies m ON m.id = r.movie_id INNER JOIN movies m ON m.id = r.movie_id
LEFT JOIN users u ON u.id = r.user_id LEFT JOIN users u ON u.id = r.user_id
@@ -522,12 +523,13 @@ impl DiaryRepository for PostgresDiaryRepository {
to_char(r.watched_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS watched_at, to_char(r.watched_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS watched_at,
to_char(r.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS created_at, to_char(r.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI:SS') AS created_at,
r.remote_actor_url, r.remote_actor_url,
CASE WHEN r.remote_actor_url IS NOT NULL THEN r.remote_actor_url r.watch_medium,
WHEN u.email IS NOT NULL THEN u.email CASE WHEN r.remote_actor_url IS NOT NULL THEN COALESCE(a.handle, r.remote_actor_url)
ELSE r.user_id END AS user_email ELSE COALESCE(u.email, r.user_id) END AS user_email
FROM reviews r FROM reviews r
INNER JOIN movies m ON m.id = r.movie_id INNER JOIN movies m ON m.id = r.movie_id
LEFT JOIN users u ON u.id = r.user_id LEFT JOIN users u ON u.id = r.user_id
LEFT JOIN ap_remote_actors a ON a.url = r.remote_actor_url
WHERE r.movie_id = $1 WHERE r.movie_id = $1
ORDER BY r.watched_at DESC ORDER BY r.watched_at DESC
LIMIT $2 OFFSET $3", LIMIT $2 OFFSET $3",

View File

@@ -309,7 +309,8 @@ impl DiaryRepository for SqliteDiaryRepository {
"SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path, "SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path,
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment,
r.watched_at, r.created_at, r.remote_actor_url, r.watch_medium, r.watched_at, r.created_at, r.remote_actor_url, r.watch_medium,
COALESCE(u.email, a.handle, r.remote_actor_url) AS user_email CASE WHEN r.remote_actor_url IS NOT NULL THEN COALESCE(a.handle, r.remote_actor_url)
ELSE COALESCE(u.email, r.user_id) END AS user_email
FROM reviews r FROM reviews r
INNER JOIN movies m ON m.id = r.movie_id INNER JOIN movies m ON m.id = r.movie_id
LEFT JOIN users u ON u.id = r.user_id LEFT JOIN users u ON u.id = r.user_id
@@ -476,12 +477,12 @@ impl DiaryRepository for SqliteDiaryRepository {
"SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path, "SELECT m.id, m.external_metadata_id, m.title, m.release_year, m.director, m.poster_path,
r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment, r.id AS review_id, r.movie_id, r.user_id, r.rating, r.comment,
r.watched_at, r.created_at, r.remote_actor_url, r.watch_medium, r.watched_at, r.created_at, r.remote_actor_url, r.watch_medium,
CASE WHEN r.remote_actor_url IS NOT NULL THEN r.remote_actor_url CASE WHEN r.remote_actor_url IS NOT NULL THEN COALESCE(a.handle, r.remote_actor_url)
WHEN u.email IS NOT NULL THEN u.email ELSE COALESCE(u.email, r.user_id) END AS user_email
ELSE r.user_id END AS user_email
FROM reviews r FROM reviews r
INNER JOIN movies m ON m.id = r.movie_id INNER JOIN movies m ON m.id = r.movie_id
LEFT JOIN users u ON u.id = r.user_id LEFT JOIN users u ON u.id = r.user_id
LEFT JOIN ap_remote_actors a ON a.url = r.remote_actor_url
WHERE r.movie_id = ? WHERE r.movie_id = ?
ORDER BY r.watched_at DESC ORDER BY r.watched_at DESC
LIMIT ? OFFSET ?", LIMIT ? OFFSET ?",

View File

@@ -42,6 +42,7 @@ pub struct DiaryQueryParams {
pub offset: Option<u32>, pub offset: Option<u32>,
pub sort_by: Option<String>, pub sort_by: Option<String>,
pub movie_id: Option<Uuid>, pub movie_id: Option<Uuid>,
pub user_id: Option<Uuid>,
} }
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)] #[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]

View File

@@ -265,7 +265,7 @@ pub fn to_diary_query(p: DiaryQueryParams) -> GetDiaryQuery {
_ => SortDirection::Descending, _ => SortDirection::Descending,
}), }),
movie_id: p.movie_id, movie_id: p.movie_id,
user_id: None, user_id: p.user_id,
} }
} }

View File

@@ -84,6 +84,7 @@ fn sort_by_asc_string_becomes_ascending() {
limit: None, limit: None,
offset: None, offset: None,
movie_id: None, movie_id: None,
user_id: None,
}; };
let query = to_diary_query(params); let query = to_diary_query(params);
assert!(matches!( assert!(matches!(
@@ -99,6 +100,7 @@ fn sort_by_other_string_becomes_descending() {
limit: None, limit: None,
offset: None, offset: None,
movie_id: None, movie_id: None,
user_id: None,
}; };
let query = to_diary_query(params); let query = to_diary_query(params);
assert!(matches!( assert!(matches!(

View File

@@ -141,10 +141,10 @@ function StatCell({ label, value }: { label: string; value: string | number }) {
) )
} }
function DiaryTab({ sortBy, search }: { sortBy: string; userId?: string; search?: string }) { function DiaryTab({ sortBy, userId, search }: { sortBy: string; userId?: string; search?: string }) {
const { t } = useTranslation() const { t } = useTranslation()
const { data, isPending, hasNextPage, isFetchingNextPage, fetchNextPage } = const { data, isPending, hasNextPage, isFetchingNextPage, fetchNextPage } =
useInfiniteDiary({ sort_by: sortBy, movie_id: undefined }) useInfiniteDiary({ sort_by: sortBy, user_id: userId })
const items = data?.pages.flatMap((p) => p.items) ?? [] const items = data?.pages.flatMap((p) => p.items) ?? []
const filtered = search const filtered = search
? items.filter((e) => ? items.filter((e) =>

View File

@@ -8,6 +8,7 @@ export const diaryQueryParamsSchema = z.object({
offset: z.number().optional(), offset: z.number().optional(),
sort_by: z.string().optional(), sort_by: z.string().optional(),
movie_id: z.string().uuid().optional(), movie_id: z.string().uuid().optional(),
user_id: z.string().uuid().optional(),
}) })
export type DiaryQueryParams = z.infer<typeof diaryQueryParamsSchema> export type DiaryQueryParams = z.infer<typeof diaryQueryParamsSchema>

View File

@@ -43,6 +43,7 @@ function ProfilePage() {
<ProfileView <ProfileView
data={data} data={data}
userId={auth.user_id}
search={search} search={search}
onSearchChange={setSearch} onSearchChange={setSearch}
actions={ actions={