fix(ui): resolve avatar URL from MinIO; fall back to OAuth provider URL
Some checks failed
CI / Backend (push) Successful in 50s
CI / UI (push) Successful in 34s
Release / Test backend (push) Successful in 37s
Release / Check ui (push) Successful in 45s
Release / Docker / caddy (push) Successful in 35s
CI / Backend (pull_request) Successful in 49s
CI / UI (pull_request) Successful in 40s
Release / Docker / backend (push) Failing after 52s
Release / Docker / runner (push) Failing after 58s
Release / Docker / ui (push) Failing after 55s
Release / Gitea Release (push) Has been skipped
Some checks failed
CI / Backend (push) Successful in 50s
CI / UI (push) Successful in 34s
Release / Test backend (push) Successful in 37s
Release / Check ui (push) Successful in 45s
Release / Docker / caddy (push) Successful in 35s
CI / Backend (pull_request) Successful in 49s
CI / UI (pull_request) Successful in 40s
Release / Docker / backend (push) Failing after 52s
Release / Docker / runner (push) Failing after 58s
Release / Docker / ui (push) Failing after 55s
Release / Gitea Release (push) Has been skipped
Add resolveAvatarUrl(userId, storedValue) helper that tries MinIO first, then falls back to the stored HTTP URL for OAuth users (Google/GitHub) who have never uploaded a custom avatar. Add getUserById() to pocketbase helpers for batch avatar resolution in comments. Update all 6 call sites to use the new helper.
This commit is contained in:
@@ -40,8 +40,8 @@ export async function presignAvatarUploadUrl(userId: string, mimeType: string):
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a presigned GET URL for a user's avatar, rewritten to the public URL.
|
||||
* Returns null if no avatar exists.
|
||||
* Returns a presigned GET URL for a user's avatar from MinIO.
|
||||
* Returns null if no avatar object exists in MinIO for this user.
|
||||
*/
|
||||
export async function presignAvatarUrl(userId: string): Promise<string | null> {
|
||||
const res = await backendFetch(`/api/presign/avatar/${encodeURIComponent(userId)}`);
|
||||
@@ -54,6 +54,42 @@ export async function presignAvatarUrl(userId: string): Promise<string | null> {
|
||||
return data.url ? rewriteHost(data.url) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the best available avatar URL for a user.
|
||||
*
|
||||
* Priority:
|
||||
* 1. MinIO — if the user has uploaded a custom avatar it will be found here
|
||||
* (presigned, short-lived GET URL).
|
||||
* 2. OAuth provider URL — stored in avatar_url when the account was created
|
||||
* via Google / GitHub OAuth (e.g. https://lh3.googleusercontent.com/...).
|
||||
* Returned as-is; the browser fetches it directly.
|
||||
*
|
||||
* Pass the raw `avatar_url` field from the PocketBase record as `storedValue`
|
||||
* so this function can distinguish between a MinIO key and a remote URL without
|
||||
* an extra DB round-trip.
|
||||
*
|
||||
* Returns null when neither source yields an avatar.
|
||||
*/
|
||||
export async function resolveAvatarUrl(
|
||||
userId: string,
|
||||
storedValue: string | null | undefined
|
||||
): Promise<string | null> {
|
||||
// 1. Try MinIO first (custom upload takes priority over OAuth picture).
|
||||
try {
|
||||
const minioUrl = await presignAvatarUrl(userId);
|
||||
if (minioUrl) return minioUrl;
|
||||
} catch {
|
||||
// MinIO unavailable — fall through to OAuth fallback.
|
||||
}
|
||||
|
||||
// 2. Fall back to OAuth-provided picture URL if it looks like a remote URL.
|
||||
if (storedValue && storedValue.startsWith('http')) {
|
||||
return storedValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites the MinIO host in a presigned URL to the public-facing URL.
|
||||
*
|
||||
|
||||
@@ -541,6 +541,19 @@ export async function getUserByUsername(username: string): Promise<User | null>
|
||||
return listOne<User>('app_users', `username="${username.replace(/"/g, '\\"')}"`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a user by their PocketBase record ID. Returns null if not found.
|
||||
*/
|
||||
export async function getUserById(id: string): Promise<User | null> {
|
||||
const token = await getToken();
|
||||
const res = await fetch(`${PB_URL}/api/collections/app_users/records/${encodeURIComponent(id)}`, {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
if (res.status === 404) return null;
|
||||
if (!res.ok) return null;
|
||||
return res.json() as Promise<User>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a user by email. Returns null if not found.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user