completionId: cgen-62bb5cdb686b41d7b91fdb77ebf7cd35
cgen-62bb5cdb686b41d7b91fdb77ebf7cd35
This commit is contained in:
parent
6b4cebb089
commit
19f334de8a
1 changed files with 72 additions and 31 deletions
|
|
@ -231,15 +231,32 @@ export const achievementService = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function for timeouts
|
||||||
|
function withTimeout<T>(
|
||||||
|
promise: Promise<T>,
|
||||||
|
ms: number,
|
||||||
|
label: string,
|
||||||
|
): Promise<T> {
|
||||||
|
return Promise.race([
|
||||||
|
promise,
|
||||||
|
new Promise<T>((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error(`${label} timeout after ${ms}ms`)), ms),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
// Community Services
|
// Community Services
|
||||||
export const communityService = {
|
export const communityService = {
|
||||||
async getPosts(limit = 10): Promise<CommunityPost[]> {
|
async getPosts(limit = 10): Promise<CommunityPost[]> {
|
||||||
// 1) Try relational select with embedded author profile
|
const DEFAULT_TIMEOUT = 8000; // 8 seconds per attempt
|
||||||
|
|
||||||
|
// 1) Try relational select with embedded author profile (with timeout)
|
||||||
try {
|
try {
|
||||||
const { data, error } = await supabase
|
const { data, error } = await withTimeout(
|
||||||
.from("community_posts")
|
supabase
|
||||||
.select(
|
.from("community_posts")
|
||||||
`
|
.select(
|
||||||
|
`
|
||||||
*,
|
*,
|
||||||
user_profiles (
|
user_profiles (
|
||||||
username,
|
username,
|
||||||
|
|
@ -247,10 +264,13 @@ export const communityService = {
|
||||||
avatar_url
|
avatar_url
|
||||||
)
|
)
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
.eq("is_published", true)
|
.eq("is_published", true)
|
||||||
.order("created_at", { ascending: false })
|
.order("created_at", { ascending: false })
|
||||||
.limit(limit);
|
.limit(limit),
|
||||||
|
DEFAULT_TIMEOUT,
|
||||||
|
"Relational select",
|
||||||
|
);
|
||||||
|
|
||||||
if (!error) {
|
if (!error) {
|
||||||
return (Array.isArray(data) ? data : []) as CommunityPost[];
|
return (Array.isArray(data) ? data : []) as CommunityPost[];
|
||||||
|
|
@ -266,34 +286,49 @@ export const communityService = {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) Fallback to simple posts select, then hydrate author profiles manually
|
// 2) Fallback to simple posts select, then hydrate author profiles manually (with timeout)
|
||||||
try {
|
try {
|
||||||
const { data: posts, error: postsErr } = await supabase
|
const { data: posts, error: postsErr } = await withTimeout(
|
||||||
.from("community_posts")
|
supabase
|
||||||
.select("*")
|
.from("community_posts")
|
||||||
.eq("is_published", true)
|
.select("*")
|
||||||
.order("created_at", { ascending: false })
|
.eq("is_published", true)
|
||||||
.limit(limit);
|
.order("created_at", { ascending: false })
|
||||||
|
.limit(limit),
|
||||||
|
DEFAULT_TIMEOUT,
|
||||||
|
"Simple posts select",
|
||||||
|
);
|
||||||
if (!postsErr && Array.isArray(posts) && posts.length) {
|
if (!postsErr && Array.isArray(posts) && posts.length) {
|
||||||
const authorIds = Array.from(
|
const authorIds = Array.from(
|
||||||
new Set(posts.map((p: any) => p.author_id).filter(Boolean)),
|
new Set(posts.map((p: any) => p.author_id).filter(Boolean)),
|
||||||
);
|
);
|
||||||
let profilesById: Record<string, any> = {};
|
let profilesById: Record<string, any> = {};
|
||||||
if (authorIds.length) {
|
if (authorIds.length) {
|
||||||
const { data: profiles, error: profErr } = await supabase
|
try {
|
||||||
.from("user_profiles")
|
const { data: profiles, error: profErr } = await withTimeout(
|
||||||
.select("id, username, full_name, avatar_url")
|
supabase
|
||||||
.in("id", authorIds);
|
.from("user_profiles")
|
||||||
if (!profErr && Array.isArray(profiles)) {
|
.select("id, username, full_name, avatar_url")
|
||||||
profilesById = Object.fromEntries(
|
.in("id", authorIds),
|
||||||
profiles.map((u: any) => [
|
DEFAULT_TIMEOUT,
|
||||||
u.id,
|
"Profile hydration",
|
||||||
{
|
);
|
||||||
username: u.username,
|
if (!profErr && Array.isArray(profiles)) {
|
||||||
full_name: u.full_name,
|
profilesById = Object.fromEntries(
|
||||||
avatar_url: u.avatar_url,
|
profiles.map((u: any) => [
|
||||||
},
|
u.id,
|
||||||
]),
|
{
|
||||||
|
username: u.username,
|
||||||
|
full_name: u.full_name,
|
||||||
|
avatar_url: u.avatar_url,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (profError) {
|
||||||
|
console.warn(
|
||||||
|
"Profile hydration timeout/error:",
|
||||||
|
(profError as any)?.message || profError,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -314,11 +349,17 @@ export const communityService = {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Final fallback to API if available
|
// 3) Final fallback to API if available (with timeout)
|
||||||
try {
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
|
||||||
|
|
||||||
const resp = await fetch(
|
const resp = await fetch(
|
||||||
`/api/posts?limit=${encodeURIComponent(String(limit))}`,
|
`/api/posts?limit=${encodeURIComponent(String(limit))}`,
|
||||||
|
{ signal: controller.signal },
|
||||||
);
|
);
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const ct = resp.headers.get("content-type") || "";
|
const ct = resp.headers.get("content-type") || "";
|
||||||
if (ct.includes("application/json") || ct.includes("json")) {
|
if (ct.includes("application/json") || ct.includes("json")) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue