completionId: cgen-62bb5cdb686b41d7b91fdb77ebf7cd35

cgen-62bb5cdb686b41d7b91fdb77ebf7cd35
This commit is contained in:
Builder.io 2025-11-05 07:21:18 +00:00
parent 6b4cebb089
commit 19f334de8a

View file

@ -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")) {