import { serve } from "https://deno.land/std@0.177.0/http/server.ts"; import { createSupabaseClient, createServiceClient } from "../_shared/supabase-client.ts"; import { trySignR2Url } from "../_shared/r2_signer.ts"; const corsHeaders = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, OPTIONS", "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type", "Vary": "Origin", }; serve(async (req: Request) => { if (req.method === "OPTIONS") { return new Response(null, { headers: corsHeaders }); } try { const authHeader = req.headers.get("Authorization"); if (!authHeader) { console.error("Missing authorization header"); return new Response(JSON.stringify({ error: "Missing authorization header" }), { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } }); } const supabase = createSupabaseClient(authHeader); // Don't pass JWT explicitly - let the SDK validate using its internal session const { data: { user }, error: authError } = await supabase.auth.getUser(); if (authError || !user) { console.error("Auth error in feed-personal:", authError); console.error("User object:", user); return new Response(JSON.stringify({ error: "Unauthorized", details: authError?.message || "No user returned" }), { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } }); } const serviceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY"); if (!serviceKey) { console.error("Missing SUPABASE_SERVICE_ROLE_KEY in function environment"); return new Response(JSON.stringify({ error: "Server misconfigured: service key missing" }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }); } // Use service client for database queries to bypass RLS const serviceClient = createServiceClient(); const url = new URL(req.url); const limit = Math.min(parseInt(url.searchParams.get("limit") || "50"), 100); const offset = parseInt(url.searchParams.get("offset") || "0"); // Check if user has opted into beacon posts const { data: profile } = await serviceClient .from("profiles") .select("beacon_enabled") .eq("id", user.id) .single(); const beaconEnabled = profile?.beacon_enabled || false; // Get list of users this person follows (with accepted status) const { data: followingData } = await serviceClient .from("follows") .select("following_id") .eq("follower_id", user.id) .eq("status", "accepted"); const followingIds = (followingData || []).map((f: any) => f.following_id); // Include user's own posts in their feed + posts from people they follow const authorIds = [user.id, ...followingIds]; // Debug: First try a simple query to see if the basic setup works console.log("Debug: About to query posts for user:", user.id); console.log("Debug: Author IDs:", authorIds); console.log("Debug: Beacon enabled:", beaconEnabled); // Fetch posts from followed users and self let postsQuery = serviceClient .from("posts") .select(`id, type, body, created_at, visibility, author_id, author:profiles!posts_author_id_fkey (id, handle, display_name, avatar_url)`) .in("author_id", authorIds); // .eq("status", "active"); // Temporarily remove status filter to debug // Filter visibility: user can see their own posts (any visibility), // or public/followers posts from people they follow postsQuery = postsQuery.or(`author_id.eq.${user.id},visibility.in.(public,followers)`); // Only filter out beacons if user has NOT opted in if (!beaconEnabled) { postsQuery = postsQuery.eq("is_beacon", false); } const { data: posts, error: postsError } = await postsQuery .order("created_at", { ascending: false }) .range(offset, offset + limit - 1); if (postsError) { console.error("Error fetching posts:", postsError); return new Response(JSON.stringify({ error: "Failed to fetch feed" }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }); } // Get chain parent posts separately (self-referential relationship not set up) const postsWithChains = posts || []; const chainParentIds = postsWithChains .filter((p: any) => p.chain_parent_id) .map((p: any) => p.chain_parent_id); let chainParentMap = new Map(); if (chainParentIds.length > 0) { const { data: chainParents } = await serviceClient .from("posts") .select(`id, body, created_at, author:profiles!posts_author_id_fkey (id, handle, display_name, avatar_url)`) .in("id", [...new Set(chainParentIds)]); chainParents?.forEach((cp: any) => { chainParentMap.set(cp.id, { id: cp.id, body: cp.body, created_at: cp.created_at, author: cp.author, }); }); } const feedItems = postsWithChains.map((post: any) => ({ id: post.id, body: post.body, body_format: post.body_format, background_id: post.background_id, created_at: post.created_at, tone_label: post.tone_label, allow_chain: post.allow_chain, chain_parent_id: post.chain_parent_id, image_url: post.image_url, visibility: post.visibility, chain_parent: post.chain_parent_id ? chainParentMap.get(post.chain_parent_id) : null, author: post.author, category: post.category, metrics: post.metrics, user_liked: post.user_liked?.some((l: any) => l.user_id === user.id) || false, user_saved: post.user_saved?.some((s: any) => s.user_id === user.id) || false, })); const signedItems = await Promise.all( feedItems.map(async (post) => { if (!post.image_url) { return post; } return { ...post, image_url: await trySignR2Url(post.image_url) }; }) ); return new Response(JSON.stringify({ posts: signedItems, pagination: { limit, offset, returned: signedItems.length } }), { status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } }); } catch (error) { console.error("Unexpected error:", error); return new Response(JSON.stringify({ error: "Internal server error" }), { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }); } });