150 lines
6.3 KiB
TypeScript
150 lines
6.3 KiB
TypeScript
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<string, any>();
|
|
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" } });
|
|
}
|
|
});
|