/** * POST /appreciate - Appreciate a post (boost it) * DELETE /appreciate - Remove appreciation * * Design intent: * - "Appreciate" instead of "like" - more intentional * - Quiet appreciation matters * - Boost-only, no downvotes */ import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'; import { createSupabaseClient, createServiceClient } from '../_shared/supabase-client.ts'; import { validateUUID, ValidationError } from '../_shared/validation.ts'; const ALLOWED_ORIGIN = Deno.env.get('ALLOWED_ORIGIN') || '*'; const CORS_HEADERS = { 'Access-Control-Allow-Origin': ALLOWED_ORIGIN, 'Access-Control-Allow-Methods': 'POST, DELETE', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', }; interface AppreciateRequest { post_id: string; } serve(async (req) => { if (req.method === 'OPTIONS') { return new Response(null, { headers: CORS_HEADERS }); } try { const authHeader = req.headers.get('Authorization'); console.log('Auth header present:', !!authHeader, 'Length:', authHeader?.length ?? 0); if (!authHeader) { return new Response(JSON.stringify({ error: 'Missing authorization header' }), { status: 401, headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, }); } const supabase = createSupabaseClient(authHeader); const adminClient = createServiceClient(); const { data: { user }, error: authError, } = await supabase.auth.getUser(); console.log('Auth result - user:', user?.id ?? 'null', 'error:', authError?.message ?? 'none'); if (authError || !user) { return new Response(JSON.stringify({ error: 'Unauthorized', message: authError?.message ?? 'No user found' }), { status: 401, headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, }); } const { post_id } = (await req.json()) as AppreciateRequest; validateUUID(post_id, 'post_id'); // Use admin client to check post existence - RLS was causing issues for some users // The post_likes insert will still enforce that only valid posts can be liked const { data: postRow, error: postError } = await adminClient .from('posts') .select('id, visibility, author_id, status') .eq('id', post_id) .maybeSingle(); if (postError || !postRow) { console.error('Post lookup failed:', { post_id, error: postError?.message }); return new Response( JSON.stringify({ error: 'Post not found' }), { status: 404, headers: { 'Content-Type': 'application/json' } } ); } // Check if post is active (published) // Note: posts use 'active' status for published posts if (postRow.status !== 'active') { return new Response( JSON.stringify({ error: 'Post is not available' }), { status: 404, headers: { 'Content-Type': 'application/json' } } ); } // For private posts, verify the user has access if (postRow.visibility === 'private' && postRow.author_id !== user.id) { return new Response( JSON.stringify({ error: 'Post not accessible' }), { status: 403, headers: { 'Content-Type': 'application/json' } } ); } // For followers-only posts, verify the user follows the author if (postRow.visibility === 'followers' && postRow.author_id !== user.id) { const { data: followRow } = await adminClient .from('follows') .select('status') .eq('follower_id', user.id) .eq('following_id', postRow.author_id) .eq('status', 'accepted') .maybeSingle(); if (!followRow) { return new Response( JSON.stringify({ error: 'You must follow this user to appreciate their posts' }), { status: 403, headers: { 'Content-Type': 'application/json' } } ); } } // Handle remove appreciation (DELETE) if (req.method === 'DELETE') { const { error: deleteError } = await adminClient .from('post_likes') .delete() .eq('user_id', user.id) .eq('post_id', post_id); if (deleteError) { console.error('Error removing appreciation:', deleteError); return new Response(JSON.stringify({ error: 'Failed to remove appreciation' }), { status: 500, headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, }); } return new Response(JSON.stringify({ success: true }), { status: 200, headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, }); } // Handle appreciate (POST) const { error: likeError } = await adminClient .from('post_likes') .insert({ user_id: user.id, post_id, }); if (likeError) { console.error('Like error details:', JSON.stringify({ code: likeError.code, message: likeError.message, details: likeError.details, hint: likeError.hint, user_id: user.id, post_id, })); // Already appreciated (duplicate key) if (likeError.code === '23505') { return new Response( JSON.stringify({ error: 'You have already appreciated this post' }), { status: 400, headers: { 'Content-Type': 'application/json' } } ); } // Post not visible (RLS blocked it) if (likeError.message?.includes('violates row-level security')) { return new Response( JSON.stringify({ error: 'Post not found or not accessible', code: likeError.code }), { status: 404, headers: { 'Content-Type': 'application/json' } } ); } console.error('Error appreciating post:', likeError); return new Response(JSON.stringify({ error: 'Failed to appreciate post', details: likeError.message }), { status: 500, headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, }); } return new Response( JSON.stringify({ success: true, message: 'Appreciation noted. Quiet signals matter.', }), { status: 200, headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, } ); } catch (error) { if (error instanceof ValidationError) { return new Response( JSON.stringify({ error: 'Validation error', message: error.message }), { status: 400, headers: { 'Content-Type': 'application/json' } } ); } console.error('Unexpected error:', error); return new Response(JSON.stringify({ error: 'Internal server error' }), { status: 500, headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, }); } });