Skip to main content
Glama
northernvariables

FedMCP - Federal Parliamentary Information

route.ts8.42 kB
/** * Bookmarks API Route * * GET /api/bookmarks - List user's bookmarks with filters * POST /api/bookmarks - Create or toggle bookmark */ import { NextRequest, NextResponse } from 'next/server'; import { auth } from '@/auth'; import { createAdminClient } from '@/lib/supabase-server'; import { canCreateBookmark, getBookmarkUsageStats, getTierLimits } from '@/lib/bookmarks/tierLimits'; export const dynamic = 'force-dynamic'; /** * GET /api/bookmarks * List user's bookmarks with optional filters */ export async function GET(request: NextRequest) { try { const session = await auth(); if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const supabase = createAdminClient(); const { searchParams } = new URL(request.url); // Parse query parameters const itemType = searchParams.get('type'); // Filter by item type const collectionId = searchParams.get('collectionId'); // Filter by collection const search = searchParams.get('search'); // Search in title/notes/tags const favoritesOnly = searchParams.get('favorites') === 'true'; const limit = parseInt(searchParams.get('limit') || '50', 10); const offset = parseInt(searchParams.get('offset') || '0', 10); // Build query let query = supabase .from('bookmarks') .select('*', { count: 'exact' }) .eq('user_id', session.user.id) .order('created_at', { ascending: false }); // Apply filters if (itemType) { query = query.eq('item_type', itemType); } if (collectionId) { query = query.eq('collection_id', collectionId); } if (favoritesOnly) { query = query.eq('is_favorite', true).order('favorite_order', { ascending: true }); } if (search) { // Search in title, notes, and tags query = query.or(`title.ilike.%${search}%,notes.ilike.%${search}%,tags.cs.{${search}}`); } // Pagination query = query.range(offset, offset + limit - 1); const { data: bookmarks, error, count } = await query; if (error) { console.error('Error fetching bookmarks:', error); return NextResponse.json({ error: 'Failed to fetch bookmarks' }, { status: 500 }); } // Get user's subscription tier and usage stats const { data: profile } = await supabase .from('user_profiles') .select('subscription_tier') .eq('id', session.user.id) .single(); const tier = profile?.subscription_tier || 'FREE'; const usageStats = getBookmarkUsageStats(count || 0, tier); const tierLimits = getTierLimits(tier); return NextResponse.json({ bookmarks: bookmarks || [], pagination: { total: count || 0, limit, offset, hasMore: (count || 0) > offset + limit, }, tier: { current: tier, limits: tierLimits, usage: usageStats, }, }); } catch (error) { console.error('Bookmarks API error:', error); return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } /** * POST /api/bookmarks * Create or toggle bookmark */ export async function POST(request: NextRequest) { try { const session = await auth(); if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const supabase = createAdminClient(); const body = await request.json(); const { itemType, itemId, title, subtitle, imageUrl, url, metadata = {}, collectionId = null, tags = [], notes = '', isFavorite = false, } = body; // Validate required fields if (!itemType || !itemId || !title || !url) { return NextResponse.json( { error: 'Missing required fields: itemType, itemId, title, url' }, { status: 400 } ); } // Check if bookmark already exists (toggle behavior) const { data: existing } = await supabase .from('bookmarks') .select('id') .eq('user_id', session.user.id) .eq('item_type', itemType) .eq('item_id', itemId) .single(); if (existing) { // Bookmark exists - delete it (toggle off) const { error: deleteError } = await supabase .from('bookmarks') .delete() .eq('id', existing.id); if (deleteError) { console.error('Error deleting bookmark:', deleteError); return NextResponse.json({ error: 'Failed to delete bookmark' }, { status: 500 }); } // Get updated count const { count } = await supabase .from('bookmarks') .select('*', { count: 'exact', head: true }) .eq('user_id', session.user.id); return NextResponse.json({ action: 'removed', bookmarkId: existing.id, count: count || 0, }); } // Bookmark doesn't exist - create it // First, check tier limits const { count: currentCount } = await supabase .from('bookmarks') .select('*', { count: 'exact', head: true }) .eq('user_id', session.user.id); const { data: profile } = await supabase .from('user_profiles') .select('subscription_tier') .eq('id', session.user.id) .single(); const tier = profile?.subscription_tier || 'FREE'; if (!canCreateBookmark(currentCount || 0, tier)) { const tierLimits = getTierLimits(tier); return NextResponse.json( { error: 'Bookmark limit reached', tier, currentCount, limit: tierLimits.maxBookmarks, upgradeRequired: true, }, { status: 403 } ); } // Check favorites limit if isFavorite if (isFavorite) { const { count: favoritesCount } = await supabase .from('bookmarks') .select('*', { count: 'exact', head: true }) .eq('user_id', session.user.id) .eq('is_favorite', true); const tierLimits = getTierLimits(tier); if ( tierLimits.maxFavorites !== null && (favoritesCount || 0) >= tierLimits.maxFavorites ) { return NextResponse.json( { error: 'Favorites limit reached', tier, currentCount: favoritesCount, limit: tierLimits.maxFavorites, }, { status: 403 } ); } } // Check collection permission if (collectionId) { const tierLimits = getTierLimits(tier); if (tierLimits.maxCollections === 0) { return NextResponse.json( { error: 'Collections not available on your tier', tier, upgradeRequired: true, }, { status: 403 } ); } } // Calculate favorite_order if isFavorite let favoriteOrder = null; if (isFavorite) { const { data: maxOrderBookmark } = await supabase .from('bookmarks') .select('favorite_order') .eq('user_id', session.user.id) .eq('is_favorite', true) .order('favorite_order', { ascending: false }) .limit(1) .single(); favoriteOrder = (maxOrderBookmark?.favorite_order || 0) + 1; } // Create bookmark const { data: bookmark, error: createError } = await supabase .from('bookmarks') .insert({ user_id: session.user.id, item_type: itemType, item_id: itemId, title, subtitle: subtitle || null, image_url: imageUrl || null, url, metadata, collection_id: collectionId, tags, notes, is_favorite: isFavorite, favorite_order: favoriteOrder, notifications_enabled: false, // Default to off }) .select() .single(); if (createError) { console.error('Error creating bookmark:', createError); return NextResponse.json({ error: 'Failed to create bookmark' }, { status: 500 }); } // Get updated usage stats const usageStats = getBookmarkUsageStats((currentCount || 0) + 1, tier); return NextResponse.json({ action: 'created', bookmark, tier: { current: tier, limits: getTierLimits(tier), usage: usageStats, }, }); } catch (error) { console.error('Bookmarks API error:', error); return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/northernvariables/FedMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server