-
Notifications
You must be signed in to change notification settings - Fork 0
API Reference
Felines uses Supabase as its backend. Most data access happens through standard Supabase queries with Row Level Security enforcing permissions automatically.
Sensitive operations use server-side RPCs (Remote Procedure Calls) that validate permissions before executing.
All requests use Supabase's built-in auth. The client is initialized with the public anon key — RLS policies determine what each user can access.
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)const { data } = await supabase
.from('colonies')
.select(`
id, name, narrative,
latitude_blurred, longitude_blurred,
castration_status, health_index,
cover_photo_url, created_at
`)Returns blurred coordinates only. Exact coordinates are never returned by public queries.
const { data } = await supabase
.from('colonies')
.select(`
*,
cats(*),
caretakers(user_id, profiles(display_name, avatar_url)),
timeline_events(*)
`)
.eq('id', colonyId)
.single()Articles are static content defined in lib/articles.ts.
No database query needed.
import { articles } from '@/lib/articles'
// Get all articles
const all = articles
// Get by slug
const article = articles.find(a => a.slug === slug)const { data } = await supabase
.from('community_contacts')
.select('*')
.order('category', { ascending: true })Require valid Supabase session. RLS automatically restricts access to appropriate data.
const { data } = await supabase
.from('colonies')
.insert({
name: 'Colony name',
narrative: 'Colony story',
latitude: -5.7945, // exact
longitude: -35.2110, // exact
latitude_blurred: -5.800, // ~600m offset
longitude_blurred: -35.216,
cover_photo_url: 'https://...'
})
.select()
.single()const { data } = await supabase
.from('feedings')
.insert({
colony_id: colonyId,
type: 'food', // or 'water'
user_id: userId
})const { data } = await supabase
.from('reports')
.insert({
colony_id: colonyId, // optional
type: 'sighting', // one of 9 types
description: '...',
latitude: -5.7945,
longitude: -35.2110,
is_anonymous: true
})Report types: sighting, no_food, no_water,
injured_cat, missing_cat, disease_suspicion,
poisoning_suspicion, abuse_suspicion, threat_to_colony
RPCs bypass RLS and run with elevated privileges. All RPCs validate permissions internally before executing.
Returns exact coordinates only if the authenticated user is a linked caretaker of the colony.
const { data } = await supabase.rpc(
'get_colony_exact_location',
{ p_colony_id: colonyId }
)
// Returns: { latitude, longitude } or permission deniedRequires: Authenticated + linked caretaker
Atomically confirms a report. Prevents self-confirmation and duplicate confirmation from the same user.
const { data } = await supabase.rpc(
'confirm_report',
{ p_report_id: reportId }
)
// Returns: true (confirmed) or errorRequires: Authenticated
Blocks: Self-confirmation, duplicates
Updates the last_seen timestamp for a cat.
const { data } = await supabase.rpc(
'mark_cat_seen',
{ p_cat_id: catId }
)Requires: Authenticated
Records a thank-you from one caretaker to another. Prevents self-thanking.
const { data } = await supabase.rpc(
'thank_action',
{
p_target_user_id: targetUserId,
p_colony_id: colonyId
}
)Requires: Authenticated
Blocks: Self-thanking
Updates caretaker streak. Idempotent — safe to call multiple times per day.
const { data } = await supabase.rpc(
'record_daily_visit',
{ p_colony_id: colonyId }
)Requires: Authenticated
Photos are stored in Supabase Storage in the
colony-photos bucket.
Upload (authenticated only):
import { buildSafeStoragePath } from '@/lib/storage'
const safePath = buildSafeStoragePath('colonies', file.name)
const { data } = await supabase.storage
.from('colony-photos')
.upload(safePath, file)buildSafeStoragePath sanitizes the prefix and uses
a UUID filename — no user input reaches the storage path.
Public read URL:
const { data } = supabase.storage
.from('colony-photos')
.getPublicUrl(path)Used for weather alerts on colony pages and the map. Coordinates are validated before use.
const url = new URL('https://api.openweathermap.org/data/2.5/weather')
url.searchParams.set('lat', String(lat))
url.searchParams.set('lon', String(lon))
url.searchParams.set('appid', process.env.NEXT_PUBLIC_WEATHER_API_KEY!)
url.searchParams.set('units', 'metric')
url.searchParams.set('lang', language === 'pt' ? 'pt_br' : 'en')Used for reverse geocoding (coordinates to city name). Coordinates are validated before the request is made.
import { reverseGeocode } from '@/lib/geocode'
import { validateCoordinates } from '@/lib/validateCoordinates'
const safe = validateCoordinates(lat, lon) // throws if invalid
const cityName = await reverseGeocode(safe.lat, safe.lon, 'en')