Skip to content

API Reference

Luara Oliveira edited this page Jul 3, 2026 · 3 revisions

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.


Authentication

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!
)

Public Endpoints (no auth required)

Get colonies (blurred locations)

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.


Get colony detail

const { data } = await supabase
  .from('colonies')
  .select(`
    *,
    cats(*),
    caretakers(user_id, profiles(display_name, avatar_url)),
    timeline_events(*)
  `)
  .eq('id', colonyId)
  .single()

Get articles

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)

Get community contacts

const { data } = await supabase
  .from('community_contacts')
  .select('*')
  .order('category', { ascending: true })

Authenticated Endpoints

Require valid Supabase session. RLS automatically restricts access to appropriate data.

Create colony

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()

Log feeding check-in

const { data } = await supabase
  .from('feedings')
  .insert({
    colony_id: colonyId,
    type: 'food', // or 'water'
    user_id: userId
  })

Create report

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 (Server-Side Functions)

RPCs bypass RLS and run with elevated privileges. All RPCs validate permissions internally before executing.

get_colony_exact_location

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 denied

Requires: Authenticated + linked caretaker


confirm_report

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 error

Requires: Authenticated
Blocks: Self-confirmation, duplicates


mark_cat_seen

Updates the last_seen timestamp for a cat.

const { data } = await supabase.rpc(
  'mark_cat_seen',
  { p_cat_id: catId }
)

Requires: Authenticated


thank_action

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


record_daily_visit

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


Storage

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)

External APIs

OpenWeatherMap

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')

Nominatim (OpenStreetMap)

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')

Clone this wiki locally