internkim
The record

The record

What answers underneath the API — Postgres functions, edge functions, and scheduled work.

The API at https://api.intern.kim/v1 is the surface a caller is meant to use. Underneath it is Postgres, and a good deal of internkim is written there rather than in the web app: a tool like attendance_add resolves the caller, decides whether they may write, and either writes or turns the request into something an administrator was asked, all inside one function.

That has a cost. A rule written in SQL is invisible to the TypeScript above it, so the two drift unless the rule is stated once. A tool schema that demands a field the function does not refuses the call before the record sees it.

The lists of functions are generated from a database with every migration applied, so they say what is actually there.

Reading the tables

Callable by

The EXECUTE privilege, which is only the first gate. Row level security still applies to every table a function touches, and most functions gate themselves besides: member_leave_days_set raises unless public.is_company_admin() holds, whoever called it.

Security

An invoker function runs as the caller, so row level security answers for it. A definer function runs as its owner and can see past those policies. That is how a function returns one person's leave balance without opening the whole table, and it means the function's own body is all that stands between a caller and everything it can reach.

The schema of record is supabase/migrations, and What the tables mean explains the tables.

Running it locally

The local stack is the Supabase CLI's native runtime, with no Docker. tools/with-local-plane starts it, holds it for one command at a time and stops it after fifteen idle minutes.

tools/with-local-plane supabase db reset                      # schema and fixtures
tools/with-local-plane supabase test db                       # pgTAP
cd web && ../tools/with-local-plane bun run dev               # the web app against it

The reset alone gives a company to sign into, as [email protected] with seed-password. Fixtures live only in supabase/seed.dev.sql, so a reset wipes anything that file does not carry. Run supabase test db after every schema change: SQL function bodies resolve at call time, so a rename in the migration that created a function breaks it silently, and pgTAP is what notices. A migration that creates a table must also grant it, because anon, authenticated and service_role start with no privileges on a fresh database.

On this page