Every entity in theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/astrxnomo/manizalesdepie/llms.txt
Use this file to discover all available pages before exploring further.
data/ directory follows the same four-file pattern. data/site/ is the reference implementation — when in doubt, copy its shape exactly. The order in which the files are created is not arbitrary: each file depends only on the ones before it, so starting in the wrong place means you are writing something you cannot yet test or authorize.
The four files
<module>.dto.ts — Zod schemas for input and output
The DTO file defines what goes in and what comes out. Two schemas minimum. The output schema is the one people skip and the one that matters most: it controls what the browser ever sees from a database row.Nothing in a DTO is spread from a database row. Every field is mapped explicitly in the DAL, so a column added to the database tomorrow cannot leak into the client by accident.The The coordinate bounds in
site entity defines several schemas. The two primary ones are siteSchema (output) and createSiteSchema (public form input):createSiteSchema are not cosmetic — they enforce the product’s geographic scope. A report from outside Manizales and Villamaría is rejected at the door with a message explaining why, not silently accepted into a queue nobody will work.The offset: true on datetime fields is required, not cosmetic. PostgREST serialises a timestamptz as "2026-08-14T02:12:27.271436+00:00" (offset notation), and Zod’s default ISO datetime parser only accepts a "Z" suffix. Without offset: true, every row fails validation the moment real data exists.<module>.policy.ts — Pure predicates
Policy files contain pure functions that return booleans. No database access, no session lookups, no side effects. A policy receives everything it needs as arguments.This constraint is enforced by ESLint: The policies for
*.policy.ts files may not import @/lib/supabase/*, next/headers, or next/navigation. The reason is testability and readability — a policy is the plain list of the product’s rules, readable without spinning up a database.canProposeSite, canConfirmSite, and canEditSite return true unconditionally. That is a deliberate product decision: during a fast emergency, requiring an account to report or confirm information makes the reviewer the bottleneck, and information arrives after it was needed.<module>.dal.ts — The database access layer
The DAL is the only file in the module that may touch the database. It opens with The
import "server-only" — importing a DAL from a client component is a build error.The class uses a private constructor and two static factories. This design means an instance cannot exist without a resolved authorization context, and the factory name makes the authorization level visible at the call site:toDTO method is the output validation step. It is private and called by every read method. A column added to the database tomorrow stays server-side until someone deliberately adds it to both toDTO and the output schema. Rows are never spread into a response.<module>.actions.ts — Server action orchestration
Action files open with Actions do not check roles or validate data themselves — that logic belongs in the policy and DAL. An action that checks
"use server". They orchestrate and nothing more: build the DAL, call it, revalidate. The DAL validates input, authorizes the caller, and validates output. The action trusts none of that to be done elsewhere.user.role === "curator" before calling the DAL is a second, unguarded copy of a rule that should live in one place.Mutation order
Every mutation in every DAL follows this sequence. There are no exceptions:- Validate input — parse with the Zod input schema
- Authorize — call the policy predicate
- Mutate — execute the database write
- Validate output — parse the result through the Zod output schema (via
toDTO)
PostGIS columns
PostgREST serialisesgeography columns as WKB hex strings, which are useless to a map renderer. The site_public and work_order_public views project the location and approx_location columns as plain longitude and latitude numbers using ST_X and ST_Y:
security_invoker = on. Without that flag, a view runs with the definer’s rights and quietly bypasses every RLS policy on the underlying tables. The DAL always reads through these views, never directly from the base tables.
Stale data policy
Nothing in this codebase is deleted for being stale. Every perishable table carriesconfirmed_at and expires_at columns. A row whose expires_at has passed is labelled stale and demoted in the map’s visual hierarchy — it stays listed, it stays contactable, but it stops competing with a freshly confirmed row for visual priority.
The confirmed_at timestamp is reset by confirmations: when someone standing in front of a site confirms its status, confirmed_at is set to now() and expires_at is pushed 24 hours forward. A map that hides unconfirmed information would hide the only warning anyone has during the first hours of an emergency. Labelling the uncertainty is the safer choice.