backend-dev
GitHub提供 Atomic CRM 后端开发规范,指导在 Supabase 环境下进行数据库迁移、视图、触发器、RLS 策略及 Edge Functions 的开发与决策。
Trigger Scenarios
Install
npx skills add marmelab/atomic-crm --skill backend-dev -g -y
SKILL.md
Frontmatter
{
"name": "backend-dev",
"description": "Coding practices for backend development in Atomic CRM. Use when deciding whether backend logic is needed, or when creating\/modifying database migrations, views, triggers, RLS policies, edge functions, or custom dataProvider methods that call Supabase APIs."
}
Backend Development
Reference for backend conventions. There is no custom backend server all server-side logic uses Supabase: PostgreSQL (tables, views, triggers, RLS), Auth API, Storage, and Edge Functions. Read the relevant section before writing backend code, then check the Red Flags and Verification list.
When to Use
- Deciding whether a change needs backend logic at all (default: prefer the frontend).
- Creating or modifying database views, triggers, or RLS policies.
- Writing or changing a Supabase edge function, or a custom dataProvider method that calls Supabase.
For the SQL migration mechanics themselves, see Skill({skill: "writing-migrations"}).
For the frontend side of a dataProvider method, see Skill({skill: "frontend-dev"}).
Deciding where logic belongs
Prefer frontend-only solutions via custom dataProvider methods calling the PostgREST API.
When backend logic is needed:
- Aggregation/read optimization: Create a database view (
CREATE OR REPLACE VIEWin a new migration). PostgREST exposes views like tables. When underlying table columns change, update thecontacts_summaryandcompanies_summaryviews too. - Complex mutations (multi-table writes): Create a Supabase edge function in Deno. Stored procedures via RPC are less preferred (code lives in migrations, harder to maintain). On the frontend, expose the edge function as a custom dataProvider method (using
httpClient(${supabaseUrl}/functions/v1/)) and call it via react-query. (e.g.salesCreate()→/functions/v1/users,mergeContacts()→/functions/v1/merge_contacts)
Edge function conventions
- Shared utils in
supabase/functions/_shared/— reuseauthentication.ts,supabaseAdmin.ts,cors.ts,utils.ts - Follow the middleware chain pattern: CORS preflight →
authenticate()→ handler verify_jwt = falsein config.toml, so JWT validation is manual viaauthenticate()
Other conventions
- New tables need RLS policies and the auto-set
sales_idtrigger (see migration20260108160722)
RLS enforcement pitfalls (security-critical)
- A trigger/function that counts rows to enforce a limit (capacity, quota, balance, subscription depletion) must be
SECURITY DEFINERwith a fixedsearch_path, or delegate to one. ASECURITY INVOKERcount runs under the caller's RLS, sees only the rows that caller may read, under-counts, and the limit silently never fires (e.g.session_booked_countis DEFINER for exactly this reason). - A
WITH CHECKclause must constrain every column a non-admin may set, not just ownership.with check (contact_id = current_user_contact())alone lets the caller forge any other column (status,type, amounts, flags) via PostgREST — privilege escalation / payment bypass. Whitelist allowed values in the policy, a trigger, or aCHECKconstraint; never trust a client-chosenstatus/type.
Red Flags
- Reaching for an edge function or RPC where a custom dataProvider method against PostgREST would do.
- Adding a column to a table that feeds
contacts_summary/companies_summarywithout updating the view. - A new table without RLS policies or the
sales_idtrigger. - A row-counting limit enforced by a
SECURITY INVOKERfunction it under-counts and never fires. - A
WITH CHECKthat constrains only ownership while leavingstatus/type/amounts client-settable. - An edge function handler that runs before
authenticate()in the middleware chain.
Verification
- The logic genuinely needs the backend no frontend-only path was available.
- New/changed views keep
contacts_summaryandcompanies_summaryin sync. - New tables have RLS policies and the
sales_idtrigger. - Any limit-enforcing count runs
SECURITY DEFINERwith a fixedsearch_path. - Every column a non-admin can set is constrained by policy, trigger, or
CHECK. - Edge functions follow CORS →
authenticate()→ handler.
Version History
- 167a4cd Current 2026-08-20 13:19


