跳到正文
cccskills
免费在 GitHub 公开

backend-database-ops

Supabase Postgres + Storage + Auth operations from FastAPI — service-role client, RLS-aware queries, additive SQL migrations under backend/db/migrations, the create_order RPC, and the product-images bucket. Use when adding tables/columns, writing queries, calling RPCs, or working with Storage.

中文简介正在准备中。当前显示原文说明。

集成工具SupabasePostgreSQL
查看安装方法

包含的文件(1)

  • SKILL.md10.7 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Database Operations Skill

Version: 1.0 | Working Directory: backend/

Supabase operations for $ARGUMENTS in this FastAPI service.

Overview

One database, three surfaces:

  • Postgres — products, orders, order_items, plus the public.create_order PL/pgSQL RPC
  • Storage — product-images bucket (public read; writes via service-role)
  • Auth — admin JWT verification handled by app/auth.py (JWKS), bootstrap is manual via the Supabase dashboard

All access from the backend uses the service-role client, which bypasses RLS by design (the service is the single trusted writer). Anonymous browser access is governed by RLS policies defined in the migration.

Constitution (Non-Negotiable Rules)

#RuleWhy
1Use get_supabase() from app.supabase_client — never construct a client inlineOne configured service-role client
2Multi-row atomic writes go through a Postgres function (RPC) — checkout uses public.create_orderPython loops are not transactional; SQL is
3Schema changes ship as new files in backend/db/migrations/000N_<change>.sql — never edit a shipped migrationMigrations are append-only history
4Public reads filter is_active = true in the service when the call is for the storefrontInactive products must not leak
5RLS stays restrictive — products SELECT public, all writes service-role; orders / order_items no anon policiesBackend is the security boundary
6Storage writes use service-role; the bucket is public-read so URLs work in the storefront without signingDon't expose service-role to the browser
7Never log or return the service-role keySecrets stay in env / config
8No SQLAlchemy / Alembic / MongoDB / Redis in this project — Supabase Postgres is the databaseDon't add a second DB stack
9Don't widen RLS without an explicit migration + approvalRLS changes are security-critical
10For data backfills, write SQL (one-off file under backend/db/migrations/ or a scripts/ script that uses the service-role client)Reviewable, replayable

When to Use

  • Adding/altering a table or column
  • Writing a Supabase query in a service
  • Calling or extending the create_order RPC
  • Uploading to or reading from Storage
  • Backfilling data
  • Debugging a 401/403/RLS issue or an "atomic write" race

Quick Database Selection

There's only one — Postgres via Supabase. Use Storage for product images, Auth for admin sessions.

Quick Reference

from app.supabase_client import get_supabase

# SELECT with filter, search, pagination
sb = get_supabase()
resp = (
    sb.table("products")
      .select("*", count="exact")
      .eq("is_active", True)
      .ilike("name", f"%{q}%")
      .order("created_at", desc=True)
      .range(offset, offset + limit - 1)
      .execute()
)
items, total = resp.data or [], resp.count or 0

# INSERT / UPDATE / DELETE
sb.table("products").insert(data.model_dump()).execute()
sb.table("products").update(payload).eq("id", str(product_id)).execute()
sb.table("products").delete().eq("id", str(product_id)).execute()  # we don't expose this in the API — use is_active=false instead

# RPC (atomic checkout)
resp = sb.rpc("create_order", {
    "p_email": email,
    "p_shipping_name": shipping_name,
    "p_shipping_address": shipping_address,
    "p_items": items,
}).execute()

# Storage upload (service-role, public-read bucket)
sb.storage.from_("product-images").upload(
    path=key,
    file=file_bytes,
    file_options={"content-type": mime, "upsert": "true"},
)
public_url = sb.storage.from_("product-images").get_public_url(key)

Schema Snapshot (single source: backend/db/migrations/0001_init.sql)

TableNotable columnsNotes
productsid uuid pk, slug text unique, name, price_cents int, image_url, stock, is_active bool, created_at, updated_atRLS: SELECT public when is_active=true; writes service-role only
ordersid uuid pk, email, shipping_name, shipping_address jsonb, subtotal_cents, total_cents, status text check ('pending','paid','shipped','cancelled')RLS: no anon policies
order_itemsid, order_id fk, product_id fk, quantity int, unit_price_cents intSnapshot price at purchase; RLS: no anon policies

Function:

  • public.create_order(p_email, p_shipping_name, p_shipping_address, p_items) — locks affected products rows, validates stock, inserts the order + items, decrements stock, marks the order paid, returns {order_id, total_cents, status}.

Migrations

Migrations are plain SQL files in backend/db/migrations/, applied in order. There's no migration tool wired in for this MVP — files are run manually against Supabase via the SQL editor or psql.

Rules

RuleWhy
One concern per file — schema vs data, not mixedEasier to roll forward / back
Filename: 000N_<change>.sql continuing from 0001_init.sqlOrder is preserved
Additive on populated tables — add nullable column → backfill → enforce in a follow-up migrationAvoids downtime / breakage
Wrap in a transaction when changes span multiple statements that must succeed togetherAtomicity
Update RLS in the same migration that adds the tableDon't ship a table with no policies and forget
Never edit a shipped migrationReplays must be deterministic

Add a column (template)

-- backend/db/migrations/0002_add_product_brand.sql
begin;

alter table public.products
  add column if not exists brand text;

create index if not exists products_brand_idx on public.products(brand);

commit;

Modify the create_order RPC (template)

-- backend/db/migrations/000N_update_create_order.sql
begin;

create or replace function public.create_order(
  p_email text,
  p_shipping_name text,
  p_shipping_address jsonb,
  p_items jsonb
) returns jsonb
language plpgsql
security definer
as $$
declare
  -- ... (mirror the existing fn structure: lock rows, validate stock, insert, decrement, set status='paid')
begin
  -- body
end;
$$;

commit;

The RPC is security definer so it runs with the function owner's privileges — that's how anonymous checkout writes orders/items even though orders has no anon RLS policies. Don't change security semantics without explicit approval.

Backfill (data migration, template)

-- backend/db/migrations/000N_backfill_brand.sql
begin;

update public.products
   set brand = split_part(name, ' ', 1)
 where brand is null;

commit;

For large tables, batch in chunks; for now we have small data so a single statement is fine.

Run a migration locally

# preferred — Supabase SQL editor (paste the file, run)
# or, with the Postgres connection string from the Supabase dashboard:
psql "$SUPABASE_DB_URL" -f backend/db/migrations/0002_add_product_brand.sql

Storage

Bucket: product-images (public read).

# backend/app/services/storage.py — exemplar
def upload_product_image(file: UploadFile) -> str:
    sb = get_supabase()
    key = f"{uuid4()}-{file.filename}"
    sb.storage.from_("product-images").upload(
        path=key,
        file=file.file.read(),
        file_options={"content-type": file.content_type or "application/octet-stream", "upsert": "true"},
    )
    return sb.storage.from_("product-images").get_public_url(key)

Rules:

  • Generate a non-guessable key (UUID prefix) so users can't enumerate
  • Store the returned public URL on products.image_url
  • For deletion, also remove the storage object (when the admin replaces an image)

RLS Quick Reference

  • products — SELECT allowed for anon when is_active = true; INSERT/UPDATE/DELETE denied for anon (service-role bypasses)
  • orders, order_items — no anon policies; only service-role and the security definer create_order function can write

If a query unexpectedly returns 0 rows or 401/403 from the anon side, RLS is almost always the cause. From the backend (service-role) it should never matter — if it does, you're using the wrong client.

Common Bugs

  • Stock overdraft on checkout → service did multi-step Python writes instead of calling create_order. Fix: call the RPC.
  • Inactive product in storefront → service forgot .eq("is_active", True). Fix: add the filter; consider adding a service helper that always applies it for public reads.
  • Image upload 403 → service-role client not initialized (env vars missing) or bucket name typo. Fix: check SUPABASE_SERVICE_ROLE_KEY and SUPABASE_URL.
  • Migration applied twice → you edited a shipped file. Don't. Add a new file.
  • Anon write to orders works (somehow) → an anon RLS policy was added by mistake. Audit policies; remove via a new migration.

Common Tasks

Add a column to products

  1. Write backend/db/migrations/000N_<change>.sql (additive)
  2. Update app/schemas/product.py to include the field (optional on ProductCreate if backfilling)
  3. Update services that read/write the column
  4. Run the migration against your Supabase project

Add a new table

  1. New migration file: create table, indexes, RLS policies (default to anon-deny), comments
  2. Schema in app/schemas/
  3. Service in app/services/
  4. Router endpoint(s) in the right routers/*.py
  5. Register router if it's a new file

Change the checkout flow

  1. Write a migration that create or replace function public.create_order(...)
  2. Update app/services/orders.py if input/output shape changes
  3. Update app/schemas/order.py to mirror the new shape
  4. Test end-to-end: cart → checkout → row in orders, order_items, stock decremented

Backfill data

  1. New migration file with the update/insert
  2. For repeatable scripts that aren't migrations (e.g., importing seed data), use backend/scripts/ (see seed_shirts.py) and the service-role client

Exemplar Files

  • backend/db/migrations/0001_init.sql — schema + RLS + RPC
  • backend/db/seed.sql — seed data
  • backend/app/supabase_client.py — service-role client factory
  • backend/app/services/products.py — query + write patterns
  • backend/app/services/orders.py — RPC call
  • backend/app/services/storage.py — Storage upload
  • backend/scripts/seed_shirts.py — data import via service-role

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

Build FastAPI endpoints in the layered router → service → Supabase pattern, with Pydantic v2 schemas, JWT/admin gating, and consistent HTTPException-based domain errors. Use when adding/modifying any endpoint under backend/.

中文简介正在准备中。当前显示原文说明。

shanraisshan/learning-x-claude22026年5月7日 更新

pytest + httpx + TestClient patterns for the FastAPI backend. Dependency overrides for auth, FakeSupabase mocking, HTTPException assertions. Use when writing or reviewing backend/tests/test_*.py.

中文简介正在准备中。当前显示原文说明。

shanraisshan/learning-x-claude22026年5月7日 更新

Drive Chrome via the claude-in-chrome MCP to exercise plan.md::Verify steps for a shipped feature. Loads deferred MCP tool schemas, asserts console/network cleanliness, records a GIF for the PR. Used by the browser-verifier agent.

中文简介正在准备中。当前显示原文说明。

shanraisshan/learning-x-claude22026年5月7日 更新

Pre-PR code review checklist for this repo. Plan alignment, security gates, layering, dead code, API-surface completeness. Used by the code-reviewer agent.

中文简介正在准备中。当前显示原文说明。

shanraisshan/learning-x-claude22026年5月7日 更新

Integrate with the FastAPI backend through the typed wrapper in frontend/lib/api.ts, attach Supabase JWTs for admin calls, and handle errors via ApiError. Use when adding/modifying API calls or wiring auth.

中文简介正在准备中。当前显示原文说明。

shanraisshan/learning-x-claude22026年5月7日 更新

Build pages and components in the Next.js 15 App Router (RSC by default, "use client" only when needed), Tailwind for styling, typed props, and the existing CartProvider for cart state. Use when creating or modifying pages, layouts, components, or forms in frontend/.

中文简介正在准备中。当前显示原文说明。

shanraisshan/learning-x-claude22026年5月7日 更新

查看 shanraisshan 的全部技能

报告此技能的问题