尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗Alberta-Cars is an Alberta-only marketplace for private used-vehicle sales. The production application is a single Next.js 15 + Supabase web app deployed on Vercel. There is no second static production/ frontend and no Telegram runtime in this repository.
TODO: the official public domain and support mailbox are not yet verified. Keep the current deployment URL configuration until an Alberta-Cars domain is configured and tested. Do not publish an unverified support address or hardcode a new canonical host before then.
pending until an administrator approves them.listings.user_id from anon are committed and still waiting to be applied. See ops/READINESS.md./_next/static/* assets with immutable caching.script-src 'unsafe-inline'.npm ci..env.example to .env.local and configure the Supabase URL and publishable key.npm run dev.Useful checks:
npm run typecheck
npm run test:unit
npm run build
npm run test:security-live
npm run test:e2e
npm run test:security-live talks to the live Supabase project. Two tests stay red until the pending migrations in ops/READINESS.md are applied: anonymous listings.user_id reads, and anonymous listing-photo list or /object/authenticated/ downloads. The other security-live tests should pass.
GitHub Actions workflow Test Alberta-Cars Web uses Node 24 and npm ci, typechecks, runs npm run test:unit, builds the Next.js app, runs the public security boundary tests, runs tests/database-security.sql when the SUPABASE_DB_URL repository secret is set, and runs Playwright plus a read-only load smoke. On main it also checks that the production catalog can be served from the Vercel CDN cache.
The CI source-tree guard fails if a legacy production/ directory, a browser Supabase session client, or Telegram runtime references are reintroduced into app, components, lib, or middleware.ts.
Update an existing database only from supabase/migrations/, in filename order. Apply each new file yourself with:
psql "$SUPABASE_DB_URL" -v ON_ERROR_STOP=1 --single-transaction -f path/to/migration.sql
supabase/schema.sql is an outdated sketch for a brand-new empty project. Do not apply it to the live database. It does not match production tables, policies, or grants.
Applied on the live project on 2026-10-23, in this order, and recorded in supabase_migrations.schema_migrations. Do not apply them again.
supabase/migrations/20261023210000_signed_url_only_listing_photos.sql — installed without storage.allow_* helpers; the operation check is inlinedsupabase/migrations/20261023211000_hide_listing_owner_from_anon.sqlsupabase/migrations/20261023212000_pending_only_listing_photo_uploads.sqlsupabase/migrations/20261023213000_protect_profile_trust_columns.sqlDetails and rollback are in those files and in ops/READINESS.md. Do not apply schema.sql.
The /admin route is owner/admin-only. Every admin mutation rechecks membership in the admins table on the server. Admin tools include listing approval/removal, reports, user moderation, aggregate analytics and recent activity.
Never place a Supabase service-role key, Sentry auth token, database URL, or other server credential in browser code or a NEXT_PUBLIC_* variable, except values explicitly designed to be public such as the Supabase publishable key and Sentry DSN.
Sign-in, signup, and password recovery can show a Cloudflare Turnstile widget and send its token to Supabase Auth as captchaToken. Supabase checks that token only after CAPTCHA is enabled in the Supabase dashboard. Password updates from the reset page are rate limited, and they do not show a widget. Sign-out is not limited.
The widget is rendered only when NEXT_PUBLIC_TURNSTILE_SITE_KEY is set. The server reads that value per request, so an empty value is not baked into the client bundle. Rate limiting uses @upstash/ratelimit and @upstash/redis. It runs only when both UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN are set. Otherwise the server logs one warning and allows the attempt. A Redis error also allows the attempt, so an Upstash outage does not take down login.
Limits, per action:
| Action | Per client IP | Per normalized email |
|---|---|---|
| Sign in | 20 / 15 minutes | 10 / 15 minutes |
| Signup | 5 / hour | 3 / hour |
| Password recovery | 5 / hour | 3 / hour |
| Password update | 10 / hour | — |
The client IP comes from Vercel's x-real-ip, then the first address in x-vercel-forwarded-for. x-forwarded-for is ignored. If neither trusted header is present, requests share an unknown bucket. Email is trimmed and lowercased. A value without @ counts against the IP bucket only. A limited request returns 429 with Retry-After and Too many attempts. Please try again later. The response does not say which bucket fired.
Rollout order, so login never breaks:
NEXT_PUBLIC_TURNSTILE_SITE_KEY, UPSTASH_REDIS_REST_URL, and UPSTASH_REDIS_REST_TOKEN in Vercel, then redeploy.CSP already allows https://challenges.cloudflare.com on script-src, frame-src, and connect-src. The widget script uses the per-request nonce.
Vercel builds the repository as Next.js using Node 24 and npm ci. The live site uses the URL configured through NEXT_PUBLIC_SITE_URL. It uses the existing Supabase project. Do not point production at a different project until the pending migrations and a restore drill are done.