first commit
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# Wallora Searcher Architecture Reference
|
||||
|
||||
## Surfaces And Roles
|
||||
|
||||
The project has three user-facing surfaces:
|
||||
|
||||
- Public API: available to external users. Use JWT login/authorization for protected API access. Keep contracts documented in `swagger.yaml`.
|
||||
- Admin: available only after account/password login. Use NextAuth-backed sessions to persist admin login state and gate admin features.
|
||||
- Public website: available to anyone on the public internet. Optimize pages for SEO, metadata, crawlability, and public performance.
|
||||
|
||||
Keep these surfaces separate in routing, middleware, authorization helpers, and mental model.
|
||||
|
||||
## Route Boundaries
|
||||
|
||||
Prefer clear route groups as the app grows:
|
||||
|
||||
- Public website: `app/(site)/...`
|
||||
- Admin UI: `app/(admin)/admin/...`
|
||||
- Protected admin UI: `app/(admin)/admin/(protected)/...`
|
||||
- Public API: `app/api/...`
|
||||
- Auth endpoints: `app/api/auth/...` for NextAuth
|
||||
|
||||
If the existing tree does not yet use route groups, introduce them only when it makes the current change clearer.
|
||||
|
||||
## Authentication And Authorization
|
||||
|
||||
- Public API authorization: validate JWTs explicitly for protected API routes. Define who issues the token before implementing token verification.
|
||||
- Current app-user JWTs are issued by `/api/v1/auth/login` and `/api/v1/auth/register`, signed with `APP_JWT_SECRET` or `AUTH_SECRET`, and validated by server-only helpers before accessing `/api/v1/users/me/...`.
|
||||
- Disabled app users should not be able to log in or use existing Bearer tokens because token validation reloads current `wg_app_users.status`.
|
||||
- Admin authorization: use NextAuth account/password login and session checks. Do not rely on public API JWT logic as the admin session source.
|
||||
- Only `/admin/login` is public under the admin surface. All other `/admin` pages need middleware/proxy protection and server-side `auth()` checks, preferably via a protected route-group layout.
|
||||
- Server code must keep secrets server-only. Never expose service-role Supabase keys, Aliyun OSS secrets, NextAuth secrets, or JWT signing secrets in `NEXT_PUBLIC_*`.
|
||||
- Keep authorization decisions close to the server boundary: route handlers, server actions, middleware, or server-only helpers.
|
||||
|
||||
## Data Storage
|
||||
|
||||
Use Supabase for application data.
|
||||
|
||||
- Prefix project-owned tables, indexes, triggers, and helper functions with `wg_` when practical.
|
||||
- Enable and design RLS for tables exposed through Supabase APIs.
|
||||
- Do not use user-editable metadata claims for authorization decisions.
|
||||
- Model admin roles and API consumer permissions in trusted tables or trusted auth metadata.
|
||||
- Verify Supabase behavior against current documentation before implementing schema, RLS, or auth-sensitive behavior.
|
||||
- Use service role credentials only in server-only code, one-shot scripts, cron workers, or protected internal route handlers.
|
||||
- Treat raw gallery rows as admin data. Public photo APIs should return picked rows only.
|
||||
- App-facing gallery list/detail endpoints may accept an optional Bearer app-user JWT to add user-specific fields such as `isFavorited`; anonymous responses must still work.
|
||||
- Keep picked state and OSS mirror metadata on `wg_gallery_photos`; use collections tables to group picked photos into public sets.
|
||||
- Admin collection writes should validate that every collection item references a picked photo.
|
||||
- Public collection APIs should filter on `is_published = true` and keep collection photo pagination compatible with infinite scroll.
|
||||
- App user favorites should accept picked photos only, and preferences/download preferences should be stored as JSON in `wg_user_settings`.
|
||||
- Keep phone and WeChat identity support as additive fields on `wg_app_users`; do not split them into separate user tables unless provider-specific complexity requires it later.
|
||||
|
||||
## Image Storage
|
||||
|
||||
Use Aliyun OSS for image files.
|
||||
|
||||
- Store files in OSS buckets; store metadata such as object key, URL, owner, MIME type, size, visibility, and related entity in Supabase.
|
||||
- Prefer server-side signed upload or server-mediated upload for private/admin flows.
|
||||
- Avoid placing OSS access keys in browser-exposed code.
|
||||
- Existing OSS env support accepts `OSS_REGION`, `OSS_ACCESS_KEY_ID`, `OSS_ACCESS_KEY_SECRET`, `OSS_BUCKET`, and either `OSS_PUBLIC_URL` or `OSS_PUBLIC_BASE_URL`.
|
||||
- Decide whether each image class is public, signed, or private before generating URLs.
|
||||
|
||||
## UI System
|
||||
|
||||
Use shadcn/ui as the base UI layer.
|
||||
|
||||
- Initialize and inspect shadcn project config before adding components.
|
||||
- Use the project package runner: `pnpm dlx shadcn@latest ...`.
|
||||
- Prefer existing shadcn components before writing custom controls.
|
||||
- Use semantic tokens and component variants instead of raw color overrides.
|
||||
- Compose forms, tables, dialogs, sheets, empty states, loading states, and feedback with shadcn primitives where available.
|
||||
|
||||
## SEO Website Pages
|
||||
|
||||
For public website pages:
|
||||
|
||||
- Use server-rendered pages by default.
|
||||
- Keep page-level metadata accurate with Next.js metadata APIs.
|
||||
- Use semantic HTML, crawlable content, canonical URLs where needed, and fast-loading assets.
|
||||
- Avoid hiding important public content behind client-only rendering.
|
||||
|
||||
## API Documentation
|
||||
|
||||
Maintain `swagger.yaml` for public API contracts.
|
||||
|
||||
- Update it whenever API endpoints, auth requirements, request schemas, response schemas, status codes, or error formats change.
|
||||
- Keep operation IDs stable and descriptive.
|
||||
- Document JWT requirements per route instead of assuming all routes share the same auth behavior.
|
||||
- Treat generated examples as contract examples, not placeholders.
|
||||
|
||||
## Scheduled Jobs
|
||||
|
||||
- Do not assume Vercel Cron. This project is not planned for Vercel.
|
||||
- Prefer a one-shot script that can be called by crontab, PM2, Docker, or another scheduler.
|
||||
- Keep scheduled routes protected with a secret when an HTTP trigger is useful.
|
||||
- Never rely on `setInterval` inside a Next.js request/serverless process for durable jobs.
|
||||
@@ -0,0 +1,109 @@
|
||||
# Wallora Searcher Project Reference
|
||||
|
||||
## Snapshot
|
||||
|
||||
- Root: `/Users/luoyangwei/Documents/workspace/wallora.top/searcher`
|
||||
- Package: `searcher`
|
||||
- Framework: Next.js `16.2.6` with App Router
|
||||
- React: `19.2.4`
|
||||
- Styling: Tailwind CSS v4 via `@import "tailwindcss";`
|
||||
- TypeScript: enabled
|
||||
- Package manager: `pnpm`
|
||||
- Git: this directory is not currently a Git repository
|
||||
- Intended UI system: shadcn/ui
|
||||
- Intended data store: Supabase with `wg_` table prefixes for project-owned tables
|
||||
- Intended image store: Aliyun OSS
|
||||
- Intended auth foundation: NextAuth plus JWT authorization for public APIs
|
||||
- Intended API documentation: `swagger.yaml`
|
||||
|
||||
## Important Files
|
||||
|
||||
- `app/layout.tsx`: root HTML shell, metadata, body layout classes, CSS-variable font tokens
|
||||
- `app/(site)/page.tsx`: public website home page
|
||||
- `app/(admin)/admin/login/page.tsx`: public admin login page
|
||||
- `app/(admin)/admin/(protected)/...`: admin pages that require a NextAuth session
|
||||
- `app/(admin)/admin/(protected)/gallery/page.tsx`: admin gallery picker with picked/color filters
|
||||
- `app/(admin)/admin/(protected)/collections/page.tsx`: admin collections CRUD surface
|
||||
- `components/admin/collections/CollectionsManager.tsx`: create/edit/delete published collections from picked photos
|
||||
- `app/api/admin/gallery-photos/route.ts`: protected admin gallery list and pick/unpick endpoint
|
||||
- `app/api/admin/photo-collections/route.ts`: protected admin collection list/create API
|
||||
- `app/api/admin/photo-collections/[id]/route.ts`: protected admin collection read/update/delete API
|
||||
- `app/api/v1/gallery/photos/route.ts`: public picked-photo API for waterfall pagination
|
||||
- `app/api/v1/gallery/photos/[id]/route.ts`: public picked-photo detail API plus favorite/unfavorite shortcut
|
||||
- `app/api/v1/gallery/collections/route.ts`: public published collection list API
|
||||
- `app/api/v1/gallery/collections/[slug]/route.ts`: public published collection detail API with paginated photos
|
||||
- `app/api/v1/auth/register/route.ts`: app user email/password registration
|
||||
- `app/api/v1/auth/login/route.ts`: app user email/password login and Bearer JWT issuance
|
||||
- `app/api/v1/users/me/route.ts`: current app user profile from Bearer JWT
|
||||
- `app/api/v1/users/me/favorites/...`: app user favorites list/add/remove APIs
|
||||
- `app/api/v1/users/me/settings/route.ts`: app user preferences and download preferences APIs
|
||||
- `app/(admin)/admin/(protected)/users/page.tsx`: protected admin app user list
|
||||
- `components/admin/users/UsersManager.tsx`: admin app user list with enable/disable actions
|
||||
- `app/api/admin/app-users/[id]/route.ts`: protected admin app user status update API
|
||||
- `app/api/cron/gallery-photos/route.ts`: protected gallery sync trigger endpoint
|
||||
- `app/globals.css`: Tailwind import, CSS variables for background/foreground, font theme tokens
|
||||
- `package.json`: scripts and dependencies
|
||||
- `next.config.ts`: Next.js config
|
||||
- `public/`: starter SVG assets from create-next-app
|
||||
- `swagger.yaml`: create or update when public API contracts are introduced or changed
|
||||
- `supabase/migrations/`: SQL migration scripts, including `wg_` gallery tables
|
||||
- `scripts/sync-gallery-photos.mjs`: one-shot gallery sync script for server cron/process managers
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
pnpm lint
|
||||
pnpm build
|
||||
pnpm start
|
||||
pnpm sync:gallery
|
||||
```
|
||||
|
||||
Use `pnpm format:check`, `pnpm lint`, and `pnpm build` for verification. Use `pnpm sync:gallery` only when intentionally touching the real Supabase database.
|
||||
|
||||
## Current Baseline Notes
|
||||
|
||||
- The public home page still contains create-next-app starter content unless updated later.
|
||||
- The admin dashboard uses shadcn `dashboard-01` under `/admin/dashboard`.
|
||||
- The admin login page uses shadcn `login-03` and NextAuth credentials under `/admin/login`.
|
||||
- The admin gallery supports picked/unpicked filters, color-family filters, pick/unpick actions, and OSS mirror status.
|
||||
- The admin collections page supports create, edit, delete, publish/draft status, cover selection, and selecting photos from picked photos.
|
||||
- Public photo consumption should use `/api/v1/gallery/photos`, which returns picked photos only.
|
||||
- App photo detail should use `/api/v1/gallery/photos/{id}`. Public callers can omit auth; authenticated callers receive `isFavorited` and can `POST`/`DELETE` the same route to favorite/unfavorite.
|
||||
- Public collection consumption should use `/api/v1/gallery/collections` and `/api/v1/gallery/collections/{slug}`; collection photos are picked photos only.
|
||||
- App users register and login with email/password via `/api/v1/auth/register` and `/api/v1/auth/login`; the returned Bearer JWT is for public app APIs only.
|
||||
- App user favorites and settings live under `/api/v1/users/me/...`.
|
||||
- Admins can review app users and enable/disable accounts under `/admin/users`.
|
||||
- Bare `/dashboard` and `/login` routes should stay absent; admin templates belong under `/admin`.
|
||||
|
||||
## Coding Conventions
|
||||
|
||||
- Keep components typed with TypeScript and follow existing double-quote import style.
|
||||
- Prefer server components unless client behavior is necessary.
|
||||
- Put global tokens and truly global styles in `app/globals.css`; keep page-specific styling near components with Tailwind classes.
|
||||
- Keep font tokens in CSS variables; avoid `next/font/google` unless the deployment environment can reliably fetch Google Fonts during build.
|
||||
- Avoid broad dependency additions for small UI tasks; use the platform and existing stack first.
|
||||
- Use `pnpm dlx shadcn@latest ...` for shadcn CLI tasks.
|
||||
- Verify current Supabase, NextAuth, and Aliyun OSS APIs before implementing auth-sensitive or storage-sensitive behavior.
|
||||
- For database objects created by this app, use the `wg_` prefix, e.g. `wg_gallery_photos`.
|
||||
- Do not read or print secrets from `.env.local`; report only whether required variables are present.
|
||||
- This project is not targeting Vercel. Prefer server cron, process manager jobs, or external scheduler calls over `vercel.json` cron config.
|
||||
|
||||
## Design Conventions
|
||||
|
||||
- Replace starter content with domain-specific product UI when asked to build the app experience.
|
||||
- Keep cards at `8px` radius or less unless a future design system says otherwise.
|
||||
- Do not nest cards inside cards.
|
||||
- Avoid one-note palettes and generic purple/blue gradient-heavy layouts.
|
||||
- Make text fit inside controls and panels at mobile and desktop widths.
|
||||
|
||||
## Architecture Summary
|
||||
|
||||
- Public API: available to external users; use JWT login/authorization and document routes in `swagger.yaml`.
|
||||
- Admin: account/password login; use NextAuth sessions to persist login state and gate admin features.
|
||||
- Public website: open internet access; optimize for SEO, metadata, crawlability, and performance.
|
||||
- Supabase stores application data with `wg_` table prefixes; Aliyun OSS stores image files while Supabase stores image metadata.
|
||||
- Gallery photo ingestion writes to `wg_gallery_photos` and tracks progress in `wg_gallery_sync_state`.
|
||||
- Picked photo metadata lives on `wg_gallery_photos` via `is_picked`, `picked_at`, `picked_by`, and `oss_*` fields.
|
||||
- Collections live in `wg_photo_collections` and `wg_photo_collection_items`.
|
||||
- App users/favorites/settings live in `wg_app_users`, `wg_user_favorites`, and `wg_user_settings`; identity fields include email now and schema placeholders for phone/WeChat later.
|
||||
@@ -0,0 +1,132 @@
|
||||
# Supabase Gallery Sync Reference
|
||||
|
||||
## Naming
|
||||
|
||||
Use `wg_` for project-owned Supabase objects.
|
||||
|
||||
- Gallery table: `wg_gallery_photos`
|
||||
- Sync state table: `wg_gallery_sync_state`
|
||||
- Collections tables: `wg_photo_collections`, `wg_photo_collection_items`
|
||||
- User tables: `wg_app_users`, `wg_user_favorites`, `wg_user_settings`
|
||||
- Example trigger/function names: `set_wg_gallery_photos_updated_at`, `set_wg_updated_at`
|
||||
|
||||
Do not introduce unprefixed replacements such as `gallery_photos`.
|
||||
|
||||
## Files
|
||||
|
||||
- `supabase/migrations/20260530000000_create_wg_gallery_tables.sql`: table/RLS/index/trigger migration.
|
||||
- `lib/supabase/service-role.ts`: server-only Supabase service-role client.
|
||||
- `supabase/migrations/20260601000000_extend_gallery_pick_collections_users.sql`: picked-photo, OSS metadata, collections, and user/favorite/settings schema extension.
|
||||
- `server/api/unsplash.ts`: official Unsplash API fetch and response validation.
|
||||
- `server/admin/gallery-sync.ts`: reusable sync implementation for Next route handlers.
|
||||
- `server/admin/gallery-pick.ts`: admin pick/unpick implementation and OSS mirror call.
|
||||
- `server/storage/aliyun-oss.ts`: server-only Aliyun OSS PUT Object helper.
|
||||
- `app/api/admin/gallery-photos/route.ts`: protected admin list + pick/unpick API.
|
||||
- `app/api/admin/photo-collections/route.ts`: protected admin collection list/create API.
|
||||
- `app/api/admin/photo-collections/[id]/route.ts`: protected admin collection read/update/delete API.
|
||||
- `app/api/v1/gallery/photos/route.ts`: public picked photos API.
|
||||
- `app/api/v1/gallery/collections/route.ts`: public published collections API.
|
||||
- `app/api/v1/gallery/collections/[slug]/route.ts`: public published collection detail API.
|
||||
- `app/api/cron/gallery-photos/route.ts`: protected HTTP trigger, guarded by `CRON_SECRET`.
|
||||
- `scripts/sync-gallery-photos.mjs`: one-shot Node script for server cron/process managers.
|
||||
|
||||
## Environment
|
||||
|
||||
Required for sync:
|
||||
|
||||
- `NEXT_PUBLIC_SUPABASE_URL`
|
||||
- `SUPABASE_SERVICE_ROLE_KEY`
|
||||
- `UNSPLASH_ACCESS_KEY`
|
||||
|
||||
Required for OSS mirroring when an admin marks a photo as picked:
|
||||
|
||||
- `OSS_REGION`
|
||||
- `OSS_ACCESS_KEY_ID`
|
||||
- `OSS_ACCESS_KEY_SECRET`
|
||||
- `OSS_BUCKET`
|
||||
- `OSS_PUBLIC_URL` or `OSS_PUBLIC_BASE_URL`
|
||||
|
||||
Optional for OSS:
|
||||
|
||||
- `OSS_ENDPOINT` to override the region-derived endpoint
|
||||
|
||||
Required for HTTP trigger:
|
||||
|
||||
- `CRON_SECRET`
|
||||
|
||||
Never print secret values. It is fine to report whether each variable is set.
|
||||
|
||||
## Scheduler
|
||||
|
||||
This project is not intended for Vercel deployment. Do not add `vercel.json` cron config unless the deployment target changes.
|
||||
|
||||
Preferred server cron:
|
||||
|
||||
```bash
|
||||
*/10 * * * * cd /Users/luoyangwei/Documents/workspace/wallora.top/searcher && pnpm sync:gallery
|
||||
```
|
||||
|
||||
The script runs once and exits. That makes it suitable for crontab, PM2 cron, Docker scheduled jobs, or an external scheduler.
|
||||
|
||||
## Sync Behavior
|
||||
|
||||
Source endpoint:
|
||||
|
||||
```txt
|
||||
https://api.unsplash.com/topics/wallpapers/photos?page=<page>&per_page=20
|
||||
```
|
||||
|
||||
Use the official Unsplash API with `Authorization: Client-ID <UNSPLASH_ACCESS_KEY>`. Do not rely on `unsplash.com/napi/...`; it is a website-internal endpoint and can return `401`, anti-bot HTML, or other non-contract responses.
|
||||
|
||||
Workflow:
|
||||
|
||||
1. Read `wg_gallery_sync_state` for source `unsplash-wallpapers`.
|
||||
2. Fetch the current page from the official Unsplash API with `per_page = 20`.
|
||||
3. Require JSON and validate that the response is an array of photo-like objects.
|
||||
4. Map every item into `wg_gallery_photos`.
|
||||
5. Populate `color_family` from the Unsplash hex color so admin color filters work.
|
||||
6. Upsert with `onConflict: "unsplash_id"` and `ignoreDuplicates: true`.
|
||||
7. Update `wg_gallery_sync_state` with next page, seen count, inserted count, timestamp, and last error.
|
||||
|
||||
If Unsplash returns an error, anti-bot HTML, or any non-JSON response, record the error in `wg_gallery_sync_state.last_error` and do not write photo rows.
|
||||
|
||||
## Picked Photos
|
||||
|
||||
- Raw gallery rows are for admin curation.
|
||||
- Public consumers should query picked rows only through `/api/v1/gallery/photos`.
|
||||
- RLS should expose `wg_gallery_photos` to `anon`/`authenticated` only when `is_picked = true`.
|
||||
- Admin pick/unpick operations run through server-only code using the Supabase service-role key.
|
||||
- Picking a photo attempts to mirror the selected Unsplash image into Aliyun OSS and stores `oss_bucket`, `oss_object_key`, `oss_url`, `oss_synced_at`, and `oss_sync_error`.
|
||||
- If OSS is missing or upload fails, the photo can still be marked picked, but `oss_sync_error` should be visible to admin users.
|
||||
|
||||
## Collections
|
||||
|
||||
- Collections are admin-managed under `/admin/collections`.
|
||||
- Admin APIs are session-protected and run with the Supabase service-role client.
|
||||
- Collection items must reference picked photos only.
|
||||
- Published collections are exposed publicly; draft collections are not returned by public APIs.
|
||||
- Public collection detail uses `page` and `pageSize` for waterfall-friendly pagination of collection photos.
|
||||
- Public gallery photo detail lives at `/api/v1/gallery/photos/[id]`; it is picked-only and supports authenticated favorite/unfavorite shortcuts.
|
||||
|
||||
## App Users
|
||||
|
||||
- App users are separate from admin users and live in `wg_app_users`.
|
||||
- Email/password auth uses server-side password hashing and Bearer JWT issuance under `/api/v1/auth/*`.
|
||||
- Protected app-user routes live under `/api/v1/users/me/*` and must validate Bearer JWTs server-side.
|
||||
- Admin app-user review and status control live under `/admin/users` and `/api/admin/app-users/[id]`.
|
||||
- Favorites live in `wg_user_favorites` and should only reference picked photos.
|
||||
- Preferences and download preferences live in `wg_user_settings` as JSON objects.
|
||||
- `wg_app_users` has additive identity columns for future phone and WeChat login support.
|
||||
|
||||
## Verification
|
||||
|
||||
Use these checks after touching sync code:
|
||||
|
||||
```bash
|
||||
node --check scripts/sync-gallery-photos.mjs
|
||||
pnpm format:check
|
||||
pnpm lint
|
||||
pnpm build
|
||||
```
|
||||
|
||||
Only run `pnpm sync:gallery` when the user explicitly wants to write to the configured Supabase database.
|
||||
Reference in New Issue
Block a user