162 lines
10 KiB
Markdown
162 lines
10 KiB
Markdown
# AGENTS.md
|
|
|
|
## Commands
|
|
|
|
- `npm run dev` — Vite dev server with HMR
|
|
- `npm run build` — `vue-tsc -b && vite build` (typecheck must pass or build aborts)
|
|
- `npx vue-tsc -b` — standalone typecheck (no script in package.json)
|
|
- No linter, formatter, or test runner exists
|
|
|
|
## Architecture
|
|
|
|
- **Vue 3 + Vite + Tailwind CSS + Vue Router + Pinia + FastAPI + AMap (高德地图) + Naive UI**
|
|
- Path alias `@/` → `src/` (configured in both `vite.config.ts` and `tsconfig.app.json`)
|
|
- FastAPI client at `src/lib/api.ts`; API base URL comes from `VITE_API_URL`
|
|
- AMap loaded lazily via `src/lib/amap.ts` with type declarations in `src/types/amap.d.ts`
|
|
- UI language is Chinese (zh-CN)
|
|
- All persistence, storage, authorization, and admin operations go through the FastAPI backend
|
|
|
|
## Directory Map
|
|
|
|
| Path | Purpose |
|
|
| --- | --- |
|
|
| `src/lib/` | Singletons and utilities: FastAPI client, amap, canvas patch, cloudTypes constants, cloudBadges canvas renderer, SEO meta builder |
|
|
| `src/stores/` | Pinia stores: auth, clouds (cloud_types cache), encyclopedia (collection + unlock tracking), profile (user pages + cloud CRUD) |
|
|
| `src/composables/` | Vue composables: `useUpload` (batch multipart upload, EXIF extraction, badge result handling) |
|
|
| `src/components/cloud/` | Cloud-related modals and widgets: ImageDetailModal, CloudEditModal, MapPickerModal, QuickUploadModal, MiniLocationMap |
|
|
| `src/components/layout/` | AppHeader (top nav bar with auth state) |
|
|
| `src/components/profile/` | ContributionHeatmap |
|
|
| `src/style.css` | Global visual system hooks: shared button/card utility classes, base typography, body background |
|
|
| `src/views/` | Route-level page components (see Routes below) |
|
|
| `src/types/` | TypeScript types: domain models (`database.ts`), API DTOs (`api.ts`), AMap declarations (`amap.d.ts`), router meta (`router.d.ts`) |
|
|
|
|
## Routes
|
|
|
|
| Path | View | Auth Required |
|
|
| --- | --- | :---: |
|
|
| `/` | MapView | No |
|
|
| `/login`, `/register`, `/forgot-password`, `/auth/confirm`, `/auth/reset-password` | Auth views | No |
|
|
| `/upload` | UploadView | Yes |
|
|
| `/encyclopedia` | EncyclopediaView | Yes |
|
|
| `/encyclopedia/:id` | CloudTypeView | Yes |
|
|
| `/gallery` | GalleryView | No |
|
|
| `/community` | CommunityView | No |
|
|
| `/profile` | ProfileView (own) | Yes |
|
|
| `/profile/settings` | ProfileSettingsView | Yes |
|
|
| `/profile/:id` | ProfileView (public) | No |
|
|
| `/admin` | AdminView | Yes (admin) |
|
|
| `/403` | ForbiddenView | No |
|
|
| `/401` | AuthRequiredView | No |
|
|
| `/:pathMatch(.*)*` | NotFoundView | No |
|
|
|
|
- Route guards in `router/index.ts`: `requiresAuth` redirects to `/401` with the intended URL in the `redirect` query; `requiresAdmin` redirects to `/403`
|
|
- SEO meta tags applied per-route via `lib/seo.ts` in `router.afterEach`
|
|
|
|
## Auth Flow
|
|
|
|
- **`main.ts` initializes auth before mounting**: `authStore.initialize()` exchanges the HttpOnly refresh cookie for an access token. The app only mounts after this initial check.
|
|
- Access tokens live in memory only. Refresh cookies are managed by the backend and sent with `credentials: 'include'`.
|
|
- `src/lib/api.ts` performs one automatic refresh-and-retry after an authenticated request receives `401`.
|
|
- API auth state changes are synchronized back to the Pinia store through `opencloud:auth-updated` and `opencloud:auth-expired` window events.
|
|
- Email confirmation and password reset pages consume the opaque `token` query parameter generated by the backend.
|
|
- Passwords must contain at least 8 characters in registration, settings, and reset flows.
|
|
- Registration, email confirmation, login, password reset, profile creation, and uniqueness checks are backend responsibilities.
|
|
|
|
## Stores
|
|
|
|
- **auth** — User session, profile, login/register/logout, username/password update, password reset. `initialize()` must be called before app mounts.
|
|
- **clouds** — Simple cache of cloud types returned by `GET /cloud-types`. Fetched once and shared across views.
|
|
- **encyclopedia** — Cloud types + user's collection (unlock state). Tracks `unlockPercent` for progress display. Depends on `authStore`.
|
|
- **profile** — User profile pages. Fetches profile + cloud list per-user. Supports update/delete/visibility-toggle with optimistic cache patching; backend deletion also removes stored files.
|
|
|
|
## FastAPI Backend
|
|
|
|
- Backend project lives in the sibling directory `../opencloud-backend`.
|
|
- Default development API URL is `http://localhost:8000/api/v1`; set `VITE_API_URL` for deployed environments.
|
|
- Frontend route guards are for navigation UX only. The backend enforces ownership, authentication, admin roles, and disabled-account rules.
|
|
- Images are uploaded as multipart form data. The backend stores originals, creates thumbnails, blurs coordinates, writes database records, and atomically unlocks badges.
|
|
|
|
## API Conventions
|
|
|
|
- All requests must go through `src/lib/api.ts`; do not call `fetch` directly from views, stores, or composables.
|
|
- API paths passed to `apiRequest()` are relative to `VITE_API_URL`, for example `/clouds` rather than `/api/v1/clouds`.
|
|
- Public requests explicitly use `{ auth: false }`. Authenticated requests use the default and receive a Bearer access token.
|
|
- JSON request bodies are plain objects. Image uploads use `FormData`; do not set the multipart `Content-Type` header manually.
|
|
- FastAPI errors use `detail`; `ApiError` converts string and validation-array details into a user-facing message.
|
|
- Backend DTO fields use `snake_case`. Page/store adapters may expose display-only camelCase fields such as `cloudTypeName`.
|
|
- Paginated endpoints return `items`, `page`, `page_size`, `total`, and `total_pages`.
|
|
- Cloud batch mutation endpoints accept at most 100 IDs. Profile and admin code split larger selections into chunks of 100.
|
|
|
|
## Gallery Pagination
|
|
|
|
- Page-based navigation (50 items/page), not infinite scroll.
|
|
- The backend resolves cloud-type/custom-type searches and `@username` searches.
|
|
- Each response includes `items`, `total`, `page`, `page_size`, and `total_pages`.
|
|
- Search debounced at 250ms.
|
|
|
|
## Map Timeline
|
|
|
|
- Realtime mode: slider selects a minute of today, shows clouds captured within 2 hours before that time. Marker opacity decays with age.
|
|
- Archive mode: browse clouds by day or month. Toggling timeline controls closed auto-returns to realtime.
|
|
- Slider resets to current time each time the controls panel is opened.
|
|
|
|
## Upload Flow
|
|
|
|
- `useUpload` handles file selection, previews, EXIF date extraction, validation, sequential multipart uploads, progress, and badge results. Thumbnail generation, coordinate blurring, storage, database insertion, and badge unlocking run on the backend.
|
|
- Batch items upload sequentially. If a later item fails, earlier successful uploads remain saved; there is no transactional rollback or resumable upload in the frontend.
|
|
- `UploadView` is the full-page batch uploader. `QuickUploadModal` is a single-image shortcut from the map page.
|
|
|
|
## Build & Deploy
|
|
|
|
- **Vercel** with `vercel.json`: SPA rewrites, security headers (X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy). No CSP or HSTS configured.
|
|
- Vite dev and preview servers both use strict port `5173`, matching the backend's default `CORS_ORIGINS` and `FRONTEND_URL`.
|
|
- **SEO plugin** in `vite.config.ts`: generates `robots.txt` and `sitemap.xml` at build time. Auth-only routes, including `/encyclopedia`, are excluded from the sitemap and disallowed in `robots.txt`.
|
|
- **`lib/canvas.ts`**: patches `HTMLCanvasElement.getContext('2d')` to always pass `willReadFrequently: true` — needed for the badge card renderer in `lib/cloudBadges.ts`.
|
|
|
|
## Environment Variables
|
|
|
|
Required for map features:
|
|
|
|
- `VITE_AMAP_KEY`
|
|
|
|
Optional:
|
|
|
|
- `VITE_API_URL` — FastAPI base URL including `/api/v1` (defaults to `http://localhost:8000/api/v1`)
|
|
- `VITE_SITE_URL` — canonical frontend URL used by route SEO and generated sitemap; set it explicitly in deployed environments
|
|
|
|
Backend-only variables such as database credentials, JWT secrets, SMTP settings, cookie policy, CORS origins, and upload paths belong in `../opencloud-backend/.env`, never in the Vite environment.
|
|
|
|
## Naive UI
|
|
|
|
- Used for modals, buttons, inputs, tags, progress bars, alerts, dropdowns, empty states, skeletons, and message toasts.
|
|
- No SSR — pure client-side rendering.
|
|
- Custom CSS overrides via scoped `<style>` blocks for slider styling, transitions, and component tweaks.
|
|
- **Do not rely on default Naive UI primary/secondary colors for page CTAs or panel actions.** The project now uses shared global classes in `src/style.css` to keep buttons visually consistent with the sky-atlas theme.
|
|
- Auth buttons use `oc-primary-button` with semantic variants:
|
|
- `oc-primary-button--teal` — login / account-access actions
|
|
- `oc-primary-button--sky` — register / create / forward actions
|
|
- Panel and utility buttons use `oc-panel-button` with semantic variants:
|
|
- `oc-panel-button--neutral` — white card-style utility actions
|
|
- `oc-panel-button--sky` — primary panel actions
|
|
- `oc-panel-button--teal` — active toggles / confirm actions
|
|
- `oc-panel-button--danger` — destructive actions
|
|
- `oc-panel-button--amber` — admin/state-toggle actions where warning emphasis fits better than danger
|
|
- Card-like containers should prefer shared shadow classes instead of ad-hoc shadows:
|
|
- `oc-panel-card`
|
|
- `oc-panel-card-soft`
|
|
- `oc-empty-card`
|
|
- When styling `NButton`, prefer `type="default"` plus the shared class when a custom panel button variant is intended. Otherwise Naive UI theme variables may override white backgrounds or semantic colors.
|
|
|
|
## Visual Notes
|
|
|
|
- The site icon and header brand mark are a matching pixel-style cloud motif; avoid reintroducing emoji or unrelated icon styles for primary brand surfaces.
|
|
- Header action buttons use slight hover lift (`translate(-1px, -1px)`) with hard-offset shadow growth; new header-like actions should follow that interaction pattern.
|
|
- Heatmap view toggles in `ContributionHeatmap` intentionally use separate buttons with gap spacing instead of `NButtonGroup`, because grouped buttons visually collide once hard shadows and hover transforms are applied.
|
|
|
|
## MVP Constraints
|
|
|
|
- No realtime updates — refresh-based loading
|
|
- No OAuth — email/password only, email confirmation required
|
|
- No AI cloud identification — manual type selection
|
|
- AMap only (China-focused), no Mapbox fallback yet
|