210 lines
21 KiB
Markdown
210 lines
21 KiB
Markdown
# Implementation Status
|
|
|
|
Last updated: 2026-09-09
|
|
|
|
## Resume Here
|
|
|
|
Before changing code, read this document, `AGENTS.md`, and the linked source-of-truth documentation. Then confirm the current baseline with:
|
|
|
|
```sh
|
|
npm test
|
|
```
|
|
|
|
For persistence or Redis-backed changes, also run the isolated integration suite after setting `TEST_DATABASE_URL` to the `*_test` database and `TEST_REDIS_URL` to the test Redis instance:
|
|
|
|
```sh
|
|
TEST_DATABASE_URL='postgresql://...' TEST_REDIS_URL='redis://...' npm run test:integration
|
|
```
|
|
|
|
The current local container runtime is OrbStack. If Docker commands point at a stale host, run:
|
|
|
|
```sh
|
|
unset DOCKER_HOST
|
|
docker context use orbstack
|
|
```
|
|
|
|
## Completed
|
|
|
|
### Workspace And Database
|
|
|
|
- Npm workspace with the API and shared contracts/validation packages.
|
|
- PostgreSQL Compose service on local port `5433`; API port `3000` is registered in `.local/project-ports.json`.
|
|
- Redis Compose service on local port `6380` supports distributed login throttling.
|
|
- Drizzle schema and migrations:
|
|
- `0000_core_inventory`: users, sellers, categories, condition profiles, conditions, products, and physical inventory items.
|
|
- `0001_auth_sessions`: hashed server-side sessions, expiry, revocation, IP address, and user agent.
|
|
- `0002_session_csrf_tokens`: session-bound hashed CSRF tokens.
|
|
- `0003_security_audit_events`: private append-only authentication security events.
|
|
- `0004_password_reset_tokens`: hashed, one-time password recovery tokens.
|
|
- `0005_mfa_methods`: protected TOTP method records and lifecycle state.
|
|
- `0006_listings`: listing records and lifecycle enum.
|
|
- `0007_listing_image_count`: persisted image prerequisite for publication.
|
|
- `0008_inventory_images`: item-owned image metadata and processing state.
|
|
- `0009_remove_listing_image_count`: publication no longer stores a listing-level image snapshot.
|
|
- Isolated integration tests require a database name ending in `_test` and truncate only that database's tables.
|
|
- Local-only `npm run provision:admin` creates the first Admin User and linked Seller atomically. It requires `INITIAL_ADMIN_EMAIL`, `INITIAL_ADMIN_PASSWORD`, and `INITIAL_SELLER_NAME`, hashes the password with scrypt, and refuses once any User exists.
|
|
|
|
### Domain And Persistence
|
|
|
|
- Product identity requires name, category, and seller.
|
|
- Products receive seller-scoped, normalized slugs; duplicate names receive a numeric suffix.
|
|
- Each Inventory Item is a separate physical copy.
|
|
- Inventory condition must belong to the Product category.
|
|
- Inventory lifecycle supports `ACTIVE`, `SOLD`, and `ARCHIVED`; sold and archived items are locked against ordinary edits.
|
|
- Repositories for products, inventory items, conditions, users, and sessions use Drizzle/PostgreSQL.
|
|
- Listing persistence supports draft creation, seller-scoped lookup, status updates, and inventory-item history.
|
|
|
|
### API And Authentication
|
|
|
|
- Fastify app bootstrap and `GET /health` response.
|
|
- Admin Product endpoints:
|
|
- `GET /api/v1/admin/products`
|
|
- `POST /api/v1/admin/products`
|
|
- Admin Inventory endpoint:
|
|
- `POST /api/v1/admin/inventory`
|
|
- Authentication endpoints:
|
|
- `POST /api/v1/auth/login`
|
|
- `POST /api/v1/auth/logout`
|
|
- `GET /api/v1/auth/me`
|
|
- Login uses asynchronous scrypt password verification. Session tokens are random, stored only as SHA-256 hashes, and never returned in JSON.
|
|
- Session cookie settings are `HttpOnly`, `SameSite=Strict`, path `/`, and `Secure` in production.
|
|
- Login is limited to five attempts per client IP every 15 minutes and returns `429 AUTH_RATE_LIMITED` with `Retry-After` once exhausted. Redis atomically coordinates the limit across API processes.
|
|
- Login sets a separate same-site CSRF cookie. Authenticated state-changing routes require its matching `x-csrf-token` header, validated against the session-bound hash.
|
|
- `GET /api/v1/auth/sessions` returns safe metadata for the current user's active sessions and marks the current session. `DELETE /api/v1/auth/sessions/:id` revokes an owned session; `DELETE /api/v1/auth/sessions` revokes all other sessions while retaining the current one. Revoking the current session clears both cookies.
|
|
- Active sessions renew `last_activity_at` after five minutes, avoiding a database write on every authenticated request.
|
|
- Authentication events are retained separately from inventory activity. `LOGIN_SUCCESS`, `LOGIN_FAILURE`, `SESSION_CREATED`, and `SESSION_REVOKED` include the known actor where available, plus safe request context. Passwords and session/CSRF tokens or hashes are never recorded.
|
|
- Password recovery uses generic `POST /api/v1/auth/forgot-password` responses to prevent account enumeration. Opaque tokens are delivered only through the SMTP adapter, stored only as hashes, expire after one hour, are consumed once, and revoke existing sessions after a password reset.
|
|
- API startup requires `SMTP_URL`, `SMTP_FROM`, and `PASSWORD_RESET_URL`; configure these local values before starting the server.
|
|
- Existing admin routes validate the authenticated session and derive seller ownership server-side.
|
|
- Request schemas live in `packages/validation`; admin response DTOs live in `packages/contracts`.
|
|
- MFA enrollment, verification, disable, protected secret handling, login verification, and audit events are implemented.
|
|
- Admin listing creation and lifecycle routes are implemented:
|
|
- `POST /api/v1/admin/listings`
|
|
- `GET /api/v1/admin/listings`
|
|
- `GET /api/v1/admin/listings/:id`
|
|
- `POST /api/v1/admin/listings/:id/publish`
|
|
- `POST /api/v1/admin/listings/:id/unpublish`
|
|
- `POST /api/v1/admin/listings/:id/pending`
|
|
- `POST /api/v1/admin/listings/:id/archive`
|
|
- Listing publication is seller-scoped, CSRF-protected, and gated by persisted identity, price, physical inventory, condition, and image prerequisites.
|
|
- Listing publication rejects physical inventory in `SOLD` or `ARCHIVED` status with a conflict response.
|
|
- Listing reads are seller-scoped and return only admin listing DTO fields.
|
|
- Item-owned image metadata can be added through a seller-scoped, CSRF-protected admin route; publication uses the persisted image count.
|
|
- Public search is available at `GET /api/v1/public/search` through a normalized SearchService boundary with pagination, price filters, text matching, and sold-item visibility controls.
|
|
- Public search also supports active category and condition lookup options, URL-backed filters, and allowlisted recent/price sorting.
|
|
- Public search responses include `items` plus `pagination` metadata (`page`, `limit`, `total`, and `totalPages`); pending listings are visible by default and sold listings require an explicit filter.
|
|
- Public product pages are available at `GET /api/v1/public/products/:slug`; responses use a public DTO and require an active physical item with an available or pending listing.
|
|
- Public listing collection and detail reads are available at `GET /api/v1/public/listings` and `GET /api/v1/public/listings/:id`; collection responses include pagination metadata and detail reads hide internal inventory identity.
|
|
- Public listing detail responses include safe condition labels, latest market value, and public image URLs; unavailable image states are rendered accessibly in the gallery.
|
|
- Public inventory collection and detail reads are available at `GET /api/v1/public/inventory` and `GET /api/v1/public/inventory/:id`; responses join only active physical items to available or pending listings and exclude private item notes.
|
|
- The public seller directory is available at `GET /api/v1/public/sellers`; it returns only sellers with visible inventory and exposes display identity without user or contact fields.
|
|
- The web workspace now has a Vue/Vite public catalog shell with Vue Router, a catalog API service, search composable, loading/error/empty states, and responsive search-first styling.
|
|
- The web catalog now provides keyboard-accessible listing cards with stable `/listings/:id` links, a public listing detail page, and service-level detail loading/error handling.
|
|
- Catalog search and pagination are synchronized to the browser URL (`q` and `page`) with accessible previous/next controls; the web port `5173` is declared in the project port registry.
|
|
- The web catalog includes a stable `/products/:slug` product detail route with API-backed loading, not-found/error states, and semantic product facts.
|
|
- The visual design foundation is centralized in web tokens with light/dark/system runtime theme support; the fixture composition has moved to Storybook at port `6006` with the accessibility addon and component stories for available, pending, and sold states.
|
|
- `ImageStorage`, traversal-safe local storage, multipart upload transport, binary signature validation, and worker-backed derivative processing are implemented.
|
|
- Derivative orchestration now defines thumbnail, medium, and large WebP storage outputs behind an injected renderer; the concrete worker-backed renderer remains.
|
|
- The worker owns an image derivative job runner with explicit processing, completion, and failure outcomes backed by persistent PostgreSQL job records.
|
|
- Image metadata limits are enforced in the service boundary: supported MIME types, 10 MiB default file size, and 10,000-pixel maximum dimensions.
|
|
- Acquisition, sales, dashboard, pricing observations, market valuations, audited sale voiding, and manual market-value overrides are implemented with seller-scoped API and admin UI workflows.
|
|
- Seller-scoped bulk archive is implemented through the CSRF-protected `POST /api/v1/admin/bulk` route; it validates active inventory lifecycle transitions and never substitutes for the dedicated sale workflow.
|
|
|
|
### Verified Baseline
|
|
|
|
| Suite | Current coverage |
|
|
| --- | --- |
|
|
| Domain | Product requirements and inventory lifecycle rules |
|
|
| Application | Product slug allocation, condition compatibility, password verification/recovery, session issuance, activity renewal, management, and audit events |
|
|
| HTTP | Product and inventory creation, validation, authentication/recovery, CSRF, rate limiting, and session management |
|
|
| Integration | PostgreSQL catalog/session/audit/password recovery persistence and Redis-shared login throttling |
|
|
|
|
Verified locally: `npm test` passes 164 tests with no failures (22 domain, 78 application, and 64 HTTP); worker tests, web tests, web build, Storybook build, Storybook interaction/accessibility checks, integration tests, and authenticated E2E workflows also pass when their required local services and credentials are available.
|
|
|
|
## Next Milestone
|
|
|
|
Complete production configuration and operational maturity around the implemented vertical slices.
|
|
|
|
1. Configure and validate an approved external pricing provider and provider-specific mapping data.
|
|
2. Expand broader accessibility/manual coverage and address any remaining integration edge cases.
|
|
|
|
Acceptance criteria: production provider credentials are secret-managed, migrations and backups are exercised in the target environment, and CI runs integration, authenticated E2E, web, build, Storybook build, and Storybook interaction/accessibility gates.
|
|
|
|
CI also runs the worker suite, production dependency audit, and deployment/restore script contract checks.
|
|
|
|
## Overall Roadmap
|
|
|
|
Work through one bounded vertical slice at a time. The linked documentation remains authoritative when this roadmap is less detailed.
|
|
|
|
Completed vertical slices include authentication, listings and image processing, acquisitions, sales and audited corrections, dashboard metrics/alerts, pricing observations and quality handling, provider mappings, durable pricing refresh jobs, public search/detail/image states, bulk operations, exports, settings, Saved Views, admin workflows, CI integration/E2E gates, production Docker packaging, rollback/health checks, PostgreSQL backup verification, restore drill scripts, image-storage restore support, and deployment smoke checks.
|
|
|
|
Remaining roadmap areas are approved external provider credentials/adapters, broader accessibility/manual coverage, and deployment-specific NAS/TLS validation.
|
|
|
|
## Known Gaps
|
|
|
|
- No initial User/Seller is provisioned in the development database yet. Run `npm run provision:admin` after setting its required local `.env` variables.
|
|
- Listings are partially implemented: draft creation, seller-scoped reads, publication gating, lifecycle writes, archive behavior, sold/archived inventory checks, and item-owned image metadata are complete; storage-backed processing remains.
|
|
- The provider abstraction, fixture provider, HTTP provider adapter, Product mapping workflow, durable refresh queue, worker execution, polling contract, retry policy, and completed-job purge operation are implemented. Production readiness still requires approved external provider credentials and provider-specific mapping data.
|
|
- API and worker production startup now reject fixture pricing, missing provider endpoints, and non-HTTPS provider endpoints before opening runtime connections.
|
|
- Pricing refresh requests now enqueue seller-scoped jobs and expose a safe `GET /api/v1/admin/jobs/:id` polling contract instead of doing provider work inline.
|
|
- Product-level provider mappings now persist seller-scoped `PRODUCT`/`SEARCH` relationships and support active listing and controlled disabling.
|
|
- Pricing refresh enqueue now resolves its provider from an active Product mapping and rejects unmapped Products explicitly.
|
|
- Local development and CI can enable a deterministic fixture provider for end-to-end refresh coverage; production provider adapters remain explicitly configured deployment boundaries.
|
|
- Pricing refresh jobs have a bounded three-attempt retry policy with exponential backoff. The worker schedules completed-job cleanup using configurable retention days and cleanup interval settings.
|
|
- Bulk archive, asynchronous bulk pricing refresh, and Product-level bulk tag updates are implemented through the seller-scoped API boundary. Inventory `SOLD` remains intentionally exclusive to the audited sale workflow.
|
|
- Admin inventory JSON export is implemented at `GET /api/v1/admin/exports/inventory.json` and serializes seller-scoped admin DTOs as a downloadable attachment.
|
|
- Admin inventory CSV export is implemented at `GET /api/v1/admin/exports/inventory.csv` with escaped spreadsheet-safe fields and seller-scoped admin DTOs.
|
|
- Admin inventory Excel-compatible export is implemented at `GET /api/v1/admin/exports/inventory.xls` using the XML workbook format and seller-scoped admin DTOs.
|
|
- Seller-scoped admin settings are implemented through `GET/PATCH /api/v1/admin/settings` with strict category allowlists and server-side partial-update merging.
|
|
- Seller-scoped saved-view CRUD is implemented through the documented admin API with validated filter maps and repository-level ownership checks.
|
|
- Large inventory JSON exports can be enqueued through `POST /api/v1/admin/exports/inventory.json/jobs`, polled through `GET /api/v1/admin/exports/jobs/:id`, and downloaded after completion; the worker owns claim, completion, and failure processing.
|
|
- Completed asynchronous inventory JSON exports can be downloaded through `GET /api/v1/admin/exports/jobs/:id/download`.
|
|
- PostgreSQL restore verification and image snapshot restore scripts are implemented and covered by shell tests as part of the deployment drill workflow.
|
|
- `npm audit --omit=dev` currently reports zero production vulnerabilities. Re-run the audit after dependency changes and review development-dependency findings separately before production releases.
|
|
- Authenticated E2E coverage now verifies both the core catalog workflow and the Settings, Saved Views, and export-controls workflow.
|
|
- Production dependency audit currently reports zero vulnerabilities with `npm audit --omit=dev`.
|
|
- Public catalog sorting now supports recently added, recently updated, name A-Z, name Z-A, price low-to-high, and price high-to-low through a URL-synchronized atomic sort control. Broader inventory filter parity and market-value sorting remain.
|
|
- Public catalog filtering now supports URL-synchronized set, event, and tag text filters through a reusable atomic text-filter control and the shared search API.
|
|
- Public catalog filtering now supports explicit Available, Pending, Sold, and All status modes plus a 30-day Newly added toggle through the shared search API and atomic controls.
|
|
- The admin workspace now includes a responsive guided inventory workflow stepper linking Product, Sources, Copy, Images, Pricing, and List stages, with progress derived from loaded admin state.
|
|
- Product identification is now an extracted `ProductIdentityStep` molecule with empty/filled Storybook states, while preserving the existing create-product API workflow.
|
|
- Source mapping is now an extracted `ProviderMappingStep` molecule with Product/Provider/PRODUCT-vs-SEARCH mapping states, while preserving existing mapping loading and save behavior.
|
|
- Physical inventory creation is now an extracted `InventoryCopyStep` molecule with Product and condition selection states, while preserving the existing seller-scoped create-copy API workflow.
|
|
- Image management is now surfaced as an extracted `InventoryImagesStep` molecule with missing-image status and direct links to the existing upload/processing manager route.
|
|
- Pricing is now an extracted `PricingReviewStep` molecule covering observations, provider refresh, and reasoned manual overrides while preserving existing pricing API behavior.
|
|
- Review and listing is now an extracted `ReviewListStep` molecule covering physical-copy selection and asking-price confirmation while preserving existing draft creation and publication lifecycle behavior.
|
|
- The guided inventory workflow now has extracted Product, Source Mapping, Physical Copy, Images, Pricing, and Review/List frontend stages, each with colocated Storybook coverage.
|
|
- Public catalog sorting now supports latest manual/calculated market value low-to-high and high-to-low, with null values placed last.
|
|
- Public listing detail now shows market-value freshness and suppresses asking price for sold listings, with Storybook coverage for sold and valued states.
|
|
- Public catalog filters now collapse behind an accessible mobile disclosure, show an active-filter count, and provide a reset action while preserving URL synchronization.
|
|
- The admin image manager now supports multi-file upload with a drag-and-drop dropzone, per-image alt-text editing, primary selection, up/down reordering, and per-image retry/delete actions targeted at the correct image instead of the first uploaded image.
|
|
- The guided inventory workflow is now a true single-step-at-a-time wizard: the stepper is clickable, only the active stage's panel is shown, Back/Next controls advance between stages, and the active stage persists across reloads through local storage.
|
|
- Listing management (publish/mark pending) is now a dedicated `/admin/listings` screen with its own navigation entry; the guided workflow overview links to it and only tracks listing creation and a lightweight count for wizard completion.
|
|
- Sale recording/voiding and acquisition recording are now dedicated `/admin/sales` and `/admin/acquisitions` screens with their own navigation entries; the guided workflow overview links to both instead of embedding their forms and history inline.
|
|
- The guided wizard now manages focus and announces step changes: selecting a stage (via the stepper or Back/Next) moves keyboard/assistive-technology focus to that stage's labeled region and updates a live-region status announcement, with Storybook interaction coverage.
|
|
- Seller-scoped latest market valuations are now readable through `GET /api/v1/admin/valuations`.
|
|
- Physical inventory management (bulk archive, bulk pricing refresh, bulk tag updates, market valuations, and pricing job retry) is now a dedicated `/admin/inventory` screen with its own navigation entry; the guided workflow overview links to it and only tracks a lightweight inventory count for wizard completion.
|
|
- Storybook now installs a lightweight router mirroring the app's named routes plus a mobile-viewport-forcing test-runner hook, enabling isolated page stories (including the public catalog) that use `useRoute`/`useRouter` or narrow-viewport CSS breakpoints.
|
|
- The public catalog now has its own Storybook coverage, including an interaction test proving the mobile filter disclosure is keyboard-operable and correctly hides/reveals filter controls via `aria-expanded`. This review also found and fixed a real color-contrast failure in the catalog error-state text.
|
|
|
|
## Local Commands
|
|
|
|
```sh
|
|
npm install
|
|
npm run db:up
|
|
npm run db:migrate
|
|
npm run provision:admin
|
|
npm test
|
|
```
|
|
|
|
The Compose database requires a local `.env` created from `.env.example`. To prepare the test database once after bringing PostgreSQL up:
|
|
|
|
```sh
|
|
docker compose exec -T postgres sh -c 'createdb -U "$POSTGRES_USER" pokemon_inventory_test'
|
|
```
|
|
|
|
Then migrate it using its `TEST_DATABASE_URL` value as `DATABASE_URL`, and run integration tests with both `TEST_DATABASE_URL` and `TEST_REDIS_URL` set. See [README.md](../README.md) for the environment-file prerequisites.
|
|
|
|
## Suggested Resume Prompt
|
|
|
|
`Read docs/IMPLEMENTATION-STATUS.md and continue the next bounded milestone. Verify the existing baseline first, follow AGENTS.md, and keep the change focused with tests.` |