Remove the temporary listing-level image count from the schema, validation, DTO, and draft contract. Publication now requires the persisted inventory image repository count. Add migration 0009 and update listing tests and implementation status. Verification: npm test passed 62 tests and integration passed 14 tests. No build script is configured in this repository.
9.8 KiB
Implementation Status
Last updated: 2026-09-08
Resume Here
Before changing code, read this document, AGENTS.md, and the linked source-of-truth documentation. Then confirm the current baseline with:
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:
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:
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 port3000is registered in.local/project-ports.json. - Redis Compose service on local port
6380supports 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
_testand truncate only that database's tables.- Local-only
npm run provision:admincreates the first Admin User and linked Seller atomically. It requiresINITIAL_ADMIN_EMAIL,INITIAL_ADMIN_PASSWORD, andINITIAL_SELLER_NAME, hashes the password with scrypt, and refuses once any User exists.
- Local-only
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, andARCHIVED; 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 /healthresponse. - Admin Product endpoints:
GET /api/v1/admin/productsPOST /api/v1/admin/products
- Admin Inventory endpoint:
POST /api/v1/admin/inventory
- Authentication endpoints:
POST /api/v1/auth/loginPOST /api/v1/auth/logoutGET /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/, andSecurein production. - Login is limited to five attempts per client IP every 15 minutes and returns
429 AUTH_RATE_LIMITEDwithRetry-Afteronce 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-tokenheader, validated against the session-bound hash. GET /api/v1/auth/sessionsreturns safe metadata for the current user's active sessions and marks the current session.DELETE /api/v1/auth/sessions/:idrevokes an owned session;DELETE /api/v1/auth/sessionsrevokes all other sessions while retaining the current one. Revoking the current session clears both cookies.- Active sessions renew
last_activity_atafter five minutes, avoiding a database write on every authenticated request. - Authentication events are retained separately from inventory activity.
LOGIN_SUCCESS,LOGIN_FAILURE,SESSION_CREATED, andSESSION_REVOKEDinclude 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-passwordresponses 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, andPASSWORD_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 inpackages/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/listingsGET /api/v1/admin/listingsGET /api/v1/admin/listings/:idPOST /api/v1/admin/listings/:id/publishPOST /api/v1/admin/listings/:id/unpublishPOST /api/v1/admin/listings/:id/pendingPOST /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
SOLDorARCHIVEDstatus 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.
ImageStorageand a traversal-safe local filesystem adapter are implemented; upload transport, signature validation, and derivative processing remain.
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 |
Latest result: npm test passed 62 tests; npm run test:integration passed 14 tests against OrbStack PostgreSQL and Redis.
Next Milestone
Complete the inventory/listing integrity boundary before starting pricing and acquisition.
- Add upload transport, MIME/signature/size validation, and processing/derivative lifecycle for persisted image metadata.
- Begin acquisition and pricing foundations after the listing/image boundary is complete.
Acceptance criteria: listing reads and lifecycle writes preserve seller ownership, CSRF, inventory status, and publication prerequisites.
Overall Roadmap
Work through one bounded vertical slice at a time. The linked documentation remains authoritative when this roadmap is less detailed.
- Authentication hardening.
- Listings and image prerequisites: keep Listing separate from Inventory Item; enforce publish requirements from REQUIREMENTS.md.
- Acquisition, pricing provider mappings, observations, valuations, and private/public DTO boundaries from DATA-MODEL.md and PRICING.md.
- Sales workflow, sold-item locking, corrections, cost basis, and audit history.
- SearchService, public catalog/search endpoints, filtering, pagination, and public product URLs.
- Image storage abstraction, upload validation, processing worker, responsive variants, and alt-text editing.
- Vue admin workflows and public catalog, following DESIGN.md, UX.md, and ACCESSIBILITY.md.
- Dashboard, bulk operations, exports, settings, workers, and provider adapters.
- CI, Docker deployment completion, backups, end-to-end/accessibility coverage, and operational documentation.
Known Gaps
- No initial User/Seller is provisioned in the development database yet. Run
npm run provision:adminafter setting its required local.envvariables. - 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.
- Sales, acquisitions, pricing, image processing, search, worker jobs, web UI, CI, and deployment services remain unimplemented.
npm installcurrently reports dependency audit findings. Review and remediate them before production deployment; do not usenpm audit fix --forcewithout reviewing its changes.
Local Commands
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:
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 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.