Register Fastify multipart support, validate JPEG/PNG/WebP binary signatures, store upload bytes through ImageStorage, persist metadata after storage succeeds, clean up bytes on metadata failure, and document the upload boundary. Verification: npm test passed 68 tests and integration passed 14 tests. No build script is configured.
157 lines
9.9 KiB
Markdown
157 lines
9.9 KiB
Markdown
# 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:
|
|
|
|
```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.
|
|
- `ImageStorage`, traversal-safe local storage, multipart upload transport, and binary signature validation are implemented; derivative processing remains.
|
|
- Image metadata limits are enforced in the service boundary: supported MIME types, 10 MiB default file size, and 10,000-pixel maximum dimensions.
|
|
|
|
### 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 68 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.
|
|
|
|
1. Add processing/derivative lifecycle for persisted image metadata.
|
|
2. 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.
|
|
|
|
1. Authentication hardening.
|
|
2. Listings and image prerequisites: keep Listing separate from Inventory Item; enforce publish requirements from [REQUIREMENTS.md](REQUIREMENTS.md).
|
|
3. Acquisition, pricing provider mappings, observations, valuations, and private/public DTO boundaries from [DATA-MODEL.md](DATA-MODEL.md) and [PRICING.md](PRICING.md).
|
|
4. Sales workflow, sold-item locking, corrections, cost basis, and audit history.
|
|
5. SearchService, public catalog/search endpoints, filtering, pagination, and public product URLs.
|
|
6. Image storage abstraction, upload validation, processing worker, responsive variants, and alt-text editing.
|
|
7. Vue admin workflows and public catalog, following [DESIGN.md](DESIGN.md), [UX.md](UX.md), and [ACCESSIBILITY.md](ACCESSIBILITY.md).
|
|
8. Dashboard, bulk operations, exports, settings, workers, and provider adapters.
|
|
9. 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: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.
|
|
- Sales, acquisitions, pricing, image processing, search, worker jobs, web UI, CI, and deployment services remain unimplemented.
|
|
- `npm install` currently reports dependency audit findings. Review and remediate them before production deployment; do not use `npm audit fix --force` without reviewing its changes.
|
|
|
|
## 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.` |