feat: establish auth listing and image vertical slices
Includes MFA-capable authentication, session and CSRF security, password recovery, listing lifecycle and ownership rules, inventory image metadata, local ImageStorage, migrations, tests, and implementation status documentation. Verification: npm test (62 passing) and integration suite (14 passing).
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
# Accessibility
|
||||
|
||||
Target WCAG 2.2 Level AA.
|
||||
|
||||
## Required practices
|
||||
|
||||
- Semantic HTML.
|
||||
- Full keyboard access.
|
||||
- Visible focus indicators.
|
||||
- Logical focus order.
|
||||
- Accessible names for controls.
|
||||
- Labels for all form inputs.
|
||||
- Required/optional state communicated programmatically and visually.
|
||||
- Accessible validation and error messages.
|
||||
- No color-only meaning.
|
||||
- Sufficient contrast.
|
||||
- Responsive layouts.
|
||||
- Support browser zoom and text resizing.
|
||||
- Accessible dialogs and galleries.
|
||||
- Focus management for dynamic UI.
|
||||
- Appropriate live-region/status announcements for important asynchronous changes.
|
||||
- Meaningful image alt text.
|
||||
- Use ARIA only where native semantics are insufficient.
|
||||
|
||||
## Testing
|
||||
|
||||
Run automated accessibility testing in CI.
|
||||
|
||||
Serious accessibility regressions should fail CI.
|
||||
|
||||
Automated tests do not replace manual keyboard and assistive-technology review for important workflows.
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
# API
|
||||
|
||||
## Base
|
||||
|
||||
REST API under `/api/v1`.
|
||||
|
||||
Responses use:
|
||||
|
||||
```json
|
||||
{ "data": {} }
|
||||
```
|
||||
|
||||
Collections include pagination metadata.
|
||||
|
||||
## Public endpoints
|
||||
|
||||
```text
|
||||
GET /api/v1/public/search
|
||||
GET /api/v1/public/products/:slug
|
||||
GET /api/v1/public/inventory
|
||||
GET /api/v1/public/listings
|
||||
GET /api/v1/public/sellers
|
||||
GET /api/v1/public/currencies
|
||||
GET /api/v1/public/exchange-rates
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
```text
|
||||
POST /api/v1/auth/login
|
||||
POST /api/v1/auth/logout
|
||||
POST /api/v1/auth/forgot-password
|
||||
POST /api/v1/auth/reset-password
|
||||
GET /api/v1/auth/me
|
||||
POST /api/v1/auth/mfa/enroll
|
||||
POST /api/v1/auth/mfa/verify
|
||||
POST /api/v1/auth/mfa/disable
|
||||
GET /api/v1/auth/sessions
|
||||
DELETE /api/v1/auth/sessions/:id
|
||||
DELETE /api/v1/auth/sessions
|
||||
```
|
||||
|
||||
`POST /api/v1/auth/forgot-password` always returns the same accepted response after validating an email-shaped request, whether or not an account exists. It sends an opaque, one-time reset token only through the configured mail adapter. `POST /api/v1/auth/reset-password` consumes that token, updates the password, and revokes all active sessions.
|
||||
|
||||
`GET /api/v1/auth/sessions` returns only the authenticated user's active sessions and marks the current session. It never returns session or CSRF token material. `DELETE /api/v1/auth/sessions/:id` revokes one session owned by the authenticated user. `DELETE /api/v1/auth/sessions` revokes all other active sessions while retaining the current session. Session revocation endpoints require CSRF validation.
|
||||
|
||||
## Admin endpoints
|
||||
|
||||
```text
|
||||
GET /api/v1/admin/products
|
||||
POST /api/v1/admin/products
|
||||
GET /api/v1/admin/products/:id
|
||||
PATCH /api/v1/admin/products/:id
|
||||
|
||||
GET /api/v1/admin/inventory
|
||||
POST /api/v1/admin/inventory
|
||||
GET /api/v1/admin/inventory/:id
|
||||
PATCH /api/v1/admin/inventory/:id
|
||||
POST /api/v1/admin/inventory/:id/list
|
||||
POST /api/v1/admin/inventory/:id/archive
|
||||
POST /api/v1/admin/inventory/:id/relist
|
||||
POST /api/v1/admin/inventory/:id/pricing/refresh
|
||||
|
||||
GET /api/v1/admin/listings
|
||||
GET /api/v1/admin/listings/:id
|
||||
PATCH /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
|
||||
|
||||
POST /api/v1/admin/pricing/refresh
|
||||
GET /api/v1/admin/jobs/:id
|
||||
|
||||
POST /api/v1/admin/inventory/:id/images
|
||||
PATCH /api/v1/admin/inventory/:id/images/order
|
||||
POST /api/v1/admin/images/:id/primary
|
||||
DELETE /api/v1/admin/images/:id
|
||||
|
||||
POST /api/v1/admin/sales
|
||||
POST /api/v1/admin/bulk
|
||||
|
||||
GET /api/v1/admin/dashboard
|
||||
|
||||
GET /api/v1/admin/saved-views
|
||||
POST /api/v1/admin/saved-views
|
||||
PATCH /api/v1/admin/saved-views/:id
|
||||
DELETE /api/v1/admin/saved-views/:id
|
||||
```
|
||||
|
||||
Listing creation and lifecycle writes require an authenticated admin session and
|
||||
the session-bound CSRF header. Publishing is seller-scoped and checks the
|
||||
persisted product, category, condition, asking price, physical inventory, and
|
||||
image prerequisites before changing a draft to `AVAILABLE`; sold and archived
|
||||
physical inventory cannot be published. Unpublish changes
|
||||
an available listing to `CLOSED`; pending changes are available from draft or
|
||||
available listings.
|
||||
|
||||
`POST /api/v1/admin/inventory/:id/images` stores validated image metadata for an
|
||||
owned physical inventory item. File bytes are handled by the future
|
||||
`ImageStorage` adapter; publication counts persisted inventory images rather
|
||||
than trusting a listing request field.
|
||||
|
||||
## Provider matching
|
||||
|
||||
The API must expose Product-level operations for:
|
||||
- searching configured providers for candidate matches
|
||||
- confirming a provider mapping
|
||||
- manually searching/refining a provider query
|
||||
- marking a provider not applicable
|
||||
- disabling/replacing a mapping
|
||||
|
||||
Exact route naming may be finalized during API implementation, but these operations must remain Product-scoped.
|
||||
|
||||
## Validation
|
||||
|
||||
Validate body, query, path, and response schemas.
|
||||
|
||||
Business validation occurs after request-shape validation.
|
||||
|
||||
Allowlist sortable fields.
|
||||
|
||||
## Errors
|
||||
|
||||
Use machine-readable codes, including:
|
||||
- `PRODUCT_NOT_FOUND`
|
||||
- `INVENTORY_NOT_FOUND`
|
||||
- `INVENTORY_EDIT_LOCKED`
|
||||
- `INVALID_STATUS_TRANSITION`
|
||||
- `LISTING_REQUIREMENTS_NOT_MET`
|
||||
- `SALE_PRICE_REQUIRED`
|
||||
- `SALE_ITEM_NOT_AVAILABLE`
|
||||
- `PRICING_PROVIDER_FAILED`
|
||||
- `IMAGE_LIMIT_EXCEEDED`
|
||||
- `UNAUTHORIZED`
|
||||
- `FORBIDDEN`
|
||||
|
||||
## Privacy
|
||||
|
||||
Public DTOs must never expose private acquisition, sale, buyer, internal, provider-debugging, pricing-history, or audit information.
|
||||
|
||||
## Pagination and query state
|
||||
|
||||
Large collections use pagination. Public/admin filters and searches should be represented in URL query state where useful for shareability, refresh, and back/forward navigation.
|
||||
@@ -0,0 +1,87 @@
|
||||
# Architecture
|
||||
|
||||
## Architectural style
|
||||
|
||||
Modular monolith.
|
||||
|
||||
Do not split domains into microservices unless a future architecture decision explicitly requires it.
|
||||
|
||||
## Runtime topology
|
||||
|
||||
```text
|
||||
Public Web
|
||||
↓ HTTPS
|
||||
Reverse Proxy
|
||||
↓
|
||||
Fastify API ───── PostgreSQL
|
||||
│
|
||||
├────────────── Redis / BullMQ
|
||||
│ ↓
|
||||
│ Worker
|
||||
│
|
||||
└────────────── Image Storage
|
||||
```
|
||||
|
||||
Docker Compose is the baseline deployment mechanism.
|
||||
|
||||
## Applications
|
||||
|
||||
### web
|
||||
Vue 3/Vite public and admin frontend.
|
||||
|
||||
### api
|
||||
Fastify REST API. Owns authentication, authorization, domain use cases, persistence, and synchronous integrations.
|
||||
|
||||
### worker
|
||||
Processes asynchronous jobs including bulk pricing, image processing, and large exports.
|
||||
|
||||
## Modules
|
||||
|
||||
API modules:
|
||||
- auth
|
||||
- users
|
||||
- sellers
|
||||
- products
|
||||
- inventory
|
||||
- listings
|
||||
- pricing
|
||||
- sales
|
||||
- images
|
||||
- search
|
||||
- settings
|
||||
- audit
|
||||
|
||||
## Layering
|
||||
|
||||
`Vue Component → Composable → API Service → HTTP → Fastify Route → Validation → Application Service → Domain Logic → Repository → PostgreSQL`
|
||||
|
||||
## External boundaries
|
||||
|
||||
Pricing providers implement `PricingProvider`.
|
||||
|
||||
Image storage implements `ImageStorage`.
|
||||
|
||||
Search is accessed through `SearchService`.
|
||||
|
||||
Currency conversion is accessed through a currency/exchange-rate service.
|
||||
|
||||
## Shared packages
|
||||
|
||||
Only genuinely shared material belongs in `packages/*`:
|
||||
- API contracts
|
||||
- validation schemas
|
||||
- safe utilities
|
||||
|
||||
Server-side domain logic stays on the server.
|
||||
|
||||
## Search
|
||||
|
||||
PostgreSQL-backed initially. Keep search behind an abstraction so OpenSearch/Elasticsearch can be introduced later without changing product-facing behavior.
|
||||
|
||||
## Authentication
|
||||
|
||||
Use server-side sessions with secure HttpOnly SameSite cookies. Authentication tokens must not be stored in localStorage.
|
||||
|
||||
## Caching
|
||||
|
||||
Use selective caching for stable/repeated data such as exchange rates, public metadata, and configuration. Avoid aggressive caching of mutations, financial calculations, auth state, and sale state.
|
||||
@@ -0,0 +1,201 @@
|
||||
# Data Model
|
||||
|
||||
## Core relationship
|
||||
|
||||
```text
|
||||
User → Seller → Product → Inventory Item → Listing
|
||||
│
|
||||
├── Images
|
||||
├── Acquisition
|
||||
├── Pricing Observations
|
||||
├── Market Valuations
|
||||
└── Activity
|
||||
|
||||
Sale → Sale Items → Inventory Items
|
||||
```
|
||||
|
||||
## Product
|
||||
|
||||
Represents what the item is, independent of physical ownership.
|
||||
|
||||
Examples:
|
||||
- Charizard ex — SV151 — #199/165
|
||||
- Pokémon Center Worlds 2025 Pikachu Plush
|
||||
|
||||
Products persist after all inventory is sold.
|
||||
|
||||
## Inventory Item
|
||||
|
||||
Represents one physical copy.
|
||||
|
||||
Each copy has its own:
|
||||
- condition
|
||||
- images
|
||||
- acquisition
|
||||
- asking price/listing
|
||||
- market valuation
|
||||
- notes
|
||||
- status
|
||||
|
||||
Do not model multiple physical copies as a quantity field on one Inventory Item.
|
||||
|
||||
## Listing
|
||||
|
||||
Represents how an Inventory Item is currently offered publicly.
|
||||
|
||||
A Product can have many physical Inventory Items with different conditions and asking prices.
|
||||
|
||||
## Sale
|
||||
|
||||
A manual external transaction. One Sale can contain multiple Sale Items.
|
||||
|
||||
## Provider Product Mapping
|
||||
|
||||
Provider mappings are Product-level relationships.
|
||||
|
||||
Recommended fields:
|
||||
- id
|
||||
- product_id
|
||||
- provider_id
|
||||
- mapping_type (`PRODUCT` or `SEARCH`)
|
||||
- external_identifier
|
||||
- search_query / search parameters
|
||||
- source_url
|
||||
- status
|
||||
- metadata
|
||||
- created_at
|
||||
- updated_at
|
||||
- disabled_at
|
||||
|
||||
A provider can support one or both mapping types.
|
||||
|
||||
eBay supports both:
|
||||
- specific product/catalog mappings where appropriate
|
||||
- search/query mappings where no canonical product is appropriate
|
||||
|
||||
Mappings can be replaced without destroying historical records.
|
||||
|
||||
Only one active/default mapping per provider should be used for normal pricing unless a provider integration explicitly supports multiple active sources.
|
||||
|
||||
## Suggested pricing tables
|
||||
|
||||
### pricing_providers
|
||||
`id, name, code, is_enabled, priority, configuration, created_at, updated_at`
|
||||
|
||||
### product_pricing_providers
|
||||
`product_id, provider_id, is_enabled`
|
||||
|
||||
### pricing_provider_mappings
|
||||
As described above.
|
||||
|
||||
### pricing_observations
|
||||
`id, product_id, inventory_item_id, provider_id, source_identifier, source_url, condition_id, price_minor, currency, observed_at, quality_score, is_outlier, outlier_reason, raw_reference`
|
||||
|
||||
### market_valuations
|
||||
`id, inventory_item_id, value_minor, currency, strategy, confidence, source_count, calculated_at`
|
||||
|
||||
### market_value_overrides
|
||||
`id, inventory_item_id, value_minor, currency, reason, created_by, created_at, removed_at`
|
||||
|
||||
### pricing_jobs
|
||||
`id, job_type, status, requested_by, started_at, completed_at, total_items, processed_items, successful_items, failed_items, error_summary, created_at`
|
||||
|
||||
## Conditions
|
||||
|
||||
`condition_profiles`:
|
||||
- id
|
||||
- category_id
|
||||
- name
|
||||
- active
|
||||
- sort
|
||||
|
||||
`conditions`:
|
||||
- id
|
||||
- condition_profile_id
|
||||
- name
|
||||
- code
|
||||
- quality_rank
|
||||
- description
|
||||
- active
|
||||
- sort
|
||||
|
||||
## Images
|
||||
|
||||
`inventory_images`:
|
||||
- id
|
||||
- inventory_item_id
|
||||
- storage_key
|
||||
- original_filename
|
||||
- mime_type
|
||||
- file_size
|
||||
- width
|
||||
- height
|
||||
- alt_text
|
||||
- sort_order
|
||||
- is_primary
|
||||
- processing_status
|
||||
- created_at
|
||||
- updated_at
|
||||
|
||||
## Acquisitions
|
||||
|
||||
`acquisitions`:
|
||||
- id
|
||||
- inventory_item_id
|
||||
- method
|
||||
- cost_minor
|
||||
- currency
|
||||
- date
|
||||
- source
|
||||
- location
|
||||
- shipping_minor
|
||||
- tax_minor
|
||||
- other_fees_minor
|
||||
- estimated_trade_value_minor
|
||||
- notes
|
||||
- created_at
|
||||
- updated_at
|
||||
|
||||
## Sales
|
||||
|
||||
`sales`:
|
||||
- id
|
||||
- seller_id
|
||||
- sale_date
|
||||
- sale_location
|
||||
- sold_price_minor
|
||||
- currency
|
||||
- platform_fees_minor
|
||||
- payment_processing_fees_minor
|
||||
- shipping_cost_minor
|
||||
- taxes_minor
|
||||
- other_fees_minor
|
||||
- buyer_name
|
||||
- buyer_contact
|
||||
- notes
|
||||
- created_by
|
||||
- created_at
|
||||
- updated_at
|
||||
|
||||
`sale_items`:
|
||||
- id
|
||||
- sale_id
|
||||
- inventory_item_id
|
||||
- sold_price_minor
|
||||
- cost_basis_minor
|
||||
|
||||
## Activity
|
||||
|
||||
Activity records retain:
|
||||
- affected Product/Inventory Item
|
||||
- event
|
||||
- previous value where applicable
|
||||
- new value
|
||||
- actor
|
||||
- timestamp
|
||||
|
||||
Security events are stored separately.
|
||||
|
||||
## Money
|
||||
|
||||
Store money as integer minor units. Negative values are prohibited; zero is valid. USD is canonical for stored/calculated market values unless a documented external representation requires another currency.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Architecture Decision: Provider Product Mapping
|
||||
|
||||
## Status
|
||||
Accepted
|
||||
|
||||
## Decision
|
||||
Provider mappings are first-class Product-level records.
|
||||
|
||||
During new Product creation, configured pricing providers are searched for candidate matches. The admin confirms the appropriate match, refines a search manually when necessary, or marks a provider Not Applicable.
|
||||
|
||||
Mappings are reused by all physical Inventory Items belonging to the Product.
|
||||
|
||||
## Mapping types
|
||||
|
||||
- `PRODUCT`: specific canonical provider/catalog entity.
|
||||
- `SEARCH`: provider search/query definition.
|
||||
|
||||
eBay supports both mapping types.
|
||||
|
||||
## Rationale
|
||||
|
||||
A provider relationship describes the identity of the Product, not a particular physical copy. Keeping it at Product level avoids repeating provider discovery for every copy while supporting multiple conditions and future copies.
|
||||
|
||||
A search mapping is necessary for providers such as eBay where a canonical product may not exist for every collectible or merchandise item.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Deployment
|
||||
|
||||
## Environments
|
||||
|
||||
- development
|
||||
- test/CI
|
||||
- production
|
||||
|
||||
## Docker Compose
|
||||
|
||||
Baseline services:
|
||||
- frontend
|
||||
- API
|
||||
- worker
|
||||
- PostgreSQL
|
||||
- Redis
|
||||
- reverse proxy
|
||||
|
||||
Persistent data:
|
||||
- PostgreSQL data
|
||||
- Redis data where needed
|
||||
- image storage
|
||||
|
||||
## Reverse proxy
|
||||
|
||||
Responsible for:
|
||||
- HTTPS/TLS
|
||||
- routing
|
||||
- compression
|
||||
- security headers
|
||||
- rate limiting where appropriate
|
||||
|
||||
## Configuration
|
||||
|
||||
Environment variables/secrets:
|
||||
- database connection
|
||||
- Redis connection
|
||||
- authentication/session secrets
|
||||
- provider credentials
|
||||
- email credentials
|
||||
- storage credentials
|
||||
|
||||
Application settings such as pricing strategy and site settings belong in the database.
|
||||
|
||||
## Backups
|
||||
|
||||
Back up:
|
||||
- PostgreSQL
|
||||
- image storage
|
||||
- required environment/configuration information
|
||||
|
||||
Retention policy and restore procedures must be documented for production.
|
||||
|
||||
Disaster recovery should allow reconstruction elsewhere from database backup, image backup, and deployment/environment configuration.
|
||||
|
||||
## Future hosting
|
||||
|
||||
Initial deployment targets a home server/NAS. The architecture should allow later movement to an online server or managed infrastructure without redesigning the domain model.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Design System Direction
|
||||
|
||||
## Principles
|
||||
|
||||
- Minimal
|
||||
- Clean
|
||||
- Information-dense without clutter
|
||||
- Accessible
|
||||
- Responsive
|
||||
- Consistent
|
||||
- Component-driven
|
||||
|
||||
## Component architecture
|
||||
|
||||
Build atomic/reusable Vue components.
|
||||
|
||||
Examples:
|
||||
- SearchBar
|
||||
- ProductCard
|
||||
- InventoryGroup
|
||||
- InventoryCopyRow
|
||||
- PriceDisplay
|
||||
- ConditionBadge
|
||||
- StatusBadge
|
||||
- ImageGallery
|
||||
- FilterPanel
|
||||
- SortControl
|
||||
- Modal/Dialog
|
||||
- FormField
|
||||
- ValidationMessage
|
||||
- DataTable
|
||||
- EmptyState
|
||||
- LoadingState
|
||||
- ErrorState
|
||||
- ActivityTimeline
|
||||
|
||||
Components should have clear responsibilities and avoid embedding domain business rules.
|
||||
|
||||
## Visual decisions
|
||||
|
||||
Exact color palette, typography, spacing tokens, and final visual styling can evolve during implementation, but must preserve accessibility and the documented information hierarchy.
|
||||
|
||||
No color-only indication of state or condition.
|
||||
@@ -0,0 +1,95 @@
|
||||
# Image Management
|
||||
|
||||
## Requirements
|
||||
|
||||
Multiple images are core functionality.
|
||||
|
||||
Use cases include:
|
||||
- card front/back
|
||||
- corners
|
||||
- edges/whitening
|
||||
- holographic details
|
||||
- merchandise angles
|
||||
- defects/details
|
||||
|
||||
## Workflow
|
||||
|
||||
`Upload → Validate → Store Original → Queue Processing → Generate Derivatives → Complete`
|
||||
|
||||
Support:
|
||||
- multiple-file upload
|
||||
- drag/drop
|
||||
- preview
|
||||
- reorder
|
||||
- primary image
|
||||
- delete
|
||||
- add images later
|
||||
|
||||
Image count is configurable.
|
||||
|
||||
## Storage
|
||||
|
||||
Images are not stored in PostgreSQL.
|
||||
|
||||
Use an `ImageStorage` abstraction.
|
||||
|
||||
Initial:
|
||||
- Docker-mounted local storage
|
||||
|
||||
Future:
|
||||
- S3-compatible object storage
|
||||
|
||||
The API uses an `ImageStorage` contract with `put`, `get`, and `delete`
|
||||
operations. The current local adapter rejects absolute and traversal paths and
|
||||
does not overwrite an existing key. It stores bytes outside PostgreSQL; image
|
||||
metadata remains in `inventory_images`.
|
||||
|
||||
## Processing
|
||||
|
||||
Validate:
|
||||
- MIME type
|
||||
- file signature where practical
|
||||
- size
|
||||
- image dimensions
|
||||
- configured image count
|
||||
|
||||
Generate:
|
||||
- thumbnail
|
||||
- medium
|
||||
- large/responsive derivatives
|
||||
|
||||
Modern formats such as WebP/AVIF may be used where appropriate.
|
||||
|
||||
Retain originals where practical.
|
||||
|
||||
## Metadata
|
||||
|
||||
Store:
|
||||
- storage key
|
||||
- original filename
|
||||
- MIME type
|
||||
- file size
|
||||
- dimensions
|
||||
- alt text
|
||||
- order
|
||||
- primary flag
|
||||
- processing status
|
||||
- timestamps
|
||||
|
||||
## Alt text
|
||||
|
||||
Generate an initial suggestion automatically, but require the seller to be able to review/edit it.
|
||||
|
||||
Do not invent details not visible or supplied by the seller.
|
||||
|
||||
## Accessibility
|
||||
|
||||
Public galleries must support:
|
||||
- keyboard operation
|
||||
- accessible controls
|
||||
- visible focus
|
||||
- meaningful names
|
||||
- focus management
|
||||
- screen-reader-friendly state
|
||||
|
||||
Primary images power cards, search results, homepage previews, and social/share metadata.
|
||||
@@ -0,0 +1,156 @@
|
||||
# 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.
|
||||
- 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` and 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.
|
||||
|
||||
1. Add upload transport, MIME/signature/size validation, and processing/derivative lifecycle for persisted image metadata.
|
||||
2. Remove the temporary listing image-count snapshot after storage-backed image counts are fully established.
|
||||
3. 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 and removal of the legacy listing snapshot remain.
|
||||
- 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.`
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
# Pricing Architecture
|
||||
|
||||
## Goals
|
||||
|
||||
Provide a defensible calculated market value while retaining provider provenance and uncertainty.
|
||||
|
||||
## Provider abstraction
|
||||
|
||||
All providers implement a common conceptual interface:
|
||||
|
||||
`PricingProvider`
|
||||
|
||||
A provider returns normalized observations containing:
|
||||
- provider/source
|
||||
- Product or Inventory Item context
|
||||
- price
|
||||
- currency
|
||||
- condition
|
||||
- timestamp
|
||||
- source URL/identifier
|
||||
- quality/confidence information
|
||||
|
||||
Provider adapters own provider-specific API behavior and normalization.
|
||||
|
||||
Use officially supported APIs or permitted sources. Do not invent APIs or bypass restrictions.
|
||||
|
||||
## Provider Product Mapping
|
||||
|
||||
Mapping occurs during new Product creation.
|
||||
|
||||
Workflow:
|
||||
|
||||
`Create Product → Search Providers → Candidate Matches → Admin Confirms Mapping`
|
||||
|
||||
The admin can also search/refine manually.
|
||||
|
||||
Mappings are persistent Product-level records and are reused for future physical copies.
|
||||
|
||||
Provider mapping is not required.
|
||||
|
||||
A provider can be:
|
||||
- linked to a canonical product
|
||||
- linked to a search/query
|
||||
- marked Not Applicable
|
||||
- left unlinked
|
||||
|
||||
### Mapping types
|
||||
|
||||
`PRODUCT`
|
||||
- specific provider/catalog entity
|
||||
|
||||
`SEARCH`
|
||||
- provider search/query definition
|
||||
|
||||
eBay supports both.
|
||||
|
||||
## Pricing pipeline
|
||||
|
||||
```text
|
||||
Raw Provider Data
|
||||
↓
|
||||
Normalized Observations
|
||||
↓
|
||||
Quality / Confidence Analysis
|
||||
↓
|
||||
Valuation Strategy
|
||||
↓
|
||||
Calculated Market Value
|
||||
↓
|
||||
Optional Manual Override
|
||||
↓
|
||||
Display Currency Conversion
|
||||
```
|
||||
|
||||
## Valuation
|
||||
|
||||
Default strategy: median.
|
||||
|
||||
Supported strategies should include:
|
||||
- average
|
||||
- median
|
||||
- weighted average
|
||||
|
||||
The strategy is configurable.
|
||||
|
||||
Outliers remain in the underlying observations and are flagged with a reason. The valuation engine may account for outlier status according to the selected strategy, but raw observations are not silently discarded.
|
||||
|
||||
One valid source can produce a market value with low confidence.
|
||||
|
||||
No reliable condition-specific value means unavailable; never fabricate a value.
|
||||
|
||||
## Refresh
|
||||
|
||||
Refresh:
|
||||
- one Inventory Item
|
||||
- Product-level copies
|
||||
- bulk selected inventory
|
||||
|
||||
Bulk refresh runs through BullMQ in the worker.
|
||||
|
||||
Provider failure should not invalidate other valid observations. The failed provider is recorded for admin diagnostics.
|
||||
|
||||
## Overrides
|
||||
|
||||
Admin can manually override market value.
|
||||
|
||||
Override requires:
|
||||
- value
|
||||
- currency
|
||||
- private reason
|
||||
- actor
|
||||
- timestamp
|
||||
|
||||
Overrides are never exposed publicly as provider-level information.
|
||||
|
||||
## Currency
|
||||
|
||||
Canonical stored/calculated market values use USD.
|
||||
|
||||
Visitor currency conversion occurs at presentation time using cached exchange rates.
|
||||
|
||||
The original USD value remains authoritative.
|
||||
|
||||
## Staleness
|
||||
|
||||
Pricing freshness is configurable. Dashboard and admin inventory can identify stale values. The exact default threshold is a configuration decision during implementation, not a hard-coded domain invariant.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Product Definition
|
||||
|
||||
## Purpose
|
||||
|
||||
Provide a personal catalog for Pokémon products the seller owns, allowing the seller to maintain inventory, understand market pricing, publish asking prices, and record completed external sales.
|
||||
|
||||
## Primary users
|
||||
|
||||
### Public visitor
|
||||
Can search and browse available inventory, inspect item details, view calculated market value and asking price, and contact the seller.
|
||||
|
||||
### Admin/seller
|
||||
Can manage products, physical inventory, listings, images, pricing, acquisitions, sales, settings, and history.
|
||||
|
||||
MVP has one seller/admin. The model is multi-seller ready.
|
||||
|
||||
## Non-goals
|
||||
|
||||
The application does not provide:
|
||||
- shopping cart
|
||||
- checkout
|
||||
- payment processing
|
||||
- shipping fulfillment
|
||||
- customer accounts
|
||||
- order management
|
||||
- in-app e-commerce transactions
|
||||
|
||||
## Product principles
|
||||
|
||||
1. Inventory accuracy over convenience.
|
||||
2. Preserve history.
|
||||
3. Keep private seller data private.
|
||||
4. Automate repetitive pricing work without hiding its provenance.
|
||||
5. Allow unusual products that have no provider match.
|
||||
6. Prefer reusable domain services over duplicated behavior.
|
||||
7. Accessibility is a product requirement, not a later enhancement.
|
||||
@@ -0,0 +1,176 @@
|
||||
# Functional Requirements
|
||||
|
||||
## Public
|
||||
|
||||
### Homepage
|
||||
- Minimal, search-first landing page.
|
||||
- Search updates as the visitor types.
|
||||
- Recently added available items appear below search.
|
||||
- No-match state: `No relevant matches found.`
|
||||
|
||||
### Search
|
||||
Search may include product name, Pokémon name, set, card number, category, description, event, year, brand, and tags.
|
||||
|
||||
Public search must use the same underlying search abstraction as admin search while respecting public visibility.
|
||||
|
||||
### Inventory
|
||||
Filters include status, newly added, category, set, condition, price range, and event. Available filter values are derived from current data where appropriate.
|
||||
|
||||
Sorting includes:
|
||||
- Recently Added
|
||||
- Recently Updated
|
||||
- Name A–Z
|
||||
- Name Z–A
|
||||
- Price Low→High
|
||||
- Price High→Low
|
||||
- Market Value Low→High
|
||||
- Market Value High→Low
|
||||
|
||||
Sold is hidden by default but can be included through an explicit public filter/search.
|
||||
|
||||
Pending listings are public and clearly marked.
|
||||
|
||||
### Item details
|
||||
Show images, title, product details, condition, asking price, market value where available, market update time, seller description, and configured seller contact methods.
|
||||
|
||||
Public pages have stable, shareable URLs.
|
||||
|
||||
## Inventory creation
|
||||
|
||||
Guided flow:
|
||||
|
||||
`Find/Create Product → Link Market Sources → Inventory Details → Images → Pricing → Review → List`
|
||||
|
||||
Required for an item to become Available:
|
||||
- Product name
|
||||
- Category
|
||||
- Asking price
|
||||
- Images
|
||||
- Physical inventory record
|
||||
- Condition
|
||||
|
||||
Provider matching is not mandatory.
|
||||
|
||||
### Existing Product
|
||||
Admin can add another physical copy, reuse prior item data as a starting point, or create a new physical record after a previous copy sold.
|
||||
|
||||
### Provider matching
|
||||
When creating a new Product, the system searches applicable configured providers and presents candidate matches. Admin can refine/search manually and confirm a mapping.
|
||||
|
||||
Mappings belong to Products and are reused by physical copies.
|
||||
|
||||
A provider may be marked Not Applicable. A Product may exist without any provider mapping.
|
||||
|
||||
## Inventory lifecycle
|
||||
|
||||
Inventory status:
|
||||
- `ACTIVE`
|
||||
- `SOLD`
|
||||
- `ARCHIVED`
|
||||
|
||||
Listing status:
|
||||
- `DRAFT`
|
||||
- `AVAILABLE`
|
||||
- `PENDING`
|
||||
- `CLOSED`
|
||||
- `ARCHIVED`
|
||||
|
||||
Owned items may have no active listing and may be designated for sale, personal collection, display, or other purposes.
|
||||
|
||||
Pending and sold items are locked against ordinary edits. Controlled corrections remain possible with audit history.
|
||||
|
||||
Relisting creates a new physical Inventory Item rather than reopening the sold physical record.
|
||||
|
||||
## Conditions
|
||||
|
||||
Card profile:
|
||||
- NM
|
||||
- LP
|
||||
- MP
|
||||
- HP
|
||||
- Damaged
|
||||
|
||||
Merchandise profile:
|
||||
- New
|
||||
- Like New
|
||||
- Used
|
||||
- Damaged
|
||||
|
||||
Profiles and labels are configurable.
|
||||
|
||||
## Pricing
|
||||
|
||||
- Multiple providers supported.
|
||||
- Median is the default valuation strategy.
|
||||
- Outliers are retained and flagged.
|
||||
- One valid source may produce a low-confidence value.
|
||||
- Manual market-value overrides require a private reason.
|
||||
- Provider errors are retained for admin visibility.
|
||||
- Public sees only calculated/overridden market value, not source-level observations.
|
||||
- Canonical currency is USD.
|
||||
- Condition-specific values are used only when reliable data exists.
|
||||
- No fabricated values.
|
||||
- Pricing can refresh by item, Product, or bulk.
|
||||
- Bulk refresh is asynchronous.
|
||||
|
||||
## Images
|
||||
|
||||
- Multiple images per physical item.
|
||||
- Multiple-file upload and drag/drop.
|
||||
- Reorder, primary selection, deletion, and later additions.
|
||||
- Configurable image count limit.
|
||||
- Automatic validation and optimization.
|
||||
- Original retained where practical.
|
||||
- Responsive derivatives generated.
|
||||
- Seller can edit generated alt-text suggestions.
|
||||
|
||||
## Acquisition
|
||||
|
||||
Private acquisition information includes cost, date, source, location, shipping, tax, fees, trade value, and notes.
|
||||
|
||||
Total cost basis is calculated from component costs.
|
||||
|
||||
## Sales
|
||||
|
||||
Record Sale requires sold price and supports date, location/platform, fees, shipping, taxes, buyer information, and notes.
|
||||
|
||||
Multiple inventory items may belong to one sale.
|
||||
|
||||
Sale correction/undo is controlled and audited.
|
||||
|
||||
## Bulk operations
|
||||
|
||||
Supported:
|
||||
- status change
|
||||
- archive
|
||||
- pricing refresh
|
||||
- tags
|
||||
|
||||
Mark Sold uses the dedicated sale workflow and cannot bypass sold-price validation.
|
||||
|
||||
## Dashboard
|
||||
|
||||
Dashboard should meaningfully present:
|
||||
- inventory count
|
||||
- available listings
|
||||
- sold count
|
||||
- total acquisition cost
|
||||
- total asking value
|
||||
- total market value
|
||||
- potential profit
|
||||
- recently added items
|
||||
- stale pricing
|
||||
- missing images
|
||||
- missing pricing
|
||||
- provider errors
|
||||
- recent activity
|
||||
|
||||
Actionable alerts link to filtered management views.
|
||||
|
||||
## Exports
|
||||
|
||||
Admin-only CSV, Excel, and JSON exports. Large exports may run asynchronously.
|
||||
|
||||
## Settings
|
||||
|
||||
General, inventory, pricing, and security settings are configurable as described in the supporting documentation.
|
||||
@@ -0,0 +1,95 @@
|
||||
# Security
|
||||
|
||||
## Authentication
|
||||
|
||||
MVP:
|
||||
- email/password
|
||||
- MFA using TOTP or passkey-capable architecture
|
||||
- password recovery
|
||||
- remember-me option
|
||||
- configurable session expiry
|
||||
- active session/device management
|
||||
- revoke one or all sessions
|
||||
|
||||
Use secure server-side sessions with:
|
||||
- HttpOnly
|
||||
- Secure in production
|
||||
- appropriate SameSite policy
|
||||
|
||||
Never store auth tokens in localStorage.
|
||||
|
||||
## Authorization
|
||||
|
||||
MVP has Admin role.
|
||||
|
||||
Design authorization so future roles can be added without rewriting domain logic.
|
||||
|
||||
All authorization is enforced server-side.
|
||||
|
||||
## CSRF
|
||||
|
||||
Implement an appropriate CSRF defense for cookie-based authentication.
|
||||
|
||||
## Input and API security
|
||||
|
||||
- Validate all inputs.
|
||||
- Parameterize database queries through Drizzle.
|
||||
- Allowlist sort fields.
|
||||
- Apply pagination.
|
||||
- Rate-limit sensitive/auth endpoints.
|
||||
- Use security headers.
|
||||
- Do not leak private fields through serialization.
|
||||
|
||||
## Upload security
|
||||
|
||||
- Validate MIME type and file signatures where practical.
|
||||
- Limit size and dimensions.
|
||||
- Limit image count.
|
||||
- Process in controlled jobs.
|
||||
- Treat filenames as untrusted.
|
||||
- Do not execute uploaded content.
|
||||
|
||||
## Sessions
|
||||
|
||||
Suggested fields:
|
||||
`id, user_id, session_token_hash, created_at, expires_at, last_activity_at, ip_address, user_agent, revoked_at`
|
||||
|
||||
## MFA
|
||||
|
||||
Suggested fields:
|
||||
`id, user_id, type, secret_reference, name, is_enabled, created_at, last_used_at`
|
||||
|
||||
Secrets must be stored using appropriate secret protection; do not expose raw MFA secrets in logs.
|
||||
|
||||
## Security audit log
|
||||
|
||||
Separate from inventory activity.
|
||||
|
||||
Events include:
|
||||
- LOGIN_SUCCESS
|
||||
- LOGIN_FAILURE
|
||||
- PASSWORD_RESET
|
||||
- MFA_ENROLLED
|
||||
- MFA_FAILURE
|
||||
- SESSION_CREATED
|
||||
- SESSION_REVOKED
|
||||
- ACCOUNT_UPDATED
|
||||
|
||||
## Secrets
|
||||
|
||||
Credentials, API keys, database URLs, and encryption secrets belong in environment/secret management, never in source control.
|
||||
|
||||
## Privacy boundary
|
||||
|
||||
Private:
|
||||
- acquisition data
|
||||
- sale data
|
||||
- buyer information
|
||||
- profit
|
||||
- internal notes
|
||||
- inventory IDs
|
||||
- provider observations and errors
|
||||
- pricing methodology/confidence/history
|
||||
- activity/audit logs
|
||||
|
||||
Public data must be exposed through purpose-built DTOs.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Testing Strategy
|
||||
|
||||
Testing begins with the first implementation.
|
||||
|
||||
## Unit
|
||||
|
||||
Test domain rules and pure services:
|
||||
- status transitions
|
||||
- listing requirements
|
||||
- cost basis
|
||||
- profit
|
||||
- valuation strategies
|
||||
- outlier handling
|
||||
- condition rules
|
||||
|
||||
## API / integration
|
||||
|
||||
Test:
|
||||
- authentication
|
||||
- authorization
|
||||
- repositories
|
||||
- API validation
|
||||
- Product/provider mapping
|
||||
- inventory lifecycle
|
||||
- sales transactions
|
||||
- public/private DTO boundaries
|
||||
|
||||
## Component
|
||||
|
||||
Test important Vue components and composables.
|
||||
|
||||
## E2E
|
||||
|
||||
Core workflows:
|
||||
1. Login
|
||||
2. Add Product
|
||||
3. Find/link provider mappings
|
||||
4. Add Inventory Item
|
||||
5. Upload images
|
||||
6. Calculate pricing
|
||||
7. Publish listing
|
||||
8. Search public site
|
||||
9. Record sale
|
||||
10. View dashboard
|
||||
|
||||
## Accessibility
|
||||
|
||||
Run automated accessibility checks and maintain manual keyboard/assistive technology coverage for critical workflows.
|
||||
|
||||
## CI
|
||||
|
||||
GitHub Actions should run:
|
||||
- linting
|
||||
- unit tests
|
||||
- integration/API tests
|
||||
- component tests
|
||||
- E2E tests as appropriate
|
||||
- accessibility checks
|
||||
- build validation
|
||||
|
||||
Do not weaken or skip tests to obtain a passing build.
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
# UX Requirements
|
||||
|
||||
## Design direction
|
||||
|
||||
Clean, minimal, catalog-focused, and responsive.
|
||||
|
||||
The homepage is intentionally search-first and visually similar in simplicity to a search engine landing page.
|
||||
|
||||
## Public information hierarchy
|
||||
|
||||
For cards:
|
||||
1. Primary image
|
||||
2. Product/item name
|
||||
3. Market value
|
||||
4. Asking price
|
||||
|
||||
The asking price is the primary seller decision value; market value provides context.
|
||||
|
||||
## Product grouping
|
||||
|
||||
Multiple physical copies of one Product are grouped together.
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
Charizard ex #199/165
|
||||
Near Mint $115 asking $120 market
|
||||
Lightly Played $95 asking $90 market
|
||||
+ 1 More Available
|
||||
```
|
||||
|
||||
Copies are sorted by condition quality, best first.
|
||||
|
||||
Selecting the group can reveal all copies.
|
||||
|
||||
Do not create separate Product groups solely because condition differs.
|
||||
|
||||
## Inventory management
|
||||
|
||||
Admin uses search plus filters and saved views.
|
||||
|
||||
Recommended saved views:
|
||||
- Needs Attention
|
||||
- Recently Added
|
||||
- High Value
|
||||
- Sold This Month
|
||||
|
||||
Bulk selection should be available where safe.
|
||||
|
||||
## Guided creation
|
||||
|
||||
`Find/Create Product → Link Market Sources → Inventory Details → Images → Pricing → Review → List`
|
||||
|
||||
The workflow should make it clear whether the user is editing the Product or the physical Inventory Item.
|
||||
|
||||
## Locked states
|
||||
|
||||
Pending and Sold items are visibly locked against ordinary edits. Controlled actions such as correcting a sale remain available through explicit workflows.
|
||||
|
||||
## Public sold items
|
||||
|
||||
Sold items show Sold status and no asking price.
|
||||
|
||||
## Responsive behavior
|
||||
|
||||
Support desktop, tablet, and mobile. Layouts should adapt rather than simply shrink desktop controls.
|
||||
|
||||
## Feedback
|
||||
|
||||
Dynamic actions must provide accessible status feedback, including:
|
||||
- loading
|
||||
- success
|
||||
- validation error
|
||||
- provider failure
|
||||
- image processing
|
||||
- background job progress
|
||||
|
||||
Avoid relying on color alone.
|
||||
Reference in New Issue
Block a user