Files
web_sport/README.md
T

257 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sport Store
A modern sports-fashion e-commerce platform. Built entirely in code — no WordPress, no
WooCommerce, no Shopify, no CMS platform underneath.
**Status: milestone 0 — architecture skeleton.** The structure, boundaries, data model and
tooling are in place and verified. Business features are not implemented yet; see
[Roadmap](#roadmap).
```
Storefront (Next.js) ─┐
├─▶ REST API (NestJS) ─▶ PostgreSQL · Redis · R2/S3
Admin (Next.js) ──────┘
```
---
## Quick start
Requires **Node ≥ 22**, **pnpm ≥ 10** (`corepack enable`) and **Docker**.
```bash
pnpm setup # env files, real JWT secrets, deps, containers, migrations, seed
pnpm dev # storefront + admin + API, all watching
```
| Service | URL |
| ------------------ | --------------------------------------------------- |
| Storefront | http://localhost:3000 |
| Admin | http://localhost:3001 |
| API | http://localhost:4000/api/v1/health |
| API docs (Swagger) | http://localhost:4000/docs |
| MinIO console | http://localhost:9001 (`sportminio` / `sportminio`) |
| Mailpit | http://localhost:8025 |
Verify the stack is healthy:
```bash
curl -s http://localhost:4000/api/v1/health | jq
```
```json
{
"success": true,
"data": {
"status": "ok",
"dependencies": {
"database": { "status": "up", "latencyMs": 3 },
"redis": { "status": "up", "latencyMs": 1 }
}
},
"meta": { "requestId": "019fef70-a80f-77d5-a975-bbe9f5f0c32c", "timestamp": "…" }
}
```
> **Ports.** PostgreSQL is published on **5433** and Redis on **6380**, not their defaults. Many
> machines already run one or both, and a shadowed port surfaces as a baffling authentication
> error rather than a clear conflict. Inside the Docker network they still use 5432/6379.
---
## Commands
### Everyday
```bash
pnpm dev # everything
pnpm dev:storefront # one app (dependencies are built first)
pnpm dev:admin
pnpm dev:api
pnpm build # build all, in dependency order
pnpm lint # ESLint across the workspace
pnpm lint:fix
pnpm typecheck # tsc --noEmit everywhere
pnpm test
pnpm format # Prettier write
pnpm format:check
```
### Infrastructure
```bash
pnpm infra:up # PostgreSQL, Redis, MinIO, Mailpit
pnpm infra:down
pnpm infra:logs
pnpm infra:reset # destroys volumes, then restarts
docker compose --profile full up --build # full stack behind Nginx on :80
```
### Database
```bash
pnpm db:migrate # create + apply a migration (dev)
pnpm db:deploy # apply pending migrations (production)
pnpm db:generate # regenerate Prisma Client
pnpm db:seed # reconcile permissions + roles (idempotent)
pnpm db:studio # Prisma Studio
pnpm db:reset # drop, re-migrate, re-seed
```
### Running one app manually
```bash
pnpm --filter @sport/api run dev # :4000
pnpm --filter @sport/storefront run dev # :3000
pnpm --filter @sport/admin run dev # :3001
```
---
## Repository layout
```
sport-store/
├── apps/
│ ├── storefront/ Next.js customer site (:3000)
│ │ └── src/
│ │ ├── app/ App Router — (shop) (checkout) (account) route groups
│ │ ├── components/ Cross-feature UI (layout, chrome)
│ │ ├── features/ auth · product · category · collection · search
│ │ │ cart · checkout · order · wishlist · account
│ │ ├── hooks/ lib/ services/ stores/ styles/ types/
│ │
│ ├── admin/ Next.js back office (:3001)
│ │ └── src/
│ │ ├── app/ (auth) login · (dashboard) everything else
│ │ ├── components/ features/ lib/ styles/
│ │
│ └── api/ NestJS modular monolith (:4000)
│ ├── prisma/ schema.prisma · migrations · seed.ts
│ └── src/
│ ├── config/ env validation → typed config object
│ ├── common/ decorators · filters · guards · interceptors
│ │ middleware · pipes · errors
│ ├── infrastructure/ prisma · redis · storage · events · logging
│ └── modules/ 20 bounded contexts
│
├── packages/
│ ├── types/ Framework-free domain + API contracts (zero deps)
│ ├── validation/ Zod schemas shared by API and both frontends
│ ├── api-client/ The only sanctioned way for a frontend to reach the API
│ ├── ui/ Design-system primitives (Button, Input, Badge, Skeleton)
│ ├── config/ Shared tsconfig bases + Tailwind theme tokens
│ └── eslint-config/ Flat configs incl. the architectural boundary rules
│
├── infrastructure/
│ ├── docker/ One Dockerfile per app (turbo prune → standalone)
│ ├── nginx/ Edge routing, rate ceiling, asset caching
│ └── scripts/ bootstrap.sh · reset-db.sh
│
├── docs/
│ ├── architecture.md Boundaries, conventions, risks — read this first
│ └── adr/ 12 decision records
│
├── docker-compose.yml Backing services; `--profile full` runs everything
├── turbo.json pnpm-workspace.yaml package.json
```
---
## Architecture in brief
Full detail in [`docs/architecture.md`](./docs/architecture.md). The rules that matter most:
1. **Only the API touches data.** Neither frontend has a database, Redis or storage client.
Enforced by ESLint and by the absence of `DATABASE_URL` from their environments.
([ADR-0004](./docs/adr/0004-the-admin-dashboard-has-no-database-access.md))
2. **All business logic lives in the backend.** The storefront may format `₫250.000`; it may
never compute a discount.
3. **Product ≠ ProductVariant.** A product has a page; a _variant_ has a SKU, a price and stock.
Every colour × size combination is its own variant.
([ADR-0003](./docs/adr/0003-product-and-productvariant-as-separate-entities.md))
4. **Authorization is permissions, never role checks.** There is no `if (user.role === 'ADMIN')`
anywhere. ([ADR-0007](./docs/adr/0007-rbac-permissions-instead-of-role-checks.md))
5. **Authentication is on by default.** The access-token guard is global; a route is public only
by declaring `@Public()`. Forgetting a decorator fails closed.
6. **Money is an integer in minor units**, everywhere.
([ADR-0011](./docs/adr/0011-money-as-integer-minor-units.md))
7. **Modules own their tables exclusively.** Cross-module access goes through a `public/` barrel
or a domain event — enforced by ESLint, which is what keeps a future service extraction
possible. ([ADR-0002](./docs/adr/0002-modular-monolith-not-microservices.md))
### Storefront routes
`/` · `/men` · `/women` · `/sports/[running|football|training|gym|badminton|lifestyle]` ·
`/products/[slug]` · `/collections/[slug]` · `/search` · `/cart` · `/checkout` ·
`/account/[profile|orders|addresses|wishlist]` · `/blog`
### Backend modules
`auth` `users` `customers` `products` `product-variants` `categories` `collections` `brands`
`inventory` `carts` `checkout` `orders` `payments` `promotions` `coupons` `wishlist` `reviews`
`cms` `media` `search` (+ `health`)
---
## Roadmap
| Milestone | Scope |
| --------- | ------------------------------------------------------------------------------------- |
| **M0** ✅ | Architecture, tooling, schema, health check, Docker, CI |
| **M1** | Catalog read API: products, variants, categories, collections, brands + Redis caching |
| **M2** | Auth: login, refresh rotation, RBAC admin, user/role management |
| **M3** | Admin catalog: product editor, variant matrix, media uploads, inventory |
| **M4** | Storefront catalog: listings, PDP with variant selector, filters |
| **M5** | Cart, checkout, orders |
| **M6** | Search + faceting |
| **M7** | Promotions, coupons, reviews, CMS |
| **M8** | Customer account |
| **M9** | Payments (VNPay, MoMo, ZaloPay, COD), shipping, notifications |
**Recommended next step: M1.** It exercises every layer end to end — Prisma repository → service
→ controller → envelope → `@sport/api-client` → a rendered page — on read-only endpoints where
mistakes are cheap. It also proves the variant model against real data before anything writes to
it. Auth (M2) comes second because the enforcement half already exists; only issuance is
missing.
---
## Verified in this environment
Everything below was run, not assumed:
- `pnpm install` — 10 workspace projects resolved
- `pnpm lint` · `pnpm typecheck` · `pnpm build` — 24/24 Turborepo tasks pass
- `pnpm format:check` — clean
- `prisma migrate dev` — 25 tables created
- `pnpm db:seed` — 36 permissions, 6 roles
- API boots; `GET /api/v1/health` returns `status: ok` with PostgreSQL and Redis both `up`
- Error envelope confirmed on a 404; `x-request-id` echoed; Helmet, CORS and rate-limit headers
present; Swagger served at `/docs`
- `pnpm test` — 5 passing RBAC guard tests
- Storefront renders 20 routes (`/sports/curling` correctly 404s); admin renders 17
Known benign noise: NestJS logs two `Unsupported route path: "/api/*"` warnings at boot. They
come from Nest's own global-prefix handling under Express 5 / path-to-regexp v8, are
auto-converted correctly, and routing is verified working. Nothing in this repository registers
that path.
---
## Contributing
- Branches: `feat/…`, `fix/…`, `chore/…`, `docs/…`
- Commits: [Conventional Commits](https://www.conventionalcommits.org/)
- CI runs lint, typecheck, format, tests against real PostgreSQL and Redis, builds every app,
and builds all three Docker images on push.
- Architectural changes need an ADR in [`docs/adr/`](./docs/adr/README.md).