This commit is contained in:
Nông Đức Huy
2026-08-13 23:20:22 +07:00
parent 3e5d38ec18
commit 3d6b0e0d4e
145 changed files with 7817 additions and 801 deletions
+57 -34
View File
@@ -2,9 +2,9 @@
A modern sports-fashion e-commerce platform.
**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).
**Status: milestone 1 — catalog read API, live end to end, in Vietnamese and English.**
The storefront renders real products from the database through the REST API. Cart, checkout,
orders and auth are still ahead; see [Roadmap](#roadmap).
```
Storefront (Next.js) ─┐
@@ -116,10 +116,12 @@ sport-store/
├── apps/
│ ├── storefront/ Next.js customer site (:3000)
│ │ └── src/
│ │ ├── app/ App Router — (shop) (checkout) (account) route groups
│ │ ├── app/[locale]/ (shop) (checkout) (account) route groups
│ │ ├── components/ Cross-feature UI (layout, chrome)
│ │ ├── features/ auth · product · category · collection · search
│ │ │ cart · checkout · order · wishlist · account
│ │ ├── i18n/ next-intl routing, request config, navigation
│ │ ├── messages/ vi.json · en.json (UI strings)
│ │ ├── hooks/ lib/ services/ stores/ styles/ types/
│ │
│ ├── admin/ Next.js back office (:3001)
@@ -151,7 +153,7 @@ sport-store/
│
├── docs/
│ ├── architecture.md Boundaries, conventions, risks — read this first
│ └── adr/ 12 decision records
│ └── adr/ 14 decision records
│
├── docker-compose.yml Backing services; `--profile full` runs everything
├── turbo.json pnpm-workspace.yaml package.json
@@ -187,6 +189,28 @@ Full detail in [`docs/architecture.md`](./docs/architecture.md). The rules that
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))
8. **Two languages, two mechanisms.** UI strings live in message catalogs; product content lives
in database translation tables with per-locale slugs and field-level fallback. Conflating them
is why most "add a language" projects end up half-translated.
([ADR-0013](./docs/adr/0013-content-translations-in-typed-tables-ui-strings-in-message-catalogs.md))
### Languages
Vietnamese is the default and is served from clean URLs; English is prefixed with `/en`.
| | Vietnamese | English |
| ------- | --------------------------- | --------------------------- |
| Listing | `/men` | `/en/men` |
| Product | `/products/ao-chay-bo-aero` | `/en/products/aero-run-tee` |
Product slugs are translated too, so each language has its own indexable URL. Every product
page emits `hreflang` alternates and a canonical link, and requesting a product by the _other_
locale's slug redirects to the canonical one — which is what keeps the language switcher on a
product page from 404ing.
The admin switches language by cookie with no URL segment: it is `noindex` everywhere, so
locale-in-path would buy nothing.
### Storefront routes
`/` · `/men` · `/women` · `/sports/[running|football|training|gym|badminton|lifestyle]` ·
@@ -203,24 +227,23 @@ Full detail in [`docs/architecture.md`](./docs/architecture.md). The rules that
## 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 |
| Milestone | Scope |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| **M0** ✅ | Architecture, tooling, schema, health check, Docker, CI |
| **M1** ✅ | Catalog read API + Redis caching + vi/en localisation + storefront wired to real data |
| **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, variant selector and filters landed with M1; sort UI, pagination and a mobile filter drawer remain |
| **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.
**Recommended next step: M2 (auth).** The enforcement half already exists — global access-token
guard, RBAC permissions guard, audience separation — so only issuance is missing: login, refresh
rotation, and the admin user/role screens. Everything after it (cart ownership, orders, the admin
write path) depends on knowing who is asking.
---
@@ -228,24 +251,24 @@ missing.
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
- `pnpm lint` · `pnpm typecheck` · `pnpm test` · `pnpm build` — 25/25 Turborepo tasks pass;
`pnpm format:check` clean
- 5 migrations applied; 32 tables; seed loads 36 permissions, 6 roles, 3 brands, 8 categories,
3 collections, 12 products, **155 variants** and 64 generated images uploaded to MinIO
- `pnpm test` — 17 passing (RBAC guards, translation fallback, `Accept-Language` negotiation)
- API: listings with filters/facets/cursor paging, PDP, navigation, brands and collections all
return correctly localised payloads in both `vi` and `en`
- Storefront: every route returns 200 in both locales; PDP renders translated options, spec
table and variant titles; `/en/products/<vi-slug>` → 307 → `/en/products/<en-slug>`;
`hreflang` + canonical emitted per locale
- Money formats per locale from one integer: `690.000 ₫` (vi) / `₫690,000` (en)
- Admin: renders Vietnamese by default and English with `sport_admin_locale=en`
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/…`