Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ POSTGRES_DB=platform
JWT_SECRET=replace-with-at-least-32-random-characters
INTERNAL_SERVICE_SECRET=replace-with-a-random-service-secret
ACCESS_TOKEN_EXPIRATION_SECONDS=900
DISPLAY_TIME_ZONE=UTC

# Optional immutable images for local Compose. Leave unset to use latest.
ACCOUNT_IMAGE=ghcr.io/microsserv52/account:latest
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ jobs:
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
submodules: false
fetch-depth: 0
- name: Checkout backend submodules
working-directory: ${{ github.workspace }}
run: >-
git -c url.https://github.com/.insteadOf=git@github.com: submodule update --init --recursive -- api/account api/auth api/gateway api/order api/order-service
- uses: actions/setup-java@v5
with:
distribution: temurin
Expand All @@ -51,7 +55,10 @@ jobs:
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
submodules: false
- name: Checkout backend submodules
run: >-
git -c url.https://github.com/.insteadOf=git@github.com: submodule update --init --recursive -- api/account api/auth api/gateway api/order api/order-service
- name: Validate Compose topology
run: docker compose config
- name: Build application images
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,16 @@ jobs:
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
submodules: false
- name: Checkout backend submodules
run: >-
git -c url.https://github.com/.insteadOf=git@github.com: submodule update --init --recursive -- api/account api/auth api/gateway api/order api/order-service
- uses: actions/setup-java@v5
if: matrix.language == 'java-kotlin'
with:
distribution: temurin
java-version: '25'
cache: maven
- uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/deploy-eks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ jobs:
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
submodules: false
- name: Checkout backend submodules
run: >-
git -c url.https://github.com/.insteadOf=git@github.com: submodule update --init --recursive -- api/account api/auth api/gateway api/order api/order-service
- uses: actions/setup-java@v5
with:
distribution: temurin
Expand Down
9 changes: 9 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,12 @@
[submodule "api/account"]
path = api/account
url = https://github.com/Microsserv52/account.git
[submodule "web/landing"]
path = web/landing
url = git@github.com:Microsserv52/landing.git
[submodule "web/store"]
path = web/store
url = git@github.com:Microsserv52/store.git
[submodule "web/admin"]
path = web/admin
url = git@github.com:Microsserv52/admin.git
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,31 @@ uv run zensical serve
Start with the [project overview](docs/project/project.md) or the
[getting started guide](docs/development/get_started.md).

## Web applications and local preview

The [frontend documentation](docs/frontend/index.md) covers three
independent Next.js applications integrated as Git submodules: `web/landing` at
`/`, `web/store` at `/store`, and `web/admin` at `/admin`, on one domain.
The agreed UI is English, with a shared modern brand and a public catalog.

This is an **educational mock store: no real payments or transactions take
place, and no goods are shipped**. Authenticated users create persisted
demo orders. The store includes an online-only PWA manifest; Grafana Faro and
Umami are integrated but disabled until their deployment values are agreed.
Each app exports static files and has a Dockerfile. Production CDN hosting and
per-app environment values will be discussed before deployment; frontend
Kubernetes deployment is deferred.

To test the frontend independently of the missing Product/Exchange images:

```bash
docker compose -f compose.frontend-demo.yml up --build -d
```

Open `http://localhost:8081/`, `/store/` or `/admin/`. This separate local-only
mode visibly labels disposable API fixtures. See the [local testing guide](docs/frontend/delivery.md)
for fixture inputs, real-service mode and verification limits.

## CI/CD and environment variables

Each service repository owns lint, format, tests, JaCoCo quality gates, CodeQL,
Expand Down
44 changes: 44 additions & 0 deletions compose.frontend-demo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: microsserv52-frontend-demo

services:
landing:
build:
context: ./web/landing
args:
NEXT_PUBLIC_APP_ENV: fixture
store:
build:
context: ./web/store
args:
NEXT_PUBLIC_APP_ENV: fixture
admin:
build:
context: ./web/admin
args:
NEXT_PUBLIC_APP_ENV: fixture
gateway:
image: python:3.13-slim
command:
[
"python",
"/qa/serve.py",
"--host",
"0.0.0.0",
"--port",
"8080",
"--fixtures",
"--api-only",
]
volumes:
- ./tests/frontend:/qa:ro
nginx:
image: nginx:1.28-alpine
ports:
- "127.0.0.1:${FRONTEND_DEMO_PORT:-8081}:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- landing
- store
- admin
- gateway
34 changes: 33 additions & 1 deletion compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,50 @@ services:
environment:
- LOG_LEVEL=info
- JWT_SECRET=${JWT_SECRET:-change-me-in-development-use-a-long-random-secret}
- SESSION_COOKIE_SECURE=false
- SESSION_COOKIE_PATH=/api
deploy:
replicas: 3
depends_on:
- account
- auth

landing:
image: microsserv52-landing:local
build:
context: ./web/landing
args:
NEXT_PUBLIC_API_BASE_URL: /api
NEXT_PUBLIC_DISPLAY_TIME_ZONE: ${DISPLAY_TIME_ZONE:-UTC}
hostname: landing

store:
image: microsserv52-store:local
build:
context: ./web/store
args:
NEXT_PUBLIC_API_BASE_URL: /api
NEXT_PUBLIC_DISPLAY_TIME_ZONE: ${DISPLAY_TIME_ZONE:-UTC}
hostname: store

admin:
image: microsserv52-admin:local
build:
context: ./web/admin
args:
NEXT_PUBLIC_API_BASE_URL: /api
NEXT_PUBLIC_DISPLAY_TIME_ZONE: ${DISPLAY_TIME_ZONE:-UTC}
hostname: admin

nginx:
image: nginx:alpine
image: nginx:1.28-alpine
hostname: nginx
ports:
- 8080:80
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- gateway
- landing
- store
- admin
1 change: 1 addition & 0 deletions docs/development/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ has already been implemented.
| [`infrastructure/`](../infrastructure/) | AWS, EKS, deployment and costs |
| [`operations/`](../operations/) | Observability, performance and load testing |
| [`services/`](../services/) | API contracts and service reference |
| [`frontend/`](../frontend/index.md) | Landing/store/admin content, architecture, packages, PWA, telemetry, local testing and verification |

## Commands

Expand Down
2 changes: 1 addition & 1 deletion docs/development/get_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ The local edge is Nginx on `http://localhost:8080`. Service containers are
replicated in the Compose topology and the gateway remains the only application
entry point exposed through the proxy.

For MicroK8s or EKS, see the [Kubernetes deployment guide](../../k8s/README.md).
For MicroK8s or EKS, see the [Kubernetes deployment guide](https://github.com/Microsserv52/platform/blob/main/k8s/README.md).

The gateway is exposed on port `8080` by the compose configuration. The
repository snapshot currently contains the Order API and documentation
Expand Down
119 changes: 119 additions & 0 deletions docs/frontend/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
icon: lucide/network
---

# Frontend architecture

## Source and runtime ownership

`web/landing`, `web/store` and `web/admin` are independent Git submodules. Each
owns a Next.js App Router application, strict TypeScript configuration, npm
lockfile, tests, static-export Dockerfile and CI workflows. No source imports
cross a submodule boundary and no JavaScript monorepo or module federation
runtime is introduced. Shared design/API conventions are documented centrally.

Each app uses `src/app` for routes, `src/components` for feature and shared UI,
`src/lib` for schemas/API access, and `tests/` for browser checks and fixtures.
The platform owns Compose, Nginx, the submodule commits and integration docs.
Gateway/Account own the backend changes. Kubernetes frontends are deferred.

## One domain, static zones

| Public path | Destination | Behavior |
| ---------------------------------------------------------------------- | -------------- | -------------------------------------------------------------- |
| `/` and landing pages | Landing export | Root assets at `/_next/*` |
| `/store/` | Store export | Fixed `/store` base path; all assets/manifests remain under it |
| `/admin/` | Admin export | Fixed `/admin` base path; independent assets |
| `/api/*` | Gateway | Nginx strips `/api` only; authenticated responses use no-store |
| Legacy `/auth`, `/accounts`, `/products`, `/orders`, `/exchange` paths | Gateway | Existing Bearer API clients remain supported |

Exact `/store` and `/admin` requests redirect to their slash form; prefix
matching cannot capture `/storefront`. Nginx serves actual exported paths and
returns a zone-specific 404 rather than rewriting unknown URLs to a shell.
Cross-zone links are ordinary anchors and load the destination app. Next.js
routing inside the store is used after demo order creation.

All apps use `output: 'export'`, `trailingSlash: true` and unoptimized static
images. There are no server actions, BFFs or runtime Next.js session handlers.
Product/order IDs are validated query parameters to statically exported detail
pages. Public catalog data is fetched at runtime; the initial export does not
require an available Product service or embed stale demo product data.

The approach follows official [static export](https://nextjs.org/docs/app/guides/static-exports),
[basePath](https://nextjs.org/docs/app/api-reference/config/next-config-js/basePath)
and [multi-zone](https://nextjs.org/docs/app/guides/multi-zones) guidance. The
future CDN needs equivalent path-to-origin behavior, trailing-slash/404 rules
and an API origin. Choose those with the owner at deployment time.

## API-owned browser sessions

| Gateway endpoint | Purpose |
| --------------------- | -------------------------------------------------------------------------------------- |
| `GET /session/csrf` | Return the Spring CSRF token/header; establish its cookie |
| `POST /session/login` | Call Auth, set the access token in an HttpOnly cookie, return 204 without a token body |
| `GET /session` | Validate the token and fetch the authenticated Account profile |
| `DELETE /session` | Clear the session cookie; no refresh/revocation capability is claimed |

Through the public proxy these endpoints are `/api/session/*`. The host-only
`ms52-session` cookie has path `/api`, SameSite=Lax and HttpOnly. It is Secure
by default; local HTTP Compose explicitly sets `SESSION_COOKIE_SECURE=false`.
Its lifetime follows Auth's access-token expiry. The frontend never stores or
reads JWTs in localStorage, sessionStorage or a JavaScript cookie.

Spring Security's cookie CSRF repository and default request-token handler
protect cookie-authenticated writes and login. Every frontend mutation obtains
a token first. The Gateway request wrapper turns the cookie into a Bearer
header for token validation and downstream routing; explicit Bearer clients
retain their existing behavior. Upstream login/profile requests have timeouts.
The signing key stays in backend secret configuration.

Gateway maps the signed `roles` claim to authorities, requires ROLE_ADMIN for
Product writes and account list/detail/deletion, and blocks public internal
credential routes. Only GET product reads are anonymous. Account enforces its
own admin list/detail/deletion rules and exposes `GET /accounts/me` for the
authenticated user. Administrator/self deletion is rejected; account deletion
is not exposed in the frontend. Admin provisioning is an operator action,
never a registration input.

The route guard is presentation only. Tests verify backend role enforcement,
CSRF and an actual CSRF-cookie/login/profile exchange with local upstream
fixtures. Logout clears the cookie/client caches and notifies other tabs.
Receiving tabs use TanStack's [query reset](https://tanstack.com/query/latest/docs/framework/react/reference/classes/QueryClient)
to notify active subscribers and refetch session state, so an open private view
does not retain the previous profile after another tab signs out.
An already-issued token remains valid until expiry if copied elsewhere; there
is no fabricated refresh/revocation endpoint.

## State and optimization

TanStack Query owns API reads/mutations and schema-checked responses. Query
keys isolate private order data by identity; session changes reset cached data.
Reads have bounded retries/cancellation, mutations never retry automatically.
The cart stores only validated IDs/positive integer quantities under
`ms52-cart-v1`, synchronizes with browser storage and revalidates catalog data
before submission. Order receives IDs/quantities and calculates authoritative
prices. An uncertain submission directs users to order history before retry.

Search/sort operate locally on the unpaginated Product array. React deferred
rendering keeps input responsive; it is not called network debounce. TanStack
Table v9 uses explicit sorting/pagination features and 10-row pages. No virtual
list is added without evidence of a large-list rendering problem, and no
background queue is needed for the current flows. New server search/pagination,
debounce or queue packages require a separate contract/approval.

## Remaining service prerequisites

- Product/Exchange source and usable local images are absent. Verify Product
accepts anonymous GET, enforces its own write permissions and matches the
documented id/name/price/unit payload before real-service acceptance.
- The checked-in Order returns `productId` in items and rejects non-USD totals.
The frontend uses that payload and USD only.
- Exchange route naming differs between docs and Gateway; currency UI is deferred.
- Order creation lacks documented idempotency; the frontend prevents duplicate
clicks and automatic retries but cannot promise exactly-once order creation.
- All-orders administration, account editing/role changes, product editing,
product media/stock/categories and financial reporting require new contracts.

The fixture-only Docker mode is for UI verification and never proves these
real-service prerequisites. Backend and frontend release commits must be
reviewed in their owning repositories before integration pointers are promoted.
Loading
Loading