A web application for managing projects and their tasks. The repository holds the front-end only — the back end is VEDS, a Kotlin microservice system, and everything this application needs reaches it through the VEDS API gateway.
The front end talks to exactly one address: the VEDS api-gateway. Services are never called directly, so the browser needs no knowledge of how the back end is split up, and the gateway is the only place where authorization, CORS and session handling live.
| Concern | Where it is served from |
|---|---|
| Sign-in and session | api-gateway (/auth/authorize, /auth/callback, /auth/session) |
| Accounts and roles | iam-service (/auth/**, /users/**, /roles/**) |
| Projects | project-service (/projects/**, /project-types/**, /project-roles/**) |
| Tasks and comments | task-service (/tasks/**) |
| Notifications | notification-service (/notifications/**, /ws/notifications) |
| Files | file-service (/files/**) |
| Translation catalogue | translation-service (/translations/**) |
- Angular.
- RxJS.
- NGXS.
- Tailwind CSS.
@stomp/stompjsfor the notification WebSocket.
Authentication is not implemented here. VEDS uses the BFF token-handler pattern: the gateway runs the Keycloak
authorization-code flow with PKCE and keeps the tokens in its own Redis-backed session, handing the browser nothing
but an HttpOnly cookie.
What follows from that, and is worth knowing before changing anything in src/app/auth:
- There is no login form. Signing in is a redirect to
/auth/authorize; Keycloak collects the credentials. - No token ever reaches JavaScript, so there is nothing to store, refresh or accidentally log. Requests carry
withCredentials: trueand nothing else. - Registration and password changes happen on Keycloak's pages, reached through
/auth/authorize?kc_action=…. The gateway only relays actions it recognizes. - The signed-in user is whatever
GET /auth/sessionreturns —{ userId, email, roles }.
- State management with NGXS.
- Fully translated into English and Polish. Reference data (project types, statuses, categories, roles) arrives
already translated: the
x-langheader decides, and the server resolves it, so the UI never picks a translation. - Notifications carry a message key and parameters rather than finished text, so an old notification still renders in whatever language is selected today.
- Custom Tailwind theme with dark/light mode.
- Components are reusable and organized by Atomic Design.
Three conventions are worth knowing before adding a call:
- Envelope. Every response is
{ data, message, timestamp }.messageis a translation key, not a sentence. - Optimistic concurrency. Anything with a
versionis written back with anIf-Match: W/"<version>"header; the service refuses the write if the record has moved on.HttpApiService.ifMatch()builds it. - Files are references. Uploads go straight to object storage on a signed URL and only the file id reaches the
service that owns the record — see
FileUploadService.
- Turborepo for script automation and monorepo structure management.
- ESLint and Prettier for static code analysis and consistent code quality.
Note
During application development, SOLID principles, DRY, composition over inheritance, dependency injection, design patterns, architectural patterns, testing, and other good programming practices were applied.
- Download the project to your local environment.
- Install the dependencies:
pnpm install
There is nothing to configure and no .env to create: the only setting is the gateway address in
apps/frontend/src/environments/environment.ts, which already points at a local VEDS.
The back end is not part of this repository. Start it from the VEDS repository first — its docker-compose.yml
brings up PostgreSQL, Keycloak, Kafka, Redis and object storage, and the services are run from there:
docker compose up -dThen start the front end:
pnpm run devNote
By default:
- The VEDS api-gateway is available at http://localhost:8080.
- The front-end application is available at http://localhost:4200.
The gateway only accepts browser requests from origins listed in its veds.gateway.cors.allowed-origins;
http://localhost:4200 is there by default.
pnpm run buildThe production bundle takes the gateway address from the API_URL build argument (see apps/frontend/Dockerfile).
No secret is baked in — the browser holds no credential of its own.




