Product name: Blocks Localization. The legacy name EuroLM survives only in internal plumbing (namespaces such as
Eurolm.DomainService,eurolm_*message queues) and is being retired incrementally.
ASP.NET Core (net10.0) + React (Vite, TypeScript) Blocks Identity / cloud admin surfaces with Blocks Localization domain extensions (Eurolm.DomainService, legacy EuroLM naming). The web host (Genesis/Blocks configuration, FluentValidation, health checks) serves the SPA from server/Api/wwwroot; GlobalApiRoutePrefixConvention prefixes attribute routes with api. A separate Worker (blocks-eurolm-worker) runs message consumers. Node/npm are for the client toolchain (npm install, npm run dev, npm run build).
blocks-localization/
βββ client/ # React + Vite + TypeScript (package name: blocks-localization-client)
β βββ app/ # Source (iam, cross-modules, routes, components, lib, β¦)
β β βββ iam/ # Auth, IAM, captcha, API settings, β¦
β β βββ cross-modules/ # Shared areas (ai, communication, identifier, lmt, β¦)
β β βββ routes/, pages/, layouts/, hooks/
β β βββ lib/ # e.g. get-api-path.ts, runtime-env.ts, http-client.ts
β βββ index.html # Inline __BLOCKS_* placeholders for prod substitution
β βββ vite.config.ts # build.outDir β ../server/Api/wwwroot; dev port 4000; BLOCKS_ env
β βββ package.json
β βββ .env.example # Template for local BLOCKS_* (copy to .env)
βββ server/
β βββ Api/ # Kestrel host (static SPA + JSON API)
β β βββ Controllers/ # See βAPI / routingβ below (~23 controllers)
β β βββ wwwroot/ # Vite output (generated; do not edit)
β β βββ Program.cs # Middleware, SPA fallback, optional token replacement into built assets
β β βββ Api.csproj
β β βββ Properties/launchSettings.json # Example: http://localhost:5000
β βββ Worker/ # Background consumers (hosted service name in code)
β βββ Eurolm.DomainService/ # EuroLM-specific services and data
β βββ DomainService/ # Standalone *.csproj on disk; not in Blocks.slnx; most `DomainService.*` namespaces live under other *Domain* projects
β βββ Authentication.DomainService/
β βββ Captcha.DomainService/
β βββ Cloud.DomainService/
β βββ Cloud.LmtService/
β βββ CloudConfiguration.DomainService/
β βββ Iam.DomainService/
β βββ Identifier.DomainService/
β βββ Mfa.DomainService/
β βββ *_Driver/ # Captcha.Driver, Iam.Driver, Mfa.Driver (outside solution)
β βββ XUnitTest/
β βββ Directory.Build.props # TargetFramework net10.0
β βββ Blocks.slnx # Api, domain projects, Worker, XUnitTest
βββ run.sh # Build client β run API from server/Api
βββ run-app-combined.sh # Optional npm i, build, Worker + API on :5000; optional distβwwwroot rsync*
βββ run-api-only.sh # Free :5000, dotnet run Api
βββ run-fe-only.sh # Prompt for Vite `--host`; map /etc/hosts; npm run dev
βββ run-worker-only.sh # dotnet run Worker
βββ e2e/ # Playwright end-to-end tests (see e2e/README.md)
βββ scripts/ # scan.sh and deploy.sh entry points
βββ Dockerfile, Dockerfile.worker # CI/production images (multi-stage API + Worker)
βββ LICENSE
βββ README.md
βββ β¦
*With the current client/vite.config.ts, vite build writes directly to server/Api/wwwroot. If client/dist does not exist, the rsync branch in run-app-combined.sh is skipped; the SPA is already in wwwroot from the build step.
- .NET 10 SDK; see
server/Directory.Build.props(net10.0) - Node.js (local dev; Dockerfile uses Node 22 for reproducible frontend builds)
This app expects Genesis/Blocks configuration and backing services appropriate to your environment. For local stacks (databases, messaging, related services), use blocks-infra Docker Compose as documented in that repository (docker compose up, etc.); bring Compose up before or alongside the API/Worker here so connectivity and vault/secrets behave as intended.
Docker alone is optional for raw dotnet/npm runs unless you explicitly depend on that Compose stack.
Ports
| Process | Typical port |
|---|---|
| API (Kestrel) | 5000 (server/Api/Properties/launchSettings.json) |
Vite (npm run dev) |
4000 (client/vite.config.ts and client/package.json script) |
launchSettings.json governs dotnet run from the IDE; shell scripts enforce 5000 their own way (see below).
Runs from repo root:
npm run buildinclient/(fillsserver/Api/wwwrootper Vite)dotnet runinserver/Api/
No flags. Example:
chmod +x run.sh # once
./run.sh- Installs
client/node_moduleswithnpm ionly if missing - Builds the client (
npm run build) - If
client/dist/exists, rsync-syncsclient/dist/βserver/Api/wwwroot/(rsync -a --delete); redundant when Vite output already targetswwwroot(current config) - Frees listeners on
5000vialsof+ kill - Starts Worker as a background job, then runs Api (foreground); Ctrl+C / cleanup stops the Worker
Example:
chmod +x run-app-combined.sh
./run-app-combined.shFrees 5000, then dotnet run on server/Api/Api.csproj from repo root.
./run-api-only.shdotnet run on server/Worker/Worker.csproj (no API port logic).
./run-worker-only.sh- Parses
blockdev host frompackage.jsonscripts.dev(--host β¦) - Prompts for a domain (defaults to detected host); may append
127.0.0.1 <domain>to/etc/hosts(sudo) - Clears
4000the same pattern as combined script does for5000 - Writes the chosen
--hostback intopackage.json - Runs
npm run devinclient/
Use this when you want Vite hot reload behind a hostname you map locally.
There is no run.ps1 in this repository. Use WSL or Git Bash for the .sh scripts, or invoke dotnet/ npm manually (below).
Ensure wwwroot is populated (either run npm run build in client/ first, or your publish pipeline copied assets):
dotnet run --project server/Api/Api.csprojWorker separately:
dotnet run --project server/Worker/Worker.csprojFrontend dev server (see vite.config.ts: BLOCKS_API_BASE_URL enables dev proxy prefixes such as /api, /iam, /communication, etc.):
cd client && npm install && npm run devVite envPrefix is BLOCKS_ (client/vite.config.ts). For production bundles, placeholders in client/index.html can be rewritten at startup by server/Api/Program.cs using process environment (DotNetEnv) for:
BLOCKS_API_BASE_URLBLOCKS_X_BLOCKS_KEYBLOCKS_GOOGLE_SITE_KEYBLOCKS_CONSTRUCT_URL
Copy client/.env.example β client/.env for values consumed at npm run / vite build time.
| Variable | Role |
|---|---|
BLOCKS_API_BASE_URL |
API base URL; import.meta.env and runtime replacement; triggers Vite proxy when non-empty (client/vite.config.ts) |
BLOCKS_X_BLOCKS_KEY |
X-Blocks-Key and project-style usage (client/app/lib/http-client.ts, auth/IAM flows) |
BLOCKS_GOOGLE_SITE_KEY |
hCaptcha/Google flows in auth forms |
BLOCKS_CONSTRUCT_URL |
Construct service links (client/app/lib/runtime-env.ts, OIDC/UI) |
BLOCKS_APP_URL |
Build-time branching in PAT/SSO UI (import.meta.env) |
BLOCKS_BASE_DOMAIN |
Project/help domain fallback (client/app/hooks/use-project.ts) |
BLOCKS_BLOCKED_MENU |
JSON string for menu filtering (client/app/hooks/use-filtered-menus.ts) |
BLOCKS_GITHUB_CLIENT_ID |
DevOps/GitHub OAuth client id (client/app/cross-modules/devops/services/providers.service.ts) |
Type-only / optional: BLOCKS_CLOUD_DASHBOARD_URL is declared in client/app/vite-env.d.ts: search usages if you rely on it.
Server-side (API startup): BLOCKS_VAULT_TYPE selects Genesis vault parsing when set; otherwise ASPNETCORE_ENVIRONMENT/Development β OnPrem, else Azure (server/Api/Program.cs, server/Worker).
Rebuild the client after changing build-time BLOCKS_*.
Frontend build publishes into server/Api/wwwroot:
(cd client && npm ci && npm run build)
dotnet publish server/Api/Api.csproj -c Release -o ./publishNo Node process is needed on the server at runtime unless you deliberately run tooling there. Prefer the repo Dockerfiles for consistent Node + SDK versions.
Worker image/pattern: Dockerfile.worker.
Run from the repository root:
# backend unit tests (xUnit)
dotnet test server/XUnitTest/XUnitTest.csproj
# frontend unit tests (Vitest)
npm --prefix client run test
# end-to-end tests (Playwright); needs a reachable app and e2e/.env.e2e,
# see e2e/README.md for setup and target modes
npm --prefix e2e run testCoverage:
dotnet test server/XUnitTest/XUnitTest.csproj --collect:"XPlat Code Coverage"
npm --prefix client run test -- --coveragescripts/scan.shis the security scan entry point (SAST, SCA and secret scanning). It is intentionally not tracked in git; internal environments provide it.scripts/deploy.shis the maintainer deploy script for a systemd host: it checks out the latestinception, builds the client, publishes the Api and Worker projects, and installs and restarts their systemd services.
- Controllers:
server/Api/Controllers/(e.g. Authentication, Iam, Mfa, Captcha, Key, Mail, Storage, Assistant, Module, Project, People, Language, Glossary, Migration, Log, Trace, Notification, Discovery (routes under[Route(".well-known")]prefixed withapi), β¦). - Global prefix:
api:GlobalApiRoutePrefixConventioninProgram.csprefixes every controller[Route(...)]. - SPA:
UseDefaultFiles,UseStaticFiles,MapFallbackToFile("/index.html")whenwwwroot/index.htmlexists (Program.cs). /apiis effectively reserved by the ASP.NET routing convention; Genesis may also expose other path prefixes in middleware; tune CORS/origin when splitting Vite (:4000) from Kestrel (:5000).
- Contribution conventions and workflow: CONTRIBUTING.md
- Reporting a vulnerability: SECURITY.md
- Community standards: CODE_OF_CONDUCT.md
API and Worker use the published Genesis 4.2.2 package. Repositories follow persisted tenant connections per operation. No environment-to-connection mapping is added here.
Singleton language, glossary, and key services read the operation's tenant context when creating records; they do not retain the tenant from construction. Module listing with an explicit project key uses that target even when the caller has a different ambient tenant. The generation worker stamps published timeline records with its event's project key. Worker messages still require the expected authenticated tenant context for repository operations that use ambient routing.
Migration source and destination databases remain explicit. Migration trackers belong to the initiating tenant carried by the authenticated message context, which may differ from both environments. Tracker repository writes require that owner; they do not fall back to root when context is missing. The migration worker preserves that context when publishing completion to OS. Shared timeline user enrichment continues using configured RootTenantId on main.
Regression tests cover independent source/target/owner databases, overwrite and retry behavior, concurrent tracker ownership, and completion message context. They use disposable local MongoDB databases through the real Genesis provider:
dotnet test server/XUnitTest/XUnitTest.csproj -c ReleaseThe new routing fixture defaults to localhost:27017. BLOCKS_ROUTING_TEST_MONGO_PORT selects another local test port; remote hosts and deployed credentials are not used. These tests do not establish independent deployed cluster connectivity or an atomic cross-cluster migration.
Deploy API and all translation/import/export/migration workers before enabling OS split placement. Keep the root registry on main. Existing tenant placement/data migration and coordinated cache refresh remain separate work.
See LICENSE.