Blocks Data is the data service of the SELISE Blocks platform. It covers two domains:
- Data Gateway: schema-driven structured data on MongoDB. Users define schemas (entities and reusable objects), and the service exposes each project's data through a per-tenant GraphQL API with row-level and column-level access control (RLS/CLS), field validation rules, mock data generation, and schema import/export.
- Storage: unstructured file storage and document management (DMS) over multiple back ends (Azure Blob, AWS S3 and S3-compatible services, SFTP), including pre-signed upload/download URLs, file versioning, and nested folders. Folders and files carry per-resource access policies scoped to users, roles, organizations or everyone, with inheritance from the parent and a per-resource override. Listings are access-resolved and cursor-paginated, so a caller sees only what they may view. Standard document-management features are included: search, a trash with restore, copy and move, and an audit trail of every access decision.
The repository ships a .NET backend, a React single-page application, and a Playwright end-to-end suite. It is released under the MIT license.
server/ .NET backend
Api/ ASP.NET Core host: REST controllers (under the /api prefix),
the per-tenant GraphQL endpoint at /api/gateway, and the
built SPA served from Api/wwwroot
DataGateway.DomainService/ Data Gateway domain: GraphQL schema building, query/mutation
services, RLS/CLS policy evaluation, validators, repositories
Storage.DomainService/ Storage domain: storage providers, file management, configuration
Worker/ Background host: message consumers (schema import/export,
migration completion, default folder creation) and a periodic ping
DataGateway.Driver/ Source of the SeliseBlocks.DataGatewayDriver NuGet package
Storage.Driver/ Source of the SeliseBlocks.StorageDriver NuGet package
XUnitTest/ xUnit test suite for the backend
client/ React + Vite SPA (schema designer, data browser, GraphQL playground,
access control and validation UIs, storage/DMS UIs)
e2e/ Playwright end-to-end tests (see e2e/README.md)
scripts/ Maintainer scripts (scan and deploy entry points)
Multi-tenancy: one instance serves all tenants. The tenant is resolved from the access token when the request is authenticated, otherwise from the x-blocks-key header. REST endpoints are protected with permission scopes of the form blocks-data::<action>.
The API and Worker use Genesis 4.2.2 to resolve each tenant's stored DbConnectionString and DBName from the central root registry. BlocksRootDb lookups remain on the main connection. The Data Gateway's explicitly configured external data source remains an override for GraphQL data operations; it does not change where tenant metadata, schemas, files, or import/export records are stored. Schema import/export messages carry the target project key, which is used for their database operations. Deploy both the API and Worker with the updated package before routing new environments to separate clusters.
- .NET SDK 10.0 (the solution targets
net10.0; the two driver packages targetnet9.0, which the 10.0 SDK builds) - Node.js 20+ with npm (Node 22 is used in the Docker build; Node 24 is known to work)
- Backend runtime dependencies are provisioned through the Blocks Genesis platform (secret vault, MongoDB, cache, message bus). Without access to a Blocks environment you can build and unit-test everything, but the API and Worker will not start end to end.
# backend
dotnet restore server/Blocks.slnx
dotnet build server/Blocks.slnx
# frontend
npm ci --prefix client
# e2e
npm ci --prefix e2enpm ci installs exactly what package-lock.json specifies. Use npm install only when you are deliberately changing a dependency.
run.sh (Linux/macOS/Git Bash) and run.ps1 (Windows PowerShell) are the local entry points:
./run.sh -f # frontend dev server (Vite, port 4000)
./run.sh -b # .NET API (port 5000)
./run.sh -w # .NET Worker
./run.sh -a # build frontend into server/Api/wwwroot, then run API + Worker
./run.sh -h # all options, including test shortcutsNotes:
- The API resolves its secrets through the Blocks Genesis vault at startup, so
./run.sh -brequires a configured Blocks environment (see Configuration below). In an environment without those backing services the process will not come up; build and unit tests still work. - The frontend dev server binds to the named host
dev-data.blocksdevelopers.com, which must resolve to127.0.0.1via a hosts entry.npm --prefix client run localstarts plain Vite without the named host. - Local HTTPS for both the Vite dev server and Kestrel is enabled by setting the OS environment variables
DATA_SSL_CERTandDATA_SSL_KEYto a PEM certificate/key pair (for example from mkcert). When unset, both serve HTTP.
Dockerfile builds the API image (SPA baked into wwwroot), and Dockerfile.worker builds the Worker image.
Run these from the repo root. The solution file is server/Blocks.slnx (the newer XML solution format; there is no .sln), and the test commands target the test project directly.
# backend unit tests
dotnet test server/XUnitTest/XUnitTest.csproj
# frontend unit tests
npm --prefix client run test
# end-to-end tests
npm --prefix e2e run testCoverage:
dotnet test server/XUnitTest/XUnitTest.csproj --collect:"XPlat Code Coverage"
npm --prefix client run test -- --coverageThe e2e suite drives the real application through a browser. It needs a running app, a .env.e2e file with the target URL and test credentials, and a hosts entry for the named domain. See e2e/README.md for the full setup.
Backend tests that touch MongoDB run against an ephemeral server through MongoFixture and are marked [Collection("Mongo")]; BlocksTestContext seeds the ambient tenant and user. To use an already running local MongoDB server instead of downloading the ephemeral server, set BLOCKS_DATA_TEST_MONGO_URI to its connection URI for the test process. The fixture creates unique database names. Frontend tests mock the HTTP client and wrap hooks in the shared query-client provider from client/app/test-utils.
Two things worth knowing before reading a failure here:
- Build before measuring coverage. A combined
dotnet testinvocation has been seen returning partial totals with whole assemblies reporting zero and no error. Rundotnet build server/XUnitTest/XUnitTest.csprojfirst, thendotnet test --no-build, and compare the test total against the previous run before quoting a number. - Delete a leftover
.sonarqubedirectory. Ifdotnet testaborts with "Test host process crashed" and writes no coverage file,scripts/scan.shleft one at the repository root. It is ignored by git, sorm -rf .sonarqubeand re-run.
The DMS exposes three controllers under server/Api/Controllers/Storage/:
| Controller | Covers |
|---|---|
FilesController |
File metadata, presigned upload and download, versions, copy, move |
FoldersController |
Folder create, read, children listing, rename, move, delete |
ContentController |
Sharing, access policies, search, trash |
Every action is authorised twice. [ProtectedEndPoint("blocks-data::<verb>")] decides who may call it at all, and the service then resolves the caller's access to the specific resource through IContentAccessResolver. Holding the endpoint permission does not grant access to any particular folder or file.
Access policies attach to a folder or a file and name a principal: a user, a role, an organization, or everyone. A resource inherits its parent's policies unless inheritance is switched off, which is refused while nothing else grants access, since the resource would otherwise become invisible to everyone including the person switching it. Deny beats allow, except that a deny aimed at the owner of the resource it sits on is rejected rather than stored.
Two conventions are easy to break by accident:
- A read the caller may not perform answers 404, not 403. Returning "forbidden" would confirm that a folder exists to someone who may not see it, which is enough to map a tree they have no access to.
- The listing visibility shortcut is only valid for one folder's children. It treats a purely inheriting resource as visible because its parent already was. Any query that crosses folders, such as search or the trash, resolves
Viewper item instead.
scripts/scan.shis the repository's scan entry point (SAST, SCA, and secret scanning). It runs in the maintainers' environment and is intentionally not tracked in git.scripts/deploy.shbuilds and deploys to the maintainers' dev VM. Warning: it runsgit reset --hard origin/inceptionon the working copy and rewrites systemd units; it is not a general-purpose installer.
Do not commit secrets. All values below are supplied per environment.
Server (server/Api/appsettings.json, server/Worker/appsettings.json, plus environment-specific overrides):
Logging: standard .NET logging levels.DatagatewayClusterNames,DatagatewayClusterRevision: cluster identifiers used by the Data Gateway pipeline builder.AiCompletionUrl,ChatGptTemperature(Api): endpoint and temperature for the regex assistant's AI completion calls.SwaggerOptions(Api): OpenAPI document settings.DownloadFilesControllerUrl,NotificationServiceUrl(Worker): service URLs used by consumers.FrontendRuntime(Api):BLOCKS_*values injected into the built SPA at startup by replacing__BLOCKS_*__placeholders inwwwroot.SecretManager: how the Genesis secret vault is reached (seeserver/Api/appsetting.Development.example). Connection strings, certificates, and API keys are resolved from the vault at startup, not from appsettings.
Client (client/.env.example): BLOCKS_-prefixed variables read by Vite (API base URL, notification URL, utility API origin, tenant key, captcha site key, construct URL). Copy to client/.env and fill in values for your environment.
E2E (e2e/.env.e2e.example): target base URL and test-account credentials. Copy to e2e/.env.e2e; the file is gitignored.
- CONTRIBUTING.md: branch model, conventions, and the checks a change must pass.
- SECURITY.md: how to report a vulnerability privately.
- CODE_OF_CONDUCT.md: community standards.
MIT, Copyright (c) SELISE Digital Platforms.