Run Locally#
Everything you need to bring up the Cube 2.0 backend on your machine.
Prerequisites#
- .NET 8 SDK — verify with
dotnet --version→8.x - Docker Desktop — for SQL Server + Seq log viewer
- Git with submodule support
- Optional:
dotnet-efglobal tool for creating migrationsdotnet tool install -g dotnet-ef - Apple Silicon users: the compose stack uses
azure-sql-edge(arm64-native). The classicmssql/serverimage does not run under QEMU — see ADR 0004.
First-time setup#
git clone git@bitbucket.org:MANEXaloha/cube2-backend.git
cd cube2-backend
git submodule update --init --recursive # pulls the docs theme
cp .env.example .env # docker-compose vars
cp src/Manex.Web/appsettings.Development.json.example \
src/Manex.Web/appsettings.Development.json # local secrets (gitignored)Edit src/Manex.Web/appsettings.Development.json:
- Set
Jwt:SigningKey— any string ≥ 32 chars for dev. - Set
ConnectionStrings:eManExif you’re using Mode B (see below); leave it if you’re using Mode A and picking values from.env.
Pick a run mode#
There are two supported modes for local dev. They differ only in which database the API talks to.
Mode A — fresh sandbox#
Empty azure-sql-edge container. No real data. Best for isolated integration work, running the test suite locally with a real SQL provider, or trying auth flows without production risk.
docker compose -f deploy/docker/docker-compose.yml up -d db seq
dotnet run --project src/Manex.Web- API on http://localhost:5000
- Seq (Serilog log viewer) on http://localhost:5341
- SQL Server on
localhost:1433(sa /YourStrong@Passw0rdfrom.env)
The sandbox starts empty. No BcomMX_dev database, no aspnet_Users, no seed data. Login will 500 until you seed something. This is intentional — ADR 0004 says the API never creates schema against a real DB, so the sandbox mirrors that.
To make login work locally, seed a minimal user. Save this as seed-dev.sql:
IF DB_ID('BcomMX_dev') IS NULL CREATE DATABASE BcomMX_dev;
GO
USE BcomMX_dev;
GO
CREATE TABLE dbo.aspnet_Users (
UserId uniqueidentifier PRIMARY KEY,
ApplicationId uniqueidentifier NOT NULL,
UserName nvarchar(256), LoweredUserName nvarchar(256),
MobileAlias nvarchar(16), IsAnonymous bit NOT NULL,
LastActivityDate datetime2
);
CREATE TABLE dbo.aspnet_Membership (
UserId uniqueidentifier PRIMARY KEY,
ApplicationId uniqueidentifier NOT NULL,
Password nvarchar(max), PasswordFormat int NOT NULL DEFAULT 0,
PasswordSalt nvarchar(max), Email nvarchar(256), LoweredEmail nvarchar(256),
IsApproved bit NOT NULL DEFAULT 1, IsLockedOut bit NOT NULL DEFAULT 0,
CreateDate datetime2 NOT NULL DEFAULT SYSUTCDATETIME(),
LastLoginDate datetime2 NOT NULL DEFAULT SYSUTCDATETIME(),
LastPasswordChangedDate datetime2 NOT NULL DEFAULT SYSUTCDATETIME(),
LastLockoutDate datetime2 NOT NULL DEFAULT SYSUTCDATETIME(),
FailedPasswordAttemptCount int NOT NULL DEFAULT 0,
FailedPasswordAttemptWindowStart datetime2 NOT NULL DEFAULT SYSUTCDATETIME(),
FailedPasswordAnswerAttemptCount int NOT NULL DEFAULT 0,
FailedPasswordAnswerAttemptWindowStart datetime2 NOT NULL DEFAULT SYSUTCDATETIME(),
Comment nvarchar(max)
);
GO
INSERT INTO dbo.aspnet_Users (UserId, ApplicationId, UserName, LoweredUserName, IsAnonymous)
VALUES ('11111111-1111-1111-1111-111111111111',
'22222222-2222-2222-2222-222222222222',
N'admin', N'admin', 0);
-- BCrypt cost-11 hash of literal password: hunter2
INSERT INTO dbo.aspnet_Membership (UserId, ApplicationId, Password)
VALUES ('11111111-1111-1111-1111-111111111111',
'22222222-2222-2222-2222-222222222222',
N'$2a$11$3V2BgB0hIVWeiDgzwhNg9u14cmUVBDkgr/ZNwLldQ8GMP671HlipG');
GORun it against the sandbox:
docker run --rm --network cube2-backend_app-network \
-v "$PWD/seed-dev.sql:/seed.sql" \
mcr.microsoft.com/mssql-tools \
/opt/mssql-tools/bin/sqlcmd -S db -U sa -P 'YourStrong@Passw0rd' -i /seed.sqlYou can now POST /api/v1/auth/login with admin / hunter2. A repeatable deploy/sql/seed-dev.sql script is Phase 1 follow-up work.
Mode B — against the real legacy DB#
Talks to the actual BcomMX_01_04 on 192.168.9.111\SQLDEV2017 (or your personal restore of a backup). Best for feature work that needs real data.
Edit .env and set the real connection string:
ConnectionStrings__eManEx=Server=192.168.9.111\SQLDEV2017;Database=BcomMX_01_04;User Id=<your-user>;Password=<your-pass>;TrustServerCertificate=True;Encrypt=False;Then bring up the API + Seq, no db container:
docker compose \
-f deploy/docker/docker-compose.yml \
-f deploy/docker/docker-compose.legacy.yml \
up -d web seq- API on http://localhost:8080 (in-container)
- Seq on http://localhost:5341
The API never runs migrations against the legacy DB. It reads and writes with EF Core against the existing schema. Any future schema change goes through a forward-only migration authored against a scaffolded snapshot — see ADR 0004.
Verify it’s up#
curl -sS http://localhost:5000/health
# → {"status":"Healthy",…}
curl -sS http://localhost:5000/ready
# → {"status":"Healthy",…"sqlserver":{"status":"Healthy"}…} ← Mode A + seeded, or Mode BOpen http://localhost:5000/docs for Swagger UI.
Get a JWT#
curl -X POST http://localhost:5000/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"hunter2"}'Response (abbreviated):
{
"success": true,
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiI...",
"tokenType": "Bearer",
"expiresIn": 3600,
"user": { "userId": "…", "username": "admin", "roles": [], "permissions": [] }
}
}Roles and permissions are empty on Phase 0 — group → claim mapping ships in Phase 1.
Use the token:
TOKEN=eyJhbGci...
curl -H "Authorization: Bearer $TOKEN" http://localhost:5000/api/v1/auth/me
curl -H "Authorization: Bearer $TOKEN" http://localhost:5000/api/v1/auth/permissionsOr paste the token into Swagger’s Authorize dialog and hit protected endpoints from the UI.
Run tests#
dotnet test cube2-backend.sln # everything (9 smoke tests)
dotnet test tests/Manex.Web.Tests # unit
dotnet test tests/Manex.Data.Tests # EF InMemory
dotnet test tests/Manex.IntegrationTests # WebApplicationFactoryIntegration tests spin up the full API in-process with an in-memory DbContext — they don’t need Docker.
Add an EF migration#
dotnet ef migrations add <Name> \
--project src/Manex.Data \
--startup-project src/Manex.Web \
--output-dir MigrationsRead ADR 0004 first. The first real migration must be authored against a scaffolded snapshot that already includes the legacy schema — otherwise its Up() will try to CREATE TABLE for things that already exist in BcomMX.
Common issues#
Jwt:SigningKey must be set (min 32 chars)on startup — yourappsettings.Development.jsonis missing the key, or the env varJwt__SigningKeyisn’t exported.ConnectionStrings:eManEx is missing— same story with the connection string./readyreturns 503 withCannot open database "BcomMX_dev"— Mode A, sandbox is empty. Run the seed script above./readyreturns 503 withLogin failed for user 'sa'— password mismatch between.envSA_PASSWORDand the value embedded inConnectionStrings__eManEx.Globalization Invariant Mode is not supported— you’re on an older commit; pull latest, this was fixed in21a6ddc.- Docker build can’t find
Manex.Contracts.csproj— you’re building fromdeploy/docker/; the build context must be the repo root. - Empty
docs/themes/hugo-bookon a fresh clone → forgot the submodule step. Rungit submodule update --init --recursive. - Migrations
.csfiles empty on fresh clone — expected. Phase 0 ships no migrations (database-first).
Shut it all down#
docker compose -f deploy/docker/docker-compose.yml down -v # -v also drops the sandbox DB volume
# or, for Mode B:
docker compose \
-f deploy/docker/docker-compose.yml \
-f deploy/docker/docker-compose.legacy.yml \
downIf you hit Ctrl-C on dotnet run, the API stops immediately — no cleanup needed.