NEXUS
Getting Started

Getting started with MiStorage

From an empty machine to the MiStorage API running locally, with a first HQ user signed in and a first location created, in about an hour. Running the web app against it is an optional last step.

Checked against master at d7c422f (backend), main at 5f50dea (web app) on .

Before you start

ToolVersionWhat it is for
GitAny recent oneCloning from Azure DevOps
.NET SDK10 (the projects target net10.0)Building, running and testing the API
EF Core CLI10.x: dotnet tool install --global dotnet-efCreating the database from the migrations
PostgreSQLA local or reachable server. Production runs 17.The API's only database
Node.js and npmNode 20 or laterOnly for the web app, the optional last step
  • Access: membership of the MiStorage project in the micampdev Azure DevOps organisation, which holds both repositories.
  • Nothing else. No cloud account, payment gateway or mail account is needed locally: card charging is off (PhoenixGate:Enabled is false) and Development writes outgoing mail to disk.

Get the code

Two repositories in the MiStorage Azure DevOps project: the API and the Next.js web app.

Clone both (the backend from master)
git clone -b master "https://micampdev.visualstudio.com/MiStorage/_git/MiStorage%20Backend" mistorage-backend
git clone "https://micampdev.visualstudio.com/MiStorage/_git/MiStorage"
Where things are
PathWhat is in it
mistorage-backend/The API project: Program.cs, controllers, services, DTOs, entities, Database/AppDbContext.cs, Migrations/, middleware and the appsettings files
tests/mistorage-backend.Tests/The xUnit suite, on EF's InMemory provider
docs/The production migration procedure, the single-operator model and one write-up per working session
Web app app/, lib/, stores/Routes (/login and /super-admin for HQ, /tenant for staff, /s/{subdomain} for the storefront, the api/*-auth relay), API clients and Zustand stores

Add your secrets

Two values are required before the API will start. In Development they come from user-secrets, and the project already carries a UserSecretsId.

From the backend repository's root
cd mistorage-backend
openssl rand -base64 48    # one way to make a signing key: 32 bytes or more
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Host=localhost;Database=mistorage;Username=postgres;Password=<your-password>"
dotnet user-secrets set "Jwt:SecretKey" "<the key you generated>"
dotnet user-secrets list
KeyFor a first runNotes
ConnectionStrings:DefaultConnectionRequiredYour local PostgreSQL. The API stops at startup without it.
Jwt:SecretKeyRequiredAt least 32 bytes. The API stops at startup if it is missing or shorter.
Security:FieldEncryptionKeyOptionalOnly needed to store a renter's Social Security Number. Without it those registrations are refused and everything else works.
Email:TransportAlready MockSet in appsettings.Development.json: mail is written as .html files to mistorage-backend/mail-drop/ instead of being sent. No SMTP credentials needed.
PhoenixGate:EnabledAlready falseNo card is charged, so no gateway credentials are needed. PhoenixGate:BaseUrl must stay set (the tracked files set it): startup builds HTTP clients from it.

Any key can also come from an environment variable, with __ in place of each colon (ConnectionStrings__DefaultConnection, Jwt__SecretKey). Environment variables win over user-secrets.

Create the database

From mistorage-backend/, with the API stopped
dotnet ef database update    # create the database and apply every migration
dotnet ef migrations list     # all 19 listed, none marked (Pending)
  • dotnet ef runs the app's startup checks to find the connection, so set the two secrets first. It runs as Development, reads your user-secret, and creates mistorage on your server if it does not exist yet.
  • The 19 migrations run from InitialPostgres to AddApplicationFieldPolicy. They seed three HQ roles (SuperAdmin, Admin, Manager), their permissions, the default system settings and the archived subscription plans.
  • No user is seeded. You create the first one through the API below.
  • The API never migrates on startup. After pulling a new migration, stop the API and run dotnet ef database update again.

Run the API

From mistorage-backend/
dotnet run                           # http profile: http://localhost:5179
dotnet run --launch-profile https    # adds https://localhost:7206
AddressWhat it is
http://localhost:5179The API. Both launch profiles set ASPNETCORE_ENVIRONMENT to Development.
/scalar/v1Scalar, the interactive API reference. Development only.
/openapi/v1.jsonThe OpenAPI document behind it
/api/health200 with database reported Healthy once PostgreSQL answers, 503 when it does not

Startup checks its configuration in one pass and stops with a list of everything that is missing (see Troubleshooting below). The payment workers log that they are idle, because the gateway is switched off.

Your first requests

Create the first HQ user, sign in, call a protected endpoint, then create a location and find it on the public storefront.

In a second terminal: Git Bash on Windows (PowerShell's curl is another command), or any Unix shell
API=http://localhost:5179

# 1. Is the API up, and can it reach the database?
curl -s $API/api/health

# 2. Register the first HQ user. The first account on an empty database becomes SuperAdmin.
curl -s -X POST $API/api/auth/register -H "Content-Type: application/json" \
  -d '{"firstName":"Ada","lastName":"Lovelace","email":"ada@example.com","password":"<your-password>","confirmPassword":"<your-password>"}'

# 3. Sign in, then copy data.accessToken from the response
curl -s -X POST $API/api/auth/login -H "Content-Type: application/json" \
  -d '{"email":"ada@example.com","password":"<your-password>"}'
TOKEN=<paste data.accessToken here>

# 4. A protected endpoint (SystemUser policy): who am I?
curl -s $API/api/auth/me -H "Authorization: Bearer $TOKEN"

# 5. Create a location, then read its storefront without a token
curl -s -X POST $API/api/customer -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"companyName":"Demo Storage","contactFirstName":"Ada","contactLastName":"Lovelace","contactEmail":"ada@example.com","subdomain":"demo"}'
curl -s $API/api/public/demo
What sign-in returns (shortened)
{
  "success": true,
  "message": "Login successful.",
  "data": {
    "accessToken": "<jwt>",
    "accessTokenExpiration": "2026-10-27T15:30:35Z",
    "user": {
      "id": "<guid>",
      "email": "ada@example.com",
      "role": "SuperAdmin",
      "permissions": ["Dashboard:View", "Users:View", "..."]
    }
  },
  "errors": null
}
  • Passwords must be 8 to 128 characters, and confirmPassword must match. Anything else answers 400 with an error per field.
  • The access token lasts 15 minutes. Sign-in also sets the HTTP-only mistorage-refresh-token cookie, which curl drops unless you keep a cookie jar, so simply sign in again when the token runs out.
  • Creating a location answers 201. Customer is the API's name for a location. The new one is Active at once and gets an Owner login for its contact email. api/public/demo then returns its profile and size guide, with no units yet.
  • Prefer a browser? Open Scalar at http://localhost:5179/scalar/v1 and send the same requests there, with the token as a bearer token for the protected ones.

Run the web app against it

Optional. The Next.js app uses your local API once two variables point at it.

.env.local in the web app's root
NEXT_PUBLIC_API_URL=http://localhost:5179
API_URL=http://localhost:5179
From the web app's root
npm install
npm run dev     # http://localhost:3000

Open http://localhost:3000/login and sign in as the HQ user you registered. The location you created is under Locations, and Manage Facility on its page opens that location's console. Its storefront is at /s/demo.

Run the checks

Each from its own repository root
dotnet test         # backend: 249 xUnit tests, no database needed

npx tsc --noEmit    # web app: type check
npm run lint        # web app: eslint
npm run build       # web app: production build
  • The backend tests run on EF's InMemory provider, so they need no PostgreSQL and finish in about 15 seconds. A green run says nothing about real SQL: raw SQL, deletes and column types need a real database to be believed.
  • The web app has no test framework. tsc, eslint and next build are the whole safety net. At 5f50dea the type check is clean and lint reports 51 problems (8 errors), all of them older than this guide.

Troubleshooting

What you seeWhy, and what to do
The backend clone holds only README.mdYou cloned the placeholder main. Run git checkout master.
Startup configuration is incomplete - 2 problem(s) foundA required key is missing or too short. The message lists every gap at once; set them with dotnet user-secrets set.
dotnet ef says Unable to create a 'DbContext' of type 'AppDbContext'The same configuration check failed while EF started the app. The line above it names the key.
MSB3027, mistorage-backend.exe is lockedA running API locks its build output, and dotnet ef builds first. Stop the API.
/api/health answers 503 with database UnhealthyPostgreSQL is not running, or the connection string's host, database or password is wrong.
401 with an empty bodyNo token, a malformed one, or one past its 15 minutes (the response then carries X-Token-Expired: true). Sign in again.
403 on an endpoint that existsThe token is for another audience. HQ, location staff and renter tokens are not interchangeable.
400 titled One or more validation errors occurred.Model validation. It answers in ASP.NET's problem-details shape, not the ApiResponse envelope; errors names each field.
429 Too Many RequestsMore than 10 sign-in or registration calls a minute from one address. Wait a minute.
404 on api/subscription-plans or api/invoiceArchived SaaS endpoints answer 404 while Saas:Enabled is false.
A password-reset or verification email never arrivesDevelopment never sends mail. Open the newest .html file in mistorage-backend/mail-drop/.
Restore warns NU1902 and NU1903 about System.Security.Cryptography.Pkcs 9.0.4A known advisory on an indirect dependency of the API (through Microsoft.EntityFrameworkCore.SqlServer). Build and tests still succeed.

Next steps