Repositories, local setup, configuration reference and the safety nets around MiStorage.
main on every push — deploy the API first, or together.GitHubAzure DevOps projectSecond remote for both repositories under the MiStorage project.Azure DevOpsdotnet test from the repo root.xUnitperf/PERFORMANCE-REPORT.md.Frontend repodotnet tool install --global dotnet-ef.mistorage-backend project directory, set at least the connection string and JWT key with dotnet user-secrets set — the project already carries a UserSecretsId.dotnet ef database update applies the migrations and seeds roles, permissions and default settings. No admin user is seeded — register the first system user through the API or the frontend.dotnet run serves http://localhost:5179; dotnet run --launch-profile https adds https://localhost:7206. In Development the Scalar reference is at /scalar/v1.cd mistorage-backend
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Host=localhost;Database=mistorage;Username=postgres;Password=<yours>"
dotnet user-secrets set "Jwt:SecretKey" "<at least 32 bytes of randomness>"
dotnet user-secrets list
dotnet ef database update
dotnet rundotnet ef resolves its connection through the normal configuration chain, so in Development it uses the user-secrets value — your local Postgres. The connection string in appsettings.json is production. Never migrate production with database update; follow the deploy procedure in docs/deploy/README.md.
Every secret is empty in the tracked files on purpose. Supply them from user-secrets in development and from environment variables or a secret store in production. A blank value is never a usable default.
| Setting | Environment variable | Behaviour when missing |
|---|---|---|
ConnectionStrings:DefaultConnection | ConnectionStrings__DefaultConnection | Fails fast at startup |
Jwt:SecretKey (≥ 32 bytes) | Jwt__SecretKey | Fails fast at startup |
Email:Username / Email:Password | Email__Username / Email__Password | SMTP send fails at runtime |
PhoenixGate:MerchantPassword | PhoenixGate__MerchantPassword | Fails fast while PhoenixGate:Enabled is true |
PhoenixGate:QuickPaymentsKey | PhoenixGate__QuickPaymentsKey | Fails fast while PhoenixGate:Enabled is true |
PhoenixGate:ResellerPassword | PhoenixGate__ResellerPassword | Error on merchant boarding only |
Security:FieldEncryptionKey (32 bytes, base64 or hex) | Security__FieldEncryptionKey | Registrations carrying a Social Security Number are refused; everything else keeps working. Never falls back to plaintext. |
Security:TrustedProxySecret | Security__TrustedProxySecret | Optional, but set it on both sides or neither: the frontend's matching variable is TRUSTED_PROXY_SECRET. Unset, the API ignores the relay's X-Client-Ip, so every relayed login — HQ, staff and renter — shares one rate-limit bucket and audit rows name the relay. In production it is set on the API (2026-09-22) but not yet on the frontend. |
| Setting | Default | What it does |
|---|---|---|
Saas:Enabled | false | false is the single-operator product. true restores the multi-tenant SaaS model whole. Mirror it in the frontend's NEXT_PUBLIC_SAAS_ENABLED. |
RenterAuth:RequireEmailVerification | true | On: a renter must confirm their email before the account works. Off: registration creates the account already verified and signs the renter in. Currently off in the tracked files while email delivery is blocked. |
PhoenixGate:Enabled | false | Whether cards are charged at all. Off, renting still completes and the first invoice is left outstanding for the location to collect. |
Email:Transport | Smtp | Mock writes outgoing mail to Email:MockDropPath instead of sending — used while SMTP is unavailable. |
| Variable | What it does |
|---|---|
NEXT_PUBLIC_API_URL | The API base for bearer calls from the browser. Unset, the app runs on mock data. |
API_URL | The API base the server-side auth relay uses; falls back to NEXT_PUBLIC_API_URL. Needed in production builds. |
NEXT_PUBLIC_USE_MOCK | true forces mock data even with an API URL |
NEXT_PUBLIC_SAAS_ENABLED | Mirror of the API's Saas:Enabled; baked in at build time |
NEXT_PUBLIC_PAYMENT_GATEWAY_ORIGINS | Where the browser may send card data (the payment-page CSP connect-src). Must change when the merchant leaves the gateway sandbox — tokenization fails closed. |
NEXT_PUBLIC_SECURITY_REPORT_URL | Where CSP reports go; defaults to the API's /api/security/csp-report |
NEXT_PUBLIC_CSP_MODE | report-only observes the baseline CSP without enforcing it. The payment-page policy always enforces. |
TRUSTED_PROXY_SECRET | Shared with the API's Security:TrustedProxySecret so the relay can report the real client address |
Security:FieldEncryptionKey encrypts the renter's Social Security Number and nothing else. Losing it makes every stored SSN permanently unreadable, by design. Generate it with python -c "import os,base64;print(base64.b64encode(os.urandom(32)).decode())" and back it up wherever the other production secrets live.
There is no CI on the API, and Amplify deploys the frontend on every push to main — so the order is on you.
docs/deploy/backups/ and check it opens.docs/deploy/README.md: an idempotent script, audited, run against a backed-up database. Code ahead of its migration breaks, even for an additive one.main; Amplify builds and serves it in a few minutes. An API ahead of its frontend is the safe order.GET /api/health on both, then sign in as HQ, as staff and as a renter.dotnet ef migrations add <Name> # create a migration from model changes
dotnet ef database update # apply pending migrations (LOCAL only)
dotnet ef migrations remove # undo the last (unapplied) migration
dotnet ef migrations list # what is applied, what is pendingASPNETCORE_ENVIRONMENT=Development dotnet ef migrations script --idempotent \
--project mistorage-backend --output docs/deploy/migrate-idempotent-<yyyyMMdd>.sql
grep -nE "DROP COLUMN|DROP TABLE|RENAME|ALTER COLUMN|DROP CONSTRAINT|NOT NULL" <script>WHERE "Col" = TIMESTAMPTZ '0001-01-01' matched nothing on Postgres, because Npgsql renders DateTime.MinValue as -infinity.42703: column does not exist — that took the HQ dashboard down for four days in September.NOT NULL added to an existing table needs a DEFAULT; narrowing a column fails on any row already longer./api/*-auth/ relay routes handle POST only.** Route a GET or PUT through them and you get 405 in production and success in development.ExecuteDelete, no real SQL semantics, no constraint enforcement. Anything involving raw SQL, deletes or types needs a real database to be believed.tsc, eslint and next build are the whole safety net; changes to auth or checkout are verified by driving a real browser against a real backend.dotnet ef builds before it does anything. Stop the API before migrations add, database update or migrations script.nav-<last URL segment> automatically, so renaming a route renames its target.