NEXUS
Resources

MiStorage Resources

Repositories, local setup, configuration reference and the safety nets around MiStorage.

mistorage-backendThe API: ASP.NET Core (.NET 10), EF Core, PostgreSQL. Pushed to GitHub and Azure DevOps.GitHubMiStorage (frontend)The Next.js web client: /super-admin, /tenant and the /s/{subdomain} storefront. Deployed independently on AWS Amplify.GitHubAzure DevOps projectSecond remote for both repositories under the MiStorage project.Azure DevOps
Test suite243 xUnit tests in tests/mistorage-backend.Tests, on EF's InMemory provider. Run with dotnet test from the repo root.xUnit
Performance reportLoad testing, EXPLAIN ANALYZE and static review, with the fixes applied (SQL-side billing summary, grouped dashboard query, added indexes). Lives in the frontend repo at perf/PERFORMANCE-REPORT.md.Frontend repo
Security hardening65 security tasks tested and passed, raw-PAN paths removed (SAQ A-EP), CSP and page-integrity reporting on the payment pages.See release notes

Run it locally

  1. 1
    Prerequisites.NET 10 SDK, a reachable PostgreSQL, and the EF Core CLI: dotnet tool install --global dotnet-ef.
  2. 2
    Seed your secretsSecrets are deliberately blank in the tracked appsettings. From the mistorage-backend project directory, set at least the connection string and JWT key with dotnet user-secrets set — the project already carries a UserSecretsId.
  3. 3
    Create the schemadotnet 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.
  4. 4
    Rundotnet run serves http://localhost:5179; dotnet run --launch-profile https adds https://localhost:7206. In Development the Scalar reference is at /scalar/v1.
Seeding a development machine
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 run

Configuration reference

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.

Secrets
SettingEnvironment variableBehaviour when missing
ConnectionStrings:DefaultConnectionConnectionStrings__DefaultConnectionFails fast at startup
Jwt:SecretKey (≥ 32 bytes)Jwt__SecretKeyFails fast at startup
Email:Username / Email:PasswordEmail__Username / Email__PasswordSMTP send fails at runtime
PhoenixGate:MerchantPasswordPhoenixGate__MerchantPasswordFails fast while PhoenixGate:Enabled is true
PhoenixGate:QuickPaymentsKeyPhoenixGate__QuickPaymentsKeyFails fast while PhoenixGate:Enabled is true
PhoenixGate:ResellerPasswordPhoenixGate__ResellerPasswordError on merchant boarding only
Security:FieldEncryptionKey (32 bytes, base64 or hex)Security__FieldEncryptionKeyRegistrations carrying a Social Security Number are refused; everything else keeps working. Never falls back to plaintext.
Security:TrustedProxySecretSecurity__TrustedProxySecretOptional. Unset, the API ignores the relay's X-Client-Ip and every relayed renter login shares one rate-limit bucket.
Behaviour switches
SettingDefaultWhat it does
Saas:Enabledfalsefalse is the single-operator product. true restores the multi-tenant SaaS model whole. Mirror it in the frontend's NEXT_PUBLIC_SAAS_ENABLED.
RenterAuth:RequireEmailVerificationtrueOn: 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:EnabledfalseWhether cards are charged at all. Off, renting still completes and the first invoice is left outstanding for the location to collect.
Email:TransportSmtpMock writes outgoing mail to Email:MockDropPath instead of sending — used while SMTP is unavailable.

Migrations cheat-sheet

Local (stop the API first — it locks bin/)
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 pending
Production — generate a script and read it, never apply directly
ASPNETCORE_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>
  • Audit the generated SQL, not the migration's C#. A backfill written as WHERE "Col" = TIMESTAMPTZ '0001-01-01' matched nothing on Postgres, because Npgsql renders DateTime.MinValue as -infinity.
  • Publish and migrate together, every time. Deploying code ahead of its migration makes every query that maps the new column fail with 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.

Traps that cost real time

  • **The frontend's /api/*-auth/ relay routes handle POST only.** Route a GET or PUT through them and you get 405 in production and success in development.
  • The backend tests use EF's InMemory provider. No ExecuteDelete, no real SQL semantics, no constraint enforcement. Anything involving raw SQL, deletes or types needs a real database to be believed.
  • The frontend has no test framework. 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.
  • A running API locks its own build output, and dotnet ef builds before it does anything. Stop the API before migrations add, database update or migrations script.
  • Read a new API field with a default at the client boundary. In the window between the API and frontend deploys, the UI briefly runs against an API that does not send the field yet.
Found a security issue?Report it through the bug bounty program instead of public channels.