Skip to content

feat(user): add the Users.IsActive column so a login can be disabled (#956) - #957

Merged
renemadsen merged 1 commit into
masterfrom
feat/956-user-is-active-column
Sep 17, 2026
Merged

renemadsen merged 1 commit into
masterfrom
feat/956-user-is-active-column

Conversation

@renemadsen

Copy link
Copy Markdown
Member

Closes #956. Step 2 of 4 — design: microting/eform-angular-frontend#8072 (spec PR #8073). Step 1 shipped EformUser.IsActive in BasePn 10.0.35 (microting/eFormApi.BasePn#972).

Bumps the BasePn pin 10.0.33 → 10.0.35, maps the property with HasDefaultValue(true), adds the column as tinyint(1) NOT NULL DEFAULT 1, and covers the behaviour with four tests.

⚠️ Release this and sweep the tenants before any plugin bumps BasePn

EformUser.IsActive is a public property on the entity, so EF maps it by convention the moment BasePn ≥ 10.0.35 is anywhere on the load path — the explicit mapping in this PR is not what puts it in the model. NuGet resolves the highest version across the whole graph, so if any single plugin bumps BasePn to 10.0.35 before the host has this migration applied, BaseDbContext's model gains IsActive, the schema does not have it, and every query against Users — login included — fails with Unknown column 'u.IsActive' on every tenant.

Order: release this package → sweep the schemas → then let anything else bump BasePn. This csproj pins BasePn 10.0.35, so taking the new base package always drags the property and the migration together; keep that coupling.

Why the migration is hand-written

dotnet ef migrations add sweeps in four unrelated InsertData operations for the CMS menu seed. That data lives in HasData under Infrastructure/Data/Seed/SeedItems/ but was applied to databases by raw SQL in 20260319062110_AddCmsMenuItem with WHERE NOT EXISTS guards — so the snapshot never learned about it, and there is a permanent 4-operation pending diff on master.

Shipping those operations would insert MenuTemplates/MenuItems with hardcoded Id = 13 into tenants that already have those rows: duplicate primary key, on all 238 schemas. So the snapshot and .Designer.cs carry only the new property.

Verified with EF's own IMigrationsModelDiffer: designer → snapshot is 0 operations, so the next developer's scaffold sees no spurious IsActive diff. (It will still see the CMS drift — that landmine is pre-existing and is being filed separately.)

Safety on production tables

  • The column appends at ordinal 48 of 48 with no AFTER clause, so MariaDB 10.3+/MySQL 8.0.12+ take ALGORITHM=INSTANT — metadata only, row count irrelevant.
  • Generated SQL is a single ALTER TABLE \Users` ADD `IsActive` tinyint(1) NOT NULL DEFAULT TRUE;`.
  • The residual cost is the brief exclusive metadata lock, so run the sweep off-peak with a short session lock_wait_timeout; a long-running transaction holding a shared MDL on Users would otherwise queue every subsequent query behind the blocked ALTER.
  • MySQL/MariaDB DDL implicitly commits, so a crash between the ALTER and the __EFMigrationsHistory insert leaves the column present and unrecorded; the retry then fails with Duplicate column name. If that happens on a tenant, insert the history row rather than re-running.

Tests

Four, observed failing before the mapping and migration existed, then passing; full suite 90/90 locally (testcontainers MariaDB, real migration chain, AsNoTracking() on every read so nothing passes vacuously):

  • the property is mapped, non-nullable, and defaults to true
  • a new account is created enabled — this is the one that proves the = true initializer and the column default agree; it fails if either the initializer is dropped, defaultValue becomes false, or defaultValue is dropped entirely
  • an explicitly disabled account is not re-enabled by the column default
  • disabling an existing account persists

Not covered: the backfill of pre-existing rows, because Database.Migrate() always runs the chain against an empty database. Verified by hand against a MariaDB container (3 of 3 legacy rows → 1), and the same defaultValue: true that does the backfill is what the second test pins.

🤖 Generated with Claude Code

…956)

Step 2 of refusing login to resigned accounts. Bumps BasePn to 10.0.35 (which
carries EformUser.IsActive), maps it with HasDefaultValue(true), and adds the
column as tinyint(1) NOT NULL DEFAULT 1.

defaultValue: true is load-bearing in two directions. It backfills every existing
account as enabled -- a false default would lock out the entire user base on
migrate -- and it keeps a rolling deploy safe: pods still on BasePn 10.0.34 have
no IsActive in their model, so their INSERTs omit the column and the store
default catches them.

The migration is hand-written rather than scaffolded. "dotnet ef migrations add"
sweeps in four unrelated InsertData operations for the CMS menu seed, which lives
in HasData but was applied to databases by raw SQL in 20260319062110_AddCmsMenuItem
instead. Shipping those would fail with duplicate primary keys on every tenant.
The snapshot and designer therefore carry only the new property; verified with
EF's own model differ that designer -> snapshot is zero operations, so the next
scaffold sees no spurious IsActive diff.

Tests cover the mapping and the three round trips that matter: a new account is
created enabled (which is what proves the "= true" initializer and the column
default agree), an explicitly disabled account stays disabled, and disabling an
existing one persists. Observed failing before the mapping and migration existed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 17, 2026 09:10
@renemadsen
renemadsen merged commit 62cfe54 into master Sep 17, 2026
3 checks passed
@renemadsen
renemadsen deleted the feat/956-user-is-active-column branch September 17, 2026 09:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A missing metadata namespace import causes a test compile failure, and legacy-row backfill coverage is still needed.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds EformUser.IsActive support so user logins can be disabled.

Changes:

  • Pins BasePn to 10.0.35.
  • Maps and migrates IsActive with a default of true.
  • Adds persistence and mapping tests.
File summaries
File Summary
Microting.EformAngularFrontendBase/Migrations/BaseDbContextModelSnapshot.cs Records the new property.
Microting.EformAngularFrontendBase/Migrations/20260917085145_AddIsActiveToUser.Designer.cs Captures the migration model.
Microting.EformAngularFrontendBase/Migrations/20260917085145_AddIsActiveToUser.cs Adds the non-nullable column with a true default.
Microting.EformAngularFrontendBase/Microting.EformAngularFrontendBase.csproj Updates the BasePn dependency.
Microting.EformAngularFrontendBase/Infrastructure/Data/BaseDbContext.cs Configures the property mapping and default.
Microting.EformAngularFrontendBase.Tests/UserIsActiveTests.cs Tests mapping and persistence behavior.
Review details
  • Files reviewed: 5/6 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.


using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
Comment on lines +15 to +20
migrationBuilder.AddColumn<bool>(
name: "IsActive",
table: "Users",
type: "tinyint(1)",
nullable: false,
defaultValue: true);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add the Users.IsActive column so a login can be disabled

2 participants