using Microsoft.EntityFrameworkCore; using SmartStack.Application.Common.Interfaces.Identity; using SmartStack.Application.Common.Interfaces.Tenants; using SmartStack.Infrastructure.Persistence; using SmartStack.Infrastructure.Persistence.Extensions; using {{ProjectName}}.Application.Common.Interfaces; namespace {{ProjectName}}.Infrastructure.Persistence; /// /// DbContext for client-specific entities (schema: extensions). /// /// Inherits , which exposes the V1 /// whitelist of Core entities — User, Role, Tenant, TenantOrganisation /// (DbSet Organisations, the shared organisation directory), Department, /// JobTitle, Office, Language, Group — as read-only DbSets via /// ExcludeFromMigrations. This lets your entities declare REAL /// navigation properties and SQL foreign keys to those Core entities. /// /// Example — Order N:1 User + N:1 TenantOrganisation: /// /// public class Order /// { /// public Guid CustomerUserId { get; private set; } /// public Guid OrganisationId { get; private set; } /// public User Customer { get; private set; } = null!; /// public TenantOrganisation Organisation { get; private set; } = null!; /// } /// // and in OrderConfiguration: /// builder.HasOne(o => o.Customer ).WithMany().HasForeignKey(o => o.CustomerUserId); /// builder.HasOne(o => o.Organisation).WithMany().HasForeignKey(o => o.OrganisationId); /// /// /// For Core entities OUTSIDE the V1 whitelist (sessions, tokens, navigation, /// AI / workflow / support internals, audit logs, …) inject /// and use its projection helpers /// (GetUserBasicInfoAsync, GetRoleByIdAsync, …). See /// docs/extensions/cross-context-references.md. /// public class ExtensionsDbContext : SmartStackExtensionDbContext, IExtensionsDbContext { /// /// Forward EVERY optional base dependency — each one silently disables a /// platform seam when omitted: /// /// tenantService — activates the named "Tenant" row filter /// (multi-tenant isolation, TENANT-FILTERS markers below). Without it the /// context is "system": every tenant-scoped query returns EVERY tenant's /// rows. /// currentUserAccessor — activates the named "DataScope" row /// filter (own/assigned lists). Without it the context is "system": never /// scoped, every list returns every row. /// serviceProvider — activates gapless business-code /// allocation for ICodedEntity entities. Without it allocation is /// silently skipped. /// /// At design time (migrations) they are null — the context stays unscoped, /// which is exactly what EF tooling needs. /// public ExtensionsDbContext( DbContextOptions options, ICurrentTenantService? tenantService = null, ICurrentUserAccessor? currentUserAccessor = null, IServiceProvider? serviceProvider = null) : base(options, tenantService, currentUserAccessor, serviceProvider) { } // === YOUR ENTITIES === // Add your DbSet properties here. Example: // public DbSet Orders => Set(); protected override void OnExtensionModelCreating(ModelBuilder modelBuilder) { // All client entities default to the 'extensions' schema (Core whitelist // entities keep their explicit ToTable("...", "core") from the base class). modelBuilder.HasDefaultSchema(SchemaConstants.Extensions); // Load entity configurations from this assembly modelBuilder.ApplyConfigurationsFromAssembly(typeof(ExtensionsDbContext).Assembly); // ── Tenant isolation (named "Tenant" filter) ─────────────────────────── // One ApplyNamed{Strict|Optional}TenantFilter per tenant-scoped entity: // every read (lists, lookups, detail, screen queries) is filtered to the // current tenant — the extension mirror of CoreDbContext's per-entity // tenant filters. Client entity configurations only carry the anonymous // soft-delete filter; WITHOUT its line here an entity's reads are // CROSS-TENANT. The scaffold-entity CLI maintains the markers (entities // referenced fully-qualified); audited by DEV-API-032. // <<< TENANT-FILTERS BEGIN >>> // ApplyNamedStrictTenantFilter(modelBuilder); // <<< TENANT-FILTERS END >>> // ── Row-level data scopes (own/assigned lists) ───────────────────────── // One ApplyDataScopeFilter per scoped entity mounts the named "DataScope" // EF query filter: lists/details are filtered to the current user unless // the caller holds the "{path}.read.all" bypass. The scaffold-data-scope // CLI fills the markers (policies live in {{ProjectName}}.Application/…/Authorization). // <<< DATA-SCOPE-FILTERS BEGIN >>> // ApplyDataScopeFilter(modelBuilder, MyApp.Application.Crm.Pipeline.Authorization.OrderScopePolicy.Instance); // <<< DATA-SCOPE-FILTERS END >>> } }