From 6db200a2fc68748eb2d16eea0e879a6a0d96e06f Mon Sep 17 00:00:00 2001 From: xiong Date: Wed, 29 Jul 2026 13:58:59 +0800 Subject: [PATCH] feat(saas): implement marketplace and tenant onboarding --- README.md | 10 +- .../ApplicationBuilderExtensions.cs | 2 + Tiku.Api/Contracts/AssetAccessDtos.cs | 21 + Tiku.Api/Contracts/AssetDtos.cs | 45 + Tiku.Api/Contracts/AssetManagementDtos.cs | 234 + Tiku.Api/Contracts/AuthDtos.cs | 92 +- Tiku.Api/Contracts/BackgroundJobDtos.cs | 15 + Tiku.Api/Contracts/BackofficeDtos.cs | 36 + Tiku.Api/Contracts/CatalogDtos.cs | 15 +- Tiku.Api/Contracts/CommerceDtos.cs | 87 + Tiku.Api/Contracts/CommissionDtos.cs | 162 + Tiku.Api/Contracts/ContentManagementDtos.cs | 291 + Tiku.Api/Contracts/ContentNavigationDtos.cs | 45 + Tiku.Api/Contracts/CrmDtos.cs | 75 + Tiku.Api/Contracts/DirectContentDtos.cs | 663 + Tiku.Api/Contracts/HealthDtos.cs | 3 + Tiku.Api/Contracts/LearningDtos.cs | 141 + Tiku.Api/Contracts/PlatformAdminDtos.cs | 292 +- Tiku.Api/Contracts/PointDtos.cs | 30 + Tiku.Api/Contracts/ProfileDtos.cs | 99 + Tiku.Api/Contracts/QuestionBankDtos.cs | 44 +- Tiku.Api/Contracts/ReferralDtos.cs | 144 + Tiku.Api/Contracts/SaasBillingDtos.cs | 151 + Tiku.Api/Contracts/ScorelineDtos.cs | 30 + Tiku.Api/Contracts/StudyContentDtos.cs | 33 + Tiku.Api/Contracts/TaxonomyDtos.cs | 24 + Tiku.Api/Contracts/TenantAdminDirectDtos.cs | 654 + Tiku.Api/Contracts/TenantCommerceDtos.cs | 420 + Tiku.Api/Contracts/TenantDtos.cs | 13 +- .../Contracts/TenantFrontendConfigDtos.cs | 24 + Tiku.Api/Contracts/VideoDtos.cs | 57 + Tiku.Api/Controllers/CatalogController.cs | 20 + Tiku.Api/Controllers/CommerceController.cs | 1 + Tiku.Api/Controllers/CommissionController.cs | 1 + Tiku.Api/Controllers/CrmController.cs | 1 + Tiku.Api/Controllers/LearningController.cs | 1 + .../Controllers/PlatformAdminController.cs | 107 +- .../PlatformBillingCallbackController.cs | 45 + .../Controllers/PlatformSaasController.cs | 98 + Tiku.Api/Controllers/PointsController.cs | 1 + Tiku.Api/Controllers/ProfileController.cs | 2 + .../Controllers/QuestionVideosController.cs | 1 + Tiku.Api/Controllers/ReferralController.cs | 1 + Tiku.Api/Controllers/ScorelineController.cs | 1 + Tiku.Api/Controllers/TaxonomyController.cs | 2 + .../Controllers/TenantBillingController.cs | 72 + .../Controllers/TenantCommerceController.cs | 1 + .../Controllers/TenantContentController.cs | 14 + .../TenantContentDirectController.cs | 101 +- .../Controllers/TenantOnboardingController.cs | 25 + Tiku.Api/Controllers/VideosController.cs | 1 + .../Middleware/ExceptionHandlingMiddleware.cs | 39 + .../AccessAuthorizationRequirements.cs | 51 +- .../Security/EndpointAuthorizationMetadata.cs | 24 +- .../Security/SaasFeatureEndpointMetadata.cs | 113 + .../wwwroot/platform-admin/spec-contract.js | 6 +- .../Assets/IAssetManagementService.cs | 5 + .../Backoffice/BackofficeModels.cs | 6 +- .../PlatformAdmin/PlatformAdminModels.cs | 102 +- .../PlatformBillingContracts.cs | 242 + .../Security/BackendPermissions.cs | 22 +- .../Security/CapabilityOperation.cs | 7 + .../FeatureQuotaConsumptionExtensions.cs | 26 + .../Security/ICapabilityAccessEvaluator.cs | 21 - .../Security/IFeatureAccessService.cs | 64 + .../IFeatureUsageReconciliationService.cs | 31 + .../Security/ProductModuleCatalog.cs | 23 - .../Security/SaasFeatureCatalog.cs | 138 + Tiku.Application/Security/TikuPolicies.cs | 1 + .../Tenancy/TenantFrontendConfigModels.cs | 4 +- .../Tenancy/TenantOnboardingModels.cs | 19 + Tiku.Domain/Commerce/CommerceEntities.cs | 23 - Tiku.Domain/Operations/OperationsEntities.cs | 2 +- .../Platform/PlatformOperationsEntities.cs | 127 +- Tiku.Domain/Platform/SaasBillingEntities.cs | 244 + .../Tenancy/TenantConfigurationEntities.cs | 1 + .../Assets/AssetManagementService.cs | 120 +- Tiku.Infrastructure/Auth/AuthService.cs | 16 +- .../Auth/SmsVerificationService.cs | 28 +- .../Backoffice/BackofficeService.cs | 169 +- .../DevelopmentPlatformAdminSeeder.cs | 19 +- .../Bootstrap/PlatformAdminBootstrapper.cs | 19 +- .../Content/DirectContentService.cs | 46 +- Tiku.Infrastructure/DependencyInjection.cs | 15 +- .../Jobs/BackgroundJobService.cs | 86 +- .../Configurations/CommerceConfigurations.cs | 29 - .../OperationsConfigurations.cs | 9 +- .../PlatformOperationsConfigurations.cs | 184 +- .../SaasBillingConfigurations.cs | 349 + .../Configurations/TenancyConfigurations.cs | 3 + .../20260728031410_InitialSchema.Designer.cs | 17657 --------------- ..._AddCommerceAdjustmentVouchers.Designer.cs | 17959 --------------- ...728062148_AddCommerceAdjustmentVouchers.cs | 213 - ...70357_AddVideoPlaybackProgress.Designer.cs | 18103 --------------- ...20260728070357_AddVideoPlaybackProgress.cs | 111 - ...093337_RemoveMfaAuthentication.Designer.cs | 18099 --------------- .../20260728093337_RemoveMfaAuthentication.cs | 29 - ...dDistributedSecurityFoundation.Designer.cs | 18569 ---------------- ...012205_AddDistributedSecurityFoundation.cs | 297 - ...60729025218_EnforceProductModuleCatalog.cs | 64 - ... 20260729053904_InitialSchema.Designer.cs} | 2599 ++- ...ema.cs => 20260729053904_InitialSchema.cs} | 2016 +- .../MigrationBuilderTenantGuardExtensions.cs | 13 + .../Migrations/TikuDbContextModelSnapshot.cs | 2595 ++- .../PostgreSqlSaasCatalogConstraintSql.cs | 224 + .../Persistence/TikuDbContext.cs | 34 +- .../PlatformAdmin/PlatformAdminService.cs | 423 +- .../PlatformBillingAdminService.cs | 147 + .../PlatformBillingNotificationService.cs | 56 + .../PlatformBillingPaymentGateway.cs | 91 + .../PlatformBillingSettlementService.cs | 268 + .../SaasCatalogAdminService.cs | 350 + .../SaasSubscriptionLifecycleService.cs | 272 + .../PlatformBilling/TenantBillingService.cs | 505 + .../PublicQuestionAccessPolicy.cs | 20 +- .../Security/CapabilityAccessEvaluator.cs | 90 - .../Security/FeatureAccessService.cs | 364 + .../FeatureUsageReconciliationService.cs | 213 + .../Tenancy/TenantFrontendConfigService.cs | 30 +- .../Tenancy/TenantOnboardingService.cs | 81 + .../TenantAdmin/TenantAdminDirectService.cs | 166 +- Tiku.IntegrationTests/Api/ApiTestFactory.cs | 210 +- .../Api/AssetManagementEndpointTests.cs | 187 +- .../Api/AuthSessionLifecycleTests.cs | 2 +- .../Api/AuthorizationManifestTests.cs | 42 +- .../Api/BackofficeUiBootstrapTests.cs | 2 +- .../Api/CapabilityAuthorizationTests.cs | 194 +- .../Api/CurrentQuotaEnforcementTests.cs | 335 + ...basePermissionServiceAuthorizationTests.cs | 4 +- .../Api/PlatformAdminEndpointTests.cs | 160 +- .../Api/PlatformBillingCallbackTests.cs | 272 + .../Api/SaasBillingLifecycleTests.cs | 962 + .../Api/TenantPublicEndpointTests.cs | 2 +- .../MigrationExecutionTests.cs | 11 +- .../PersistenceModelTests.cs | 86 +- .../PhaseThreeTenantIsolationTests.cs | 64 +- Tiku.UnitTests/Auth/AuthServiceTests.cs | 43 +- .../Auth/SmsVerificationServiceTests.cs | 114 +- Tiku.Worker/Program.cs | 6 + Tiku.Worker/Worker.cs | 29 +- Tiku.Worker/appsettings.json | 10 + ...entication-authorization-hardening-plan.md | 2 +- .../saas-product-and-api-roadmap.md | 53 +- docs/migration-roadmap.md | 12 +- ...phase-9-saas-marketplace-and-onboarding.md | 50 + 145 files changed, 16862 insertions(+), 94529 deletions(-) create mode 100644 Tiku.Api/Contracts/SaasBillingDtos.cs create mode 100644 Tiku.Api/Controllers/PlatformBillingCallbackController.cs create mode 100644 Tiku.Api/Controllers/PlatformSaasController.cs create mode 100644 Tiku.Api/Controllers/TenantBillingController.cs create mode 100644 Tiku.Api/Controllers/TenantOnboardingController.cs create mode 100644 Tiku.Api/Security/SaasFeatureEndpointMetadata.cs create mode 100644 Tiku.Application/PlatformBilling/PlatformBillingContracts.cs create mode 100644 Tiku.Application/Security/CapabilityOperation.cs create mode 100644 Tiku.Application/Security/FeatureQuotaConsumptionExtensions.cs delete mode 100644 Tiku.Application/Security/ICapabilityAccessEvaluator.cs create mode 100644 Tiku.Application/Security/IFeatureAccessService.cs create mode 100644 Tiku.Application/Security/IFeatureUsageReconciliationService.cs delete mode 100644 Tiku.Application/Security/ProductModuleCatalog.cs create mode 100644 Tiku.Application/Security/SaasFeatureCatalog.cs create mode 100644 Tiku.Application/Tenancy/TenantOnboardingModels.cs create mode 100644 Tiku.Domain/Platform/SaasBillingEntities.cs create mode 100644 Tiku.Infrastructure/Persistence/Configurations/SaasBillingConfigurations.cs delete mode 100644 Tiku.Infrastructure/Persistence/Migrations/20260728031410_InitialSchema.Designer.cs delete mode 100644 Tiku.Infrastructure/Persistence/Migrations/20260728062148_AddCommerceAdjustmentVouchers.Designer.cs delete mode 100644 Tiku.Infrastructure/Persistence/Migrations/20260728062148_AddCommerceAdjustmentVouchers.cs delete mode 100644 Tiku.Infrastructure/Persistence/Migrations/20260728070357_AddVideoPlaybackProgress.Designer.cs delete mode 100644 Tiku.Infrastructure/Persistence/Migrations/20260728070357_AddVideoPlaybackProgress.cs delete mode 100644 Tiku.Infrastructure/Persistence/Migrations/20260728093337_RemoveMfaAuthentication.Designer.cs delete mode 100644 Tiku.Infrastructure/Persistence/Migrations/20260728093337_RemoveMfaAuthentication.cs delete mode 100644 Tiku.Infrastructure/Persistence/Migrations/20260729012205_AddDistributedSecurityFoundation.Designer.cs delete mode 100644 Tiku.Infrastructure/Persistence/Migrations/20260729012205_AddDistributedSecurityFoundation.cs delete mode 100644 Tiku.Infrastructure/Persistence/Migrations/20260729025218_EnforceProductModuleCatalog.cs rename Tiku.Infrastructure/Persistence/Migrations/{20260729025218_EnforceProductModuleCatalog.Designer.cs => 20260729053904_InitialSchema.Designer.cs} (92%) rename Tiku.Infrastructure/Persistence/Migrations/{20260728031410_InitialSchema.cs => 20260729053904_InitialSchema.cs} (87%) create mode 100644 Tiku.Infrastructure/Persistence/PostgreSqlSaasCatalogConstraintSql.cs create mode 100644 Tiku.Infrastructure/PlatformBilling/PlatformBillingAdminService.cs create mode 100644 Tiku.Infrastructure/PlatformBilling/PlatformBillingNotificationService.cs create mode 100644 Tiku.Infrastructure/PlatformBilling/PlatformBillingPaymentGateway.cs create mode 100644 Tiku.Infrastructure/PlatformBilling/PlatformBillingSettlementService.cs create mode 100644 Tiku.Infrastructure/PlatformBilling/SaasCatalogAdminService.cs create mode 100644 Tiku.Infrastructure/PlatformBilling/SaasSubscriptionLifecycleService.cs create mode 100644 Tiku.Infrastructure/PlatformBilling/TenantBillingService.cs delete mode 100644 Tiku.Infrastructure/Security/CapabilityAccessEvaluator.cs create mode 100644 Tiku.Infrastructure/Security/FeatureAccessService.cs create mode 100644 Tiku.Infrastructure/Security/FeatureUsageReconciliationService.cs create mode 100644 Tiku.Infrastructure/Tenancy/TenantOnboardingService.cs create mode 100644 Tiku.IntegrationTests/Api/CurrentQuotaEnforcementTests.cs create mode 100644 Tiku.IntegrationTests/Api/PlatformBillingCallbackTests.cs create mode 100644 Tiku.IntegrationTests/Api/SaasBillingLifecycleTests.cs create mode 100644 docs/migration/phase-9-saas-marketplace-and-onboarding.md diff --git a/README.md b/README.md index ef10a90..f92b6df 100644 --- a/README.md +++ b/README.md @@ -83,12 +83,15 @@ PostgreSQL guard 负责 EF 无法表达的跨表租户不变量: - `TenantQuestionReference` 只能引用平台公共题或当前租户私题; - `TaxonomyNode` 父节点只能属于平台主体或当前租户; - 需要读取 `tenants.mode` 或比较 owner 关系的条件约束。 +- 已发布套餐版本及其模块、额度清单不可修改;订阅套餐类型与订单快照必须一致。 +- 套餐控制 Feature,角色控制 Permission,菜单仅按有效权限生成导航;内容后台按题库、词汇、手册、视频、分数线和站点内容分模块授权。 +- 员工、学生、私有题、存储、导入、导出和短信额度在业务写入时原子消费,Worker 定期按真实数据校准当前量。 维护规则: -1. SQL 只放在 `Tiku.Infrastructure/Persistence/PostgreSqlTenantConstraintSql.cs`。 -2. Migration 只调用 `migrationBuilder.EnsureTenantIsolationGuards()` / `DropTenantIsolationGuards()`。 -3. 重建 `InitialSchema` 后,`Up()` 末尾必须调用 `EnsureTenantIsolationGuards()`,`Down()` 开头必须调用 `DropTenantIsolationGuards()`。 +1. 租户 SQL 放在 `PostgreSqlTenantConstraintSql.cs`,SaaS 商品 SQL 放在 `PostgreSqlSaasCatalogConstraintSql.cs`。 +2. Migration 只调用集中 helper,不复制 trigger SQL。 +3. 重建 `InitialSchema` 后,`Up()` 末尾必须调用 `EnsureTenantIsolationGuards()` 和 `EnsureSaasCatalogGuards()`;`Down()` 先调用对应 Drop helper。 4. 新增 guard 前先判断能否用 EF FK / unique / check 表达;表达不了才加 PostgreSQL guard。 5. 每个 guard 必须有 migration script 断言和真实 PostgreSQL 越权测试。 @@ -110,6 +113,7 @@ PostgreSQL guard 负责 EF 无法表达的跨表租户不变量: - [迁移路线与剩余范围](docs/migration-roadmap.md) - [API 契约基线说明](docs/migration/contracts/README.md) - [AI 底座设计](docs/migration/phase-8-ai-foundation.md) +- [第九阶段:SaaS 模块商城与租户交付闭环](docs/migration/phase-9-saas-marketplace-and-onboarding.md) ## 常用命令 diff --git a/Tiku.Api/Configuration/ApplicationBuilderExtensions.cs b/Tiku.Api/Configuration/ApplicationBuilderExtensions.cs index 5c5d229..5a3ec18 100644 --- a/Tiku.Api/Configuration/ApplicationBuilderExtensions.cs +++ b/Tiku.Api/Configuration/ApplicationBuilderExtensions.cs @@ -3,6 +3,7 @@ using Serilog; using Tiku.Api.Logging; using Tiku.Api.Middleware; using Tiku.Api.Options; +using Tiku.Api.Security; namespace Tiku.Api.Configuration; @@ -35,6 +36,7 @@ public static class ApplicationBuilderExtensions app.UseRateLimiter(); app.UseMiddleware(); app.UseAuthorization(); + app.UseMiddleware(); app.MapControllers(); return app; diff --git a/Tiku.Api/Contracts/AssetAccessDtos.cs b/Tiku.Api/Contracts/AssetAccessDtos.cs index 00aaa9c..880ec51 100644 --- a/Tiku.Api/Contracts/AssetAccessDtos.cs +++ b/Tiku.Api/Contracts/AssetAccessDtos.cs @@ -6,15 +6,27 @@ using Tiku.Domain.Content; namespace Tiku.Api.Contracts; +/// +/// 资产访问签名查询参数。 +/// public sealed class AssetAccessQueryDto { + /// + /// 租户编码。 + /// [StringLength(100)] public string? TenantCode { get; set; } + /// + /// 有效期秒数。 + /// [Range(1, 7200)] public int? ExpiresInSeconds { get; set; } } +/// +/// 资产访问签名响应。 +/// public sealed record AssetAccessResponseDto( ContentAssetAccessSummaryDto Item, AssetAccessPrincipalDto Access, @@ -34,6 +46,9 @@ public sealed record AssetAccessResponseDto( } } +/// +/// 内容资产访问摘要。 +/// public sealed record ContentAssetAccessSummaryDto( Guid Id, ContentAssetType AssetType, @@ -60,11 +75,17 @@ public sealed record ContentAssetAccessSummaryDto( } } +/// +/// 资产访问Principal请求 DTO。 +/// public sealed record AssetAccessPrincipalDto( Guid? UserId, bool IsMember, bool HasSvip); +/// +/// SignedStorageUrl请求 DTO。 +/// public sealed record SignedStorageUrlDto( string Provider, string? Bucket, diff --git a/Tiku.Api/Contracts/AssetDtos.cs b/Tiku.Api/Contracts/AssetDtos.cs index 3326bf1..a1b7b3b 100644 --- a/Tiku.Api/Contracts/AssetDtos.cs +++ b/Tiku.Api/Contracts/AssetDtos.cs @@ -3,39 +3,84 @@ using Tiku.Application.Assets; namespace Tiku.Api.Contracts; +/// +/// 资产查询参数。 +/// public sealed class AssetQueryDto { + /// + /// 租户编码。 + /// [StringLength(100)] public string? TenantCode { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 科目 ID。 + /// public Guid? SubjectId { get; set; } + /// + /// 分类 ID。 + /// public Guid? CategoryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 题目 ID。 + /// public Guid? QuestionId { get; set; } + /// + /// 资产 ID。 + /// public Guid? AssetId { get; set; } + /// + /// 资产类型。 + /// [StringLength(50)] public string? AssetType { get; set; } + /// + /// 分类。 + /// [StringLength(100)] public string? Category { get; set; } + /// + /// 资产键。 + /// [StringLength(200)] public string? AssetKey { get; set; } + /// + /// 关键字。 + /// [StringLength(100)] public string? Keyword { get; set; } + /// + /// 是否包含锁定资源。 + /// public bool IncludeLocked { get; set; } + /// + /// 是否包含停用数据。 + /// public bool IncludeInactive { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } diff --git a/Tiku.Api/Contracts/AssetManagementDtos.cs b/Tiku.Api/Contracts/AssetManagementDtos.cs index 1ce3894..615bac8 100644 --- a/Tiku.Api/Contracts/AssetManagementDtos.cs +++ b/Tiku.Api/Contracts/AssetManagementDtos.cs @@ -5,64 +5,130 @@ using Tiku.Domain.Common; namespace Tiku.Api.Contracts; +/// +/// 创建资产上传签名请求。 +/// public sealed class AssetUploadSignDto { + /// + /// 资产 ID。 + /// public Guid? AssetId { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 科目 ID。 + /// public Guid? SubjectId { get; set; } + /// + /// 分类 ID。 + /// public Guid? CategoryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 资产键。 + /// [StringLength(200)] public string? AssetKey { get; set; } + /// + /// 标题。 + /// [StringLength(300)] public string? Title { get; set; } + /// + /// 分类。 + /// [StringLength(100)] public string? Category { get; set; } + /// + /// 说明。 + /// [StringLength(1000)] public string? Description { get; set; } + /// + /// 文件名。 + /// [Required] [StringLength(500)] public string FileName { get; set; } = string.Empty; + /// + /// MIME 类型。 + /// [Required] [StringLength(200)] public string MimeType { get; set; } = string.Empty; + /// + /// 文件大小,单位为字节。 + /// [Range(0, long.MaxValue)] public long? FileSizeBytes { get; set; } + /// + /// SHA-256 校验值。 + /// [RegularExpression("^[A-Fa-f0-9]{64}$")] public string? ChecksumSha256 { get; set; } + /// + /// 资产类型。 + /// [StringLength(50)] public string? AssetType { get; set; } + /// + /// 可见性。 + /// [StringLength(50)] public string? Visibility { get; set; } + /// + /// 是否公开。 + /// public bool? IsPublic { get; set; } + /// + /// 服务提供方。 + /// [StringLength(50)] public string? Provider { get; set; } + /// + /// 存储桶。 + /// [StringLength(200)] public string? Bucket { get; set; } + /// + /// 对象存储键。 + /// [StringLength(1000)] public string? ObjectKey { get; set; } + /// + /// 有效期秒数。 + /// [Range(1, 3600)] public int? ExpiresInSeconds { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public AssetUploadSignCommand ToCommand() @@ -92,17 +158,32 @@ public sealed class AssetUploadSignDto } } +/// +/// 确认资产上传请求。 +/// public sealed class AssetUploadConfirmDto { + /// + /// 资产 ID。 + /// [Required] public Guid AssetId { get; set; } + /// + /// MIME 类型。 + /// [StringLength(200)] public string? MimeType { get; set; } + /// + /// 文件大小,单位为字节。 + /// [Range(0, long.MaxValue)] public long? FileSizeBytes { get; set; } + /// + /// SHA-256 校验值。 + /// [RegularExpression("^[A-Fa-f0-9]{64}$")] public string? ChecksumSha256 { get; set; } @@ -112,34 +193,118 @@ public sealed class AssetUploadConfirmDto } } +/// +/// 新增或更新内容资产请求。 +/// public sealed class UpsertAssetDto { + /// + /// 资产 ID。 + /// public Guid? AssetId { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 科目 ID。 + /// public Guid? SubjectId { get; set; } + /// + /// 分类 ID。 + /// public Guid? CategoryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 资产键。 + /// public string? AssetKey { get; set; } + /// + /// 标题。 + /// public string? Title { get; set; } + /// + /// 分类。 + /// public string? Category { get; set; } + /// + /// 说明。 + /// public string? Description { get; set; } + /// + /// 文件名。 + /// public string? FileName { get; set; } + /// + /// CDN 地址。 + /// public string? CdnUrl { get; set; } + /// + /// 是否公开。 + /// public bool? IsPublic { get; set; } + /// + /// 资产类型。 + /// public string? AssetType { get; set; } + /// + /// 可见性。 + /// public string? Visibility { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 服务提供方。 + /// public string? Provider { get; set; } + /// + /// 存储桶。 + /// public string? Bucket { get; set; } + /// + /// 对象存储键。 + /// public string? ObjectKey { get; set; } + /// + /// MIME 类型。 + /// public string? MimeType { get; set; } + /// + /// 文件大小,单位为字节。 + /// public long? FileSizeBytes { get; set; } + /// + /// SHA-256 校验值。 + /// public string? ChecksumSha256 { get; set; } + /// + /// 预览地址。 + /// public string? PreviewUrl { get; set; } + /// + /// 预览对象存储键。 + /// public string? PreviewObjectKey { get; set; } + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 访问规则。 + /// public JsonElement AccessRules { get; set; } = JsonDefaults.Object(); + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public UpsertAssetCommand ToCommand() @@ -175,11 +340,20 @@ public sealed class UpsertAssetDto } } +/// +/// 创建资产访问签名请求。 +/// public sealed class AssetAccessSignDto { + /// + /// 资产 ID。 + /// [Required] public Guid AssetId { get; set; } + /// + /// 有效期秒数。 + /// [Range(60, 3600)] public int? ExpiresInSeconds { get; set; } @@ -189,31 +363,64 @@ public sealed class AssetAccessSignDto } } +/// +/// 资产管理查询参数。 +/// public sealed class AssetManagementQueryDto { + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 科目 ID。 + /// public Guid? SubjectId { get; set; } + /// + /// 分类 ID。 + /// public Guid? CategoryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 资产类型。 + /// [StringLength(50)] public string? AssetType { get; set; } + /// + /// 分类。 + /// [StringLength(100)] public string? Category { get; set; } + /// + /// 上传状态。 + /// [StringLength(50)] public string? UploadStatus { get; set; } + /// + /// 安全扫描状态。 + /// [StringLength(50)] public string? SecurityScanStatus { get; set; } + /// + /// 关键字。 + /// [StringLength(100)] public string? Keyword { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -233,11 +440,23 @@ public sealed class AssetManagementQueryDto } } +/// +/// 资产事件查询参数。 +/// public sealed class AssetEventQueryDto { + /// + /// 资产 ID。 + /// public Guid? AssetId { get; set; } + /// + /// 用户 ID。 + /// public Guid? UserId { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -247,17 +466,32 @@ public sealed class AssetEventQueryDto } } +/// +/// 导入任务查询参数。 +/// public sealed class ImportJobQueryDto { + /// + /// 状态。 + /// [StringLength(50)] public string? Status { get; set; } + /// + /// 导入Type。 + /// [StringLength(50)] public string? ImportType { get; set; } + /// + /// 来源格式。 + /// [StringLength(50)] public string? SourceFormat { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } diff --git a/Tiku.Api/Contracts/AuthDtos.cs b/Tiku.Api/Contracts/AuthDtos.cs index 2c0fa04..42525f2 100644 --- a/Tiku.Api/Contracts/AuthDtos.cs +++ b/Tiku.Api/Contracts/AuthDtos.cs @@ -6,32 +6,38 @@ using Tiku.Domain.Tenancy; namespace Tiku.Api.Contracts; /// -/// 手机号密码登录请求。 +/// PasswordLogin请求 DTO。 /// public sealed class PasswordLoginDto { + /// + /// 认证域。 + /// [Required] public AuthRealm? Realm { get; set; } /// - /// 平台控制域名登录时使用的租户代码;自定义域名登录可省略。 + /// 租户编码。 /// [StringLength(100)] [Description("平台控制域名登录时使用的租户代码;自定义域名登录可省略。")] public string? TenantCode { get; set; } /// - /// 账号标识。tenant 可使用手机号,platform 可使用邮箱或用户名。 + /// 账号标识。 /// [StringLength(320)] [Description("账号标识。tenant 可使用手机号,platform 可使用邮箱或用户名。")] public string? Identifier { get; set; } + /// + /// 兼容手机号字段;新客户端应使用 identifier。 + /// [StringLength(32)] [Description("兼容手机号字段;新客户端应使用 identifier。")] public string? Phone { get; set; } /// - /// 用户密码。 + /// 密码。 /// [Required] [StringLength(128, MinimumLength = 8)] @@ -40,21 +46,24 @@ public sealed class PasswordLoginDto } /// -/// 短信验证码登录请求。 +/// SmsLogin请求 DTO。 /// public sealed class SmsLoginDto { + /// + /// 认证域。 + /// [Required] public AuthRealm? Realm { get; set; } /// - /// 平台控制域名登录时使用的租户代码;自定义域名登录可省略。 + /// 租户编码。 /// [StringLength(100)] [Description("平台控制域名登录时使用的租户代码;自定义域名登录可省略。")] public string? TenantCode { get; set; } /// - /// 中国大陆手机号。 + /// 手机号。 /// [Required] [StringLength(32)] @@ -62,7 +71,7 @@ public sealed class SmsLoginDto public string Phone { get; set; } = string.Empty; /// - /// 收到的短信验证码。 + /// 编码。 /// [Required] [StringLength(12, MinimumLength = 4)] @@ -70,38 +79,56 @@ public sealed class SmsLoginDto public string Code { get; set; } = string.Empty; } +/// +/// 发送短信验证码请求。 +/// public sealed class SendSmsCodeDto { + /// + /// 认证域。 + /// [Required] public AuthRealm? Realm { get; set; } + /// + /// 租户编码。 + /// [StringLength(100)] public string? TenantCode { get; set; } + /// + /// 手机号。 + /// [Required] [StringLength(32)] public string Phone { get; set; } = string.Empty; + /// + /// 设备 ID。 + /// [StringLength(256)] public string? DeviceId { get; set; } } /// -/// OAuth code 登录请求。 +/// O认证编码请求 DTO。 /// public sealed class OAuthCodeDto { + /// + /// 认证域。 + /// [Required] public AuthRealm? Realm { get; set; } /// - /// 平台控制域名登录时使用的租户代码;自定义域名登录可省略。 + /// 租户编码。 /// [StringLength(100)] [Description("平台控制域名登录时使用的租户代码;自定义域名登录可省略。")] public string? TenantCode { get; set; } /// - /// OAuth 平台返回的一次性授权 code。 + /// 编码。 /// [Required] [StringLength(512)] @@ -109,13 +136,13 @@ public sealed class OAuthCodeDto public string Code { get; set; } = string.Empty; /// - /// 客户端可提供的公开用户资料,不允许包含 token/secret。当前后端先保留模板字段,后续按业务需要逐步使用。 + /// 公开用户资料。 /// [Description("客户端可提供的公开用户资料,不允许包含 token/secret。当前后端先保留模板字段,后续按业务需要逐步使用。")] public Dictionary? Profile { get; set; } /// - /// 微信用户资料语言,例如 zh_CN。当前微信小程序登录不会使用该字段。 + /// 语言。 /// [StringLength(20)] [Description("微信用户资料语言,例如 zh_CN。当前微信小程序登录不会使用该字段。")] @@ -123,12 +150,12 @@ public sealed class OAuthCodeDto } /// -/// refresh token 请求。 +/// 刷新会话请求 DTO。 /// public sealed class RefreshSessionDto { /// - /// refresh token。服务端只存储哈希,明文只返回给客户端一次。 + /// 刷新令牌。 /// [Required] [StringLength(2048)] @@ -137,7 +164,7 @@ public sealed class RefreshSessionDto } /// -/// 登录成功后的用户、租户成员和令牌信息。 +/// Authenticated用户请求 DTO。 /// public sealed class AuthenticatedUserDto { @@ -157,19 +184,22 @@ public sealed class AuthenticatedUserDto public string? Email { get; init; } /// - /// 用户显示名称。 + /// 名称。 /// public string? Name { get; init; } + /// + /// 认证域。 + /// public AuthRealm Realm { get; init; } /// - /// 当前登录租户成员摘要。 + /// 租户成员摘要。 /// public TenantMembershipSummary? Tenant { get; init; } /// - /// access token 和 refresh token。 + /// 访问令牌和刷新令牌。 /// public AuthTokenPair Tokens { get; init; } = default!; @@ -188,11 +218,26 @@ public sealed class AuthenticatedUserDto } } +/// +/// 登录认证结果。 +/// public sealed class AuthenticationResultDto { + /// + /// 状态。 + /// public AuthenticationStatus Status { get; init; } + /// + /// 用户信息。 + /// public AuthenticatedUserDto? User { get; init; } + /// + /// 挑战令牌。 + /// public string? ChallengeToken { get; init; } + /// + /// 挑战令牌过期时间。 + /// public DateTimeOffset? ChallengeExpiresAt { get; init; } public static AuthenticationResultDto FromApplication(AuthenticationResult result) => new() @@ -204,12 +249,21 @@ public sealed class AuthenticationResultDto }; } +/// +/// 首次登录必改密码请求。 +/// public sealed class RequiredPasswordChangeDto { + /// + /// 挑战令牌。 + /// [Required] [StringLength(2048)] public string ChallengeToken { get; set; } = string.Empty; + /// + /// 新密码。 + /// [Required] [StringLength(128, MinimumLength = 8)] public string NewPassword { get; set; } = string.Empty; diff --git a/Tiku.Api/Contracts/BackgroundJobDtos.cs b/Tiku.Api/Contracts/BackgroundJobDtos.cs index 04be121..7c0c8d9 100644 --- a/Tiku.Api/Contracts/BackgroundJobDtos.cs +++ b/Tiku.Api/Contracts/BackgroundJobDtos.cs @@ -3,11 +3,26 @@ using Tiku.Application.Jobs; namespace Tiku.Api.Contracts; +/// +/// 创建租户后台任务请求。 +/// public sealed class CreateBackgroundJobDto { + /// + /// 任务类型。 + /// public string JobType { get; set; } = string.Empty; + /// + /// 任务载荷。 + /// public JsonElement Payload { get; set; } + /// + /// 计划运行时间。 + /// public DateTimeOffset? RunAfter { get; set; } + /// + /// 最大重试次数。 + /// public int MaxRetries { get; set; } = 3; public CreateBackgroundJobCommand ToCommand(Guid tenantId) diff --git a/Tiku.Api/Contracts/BackofficeDtos.cs b/Tiku.Api/Contracts/BackofficeDtos.cs index 4b26447..0b9641b 100644 --- a/Tiku.Api/Contracts/BackofficeDtos.cs +++ b/Tiku.Api/Contracts/BackofficeDtos.cs @@ -4,13 +4,34 @@ using Tiku.Domain.Operations; namespace Tiku.Api.Contracts; +/// +/// 创建或更新后台角色请求。 +/// public sealed class UpsertBackofficeRoleDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 编码。 + /// public string Code { get; set; } = string.Empty; + /// + /// 名称。 + /// public string Name { get; set; } = string.Empty; + /// + /// 状态。 + /// public BackendRoleStatus Status { get; set; } = BackendRoleStatus.Active; + /// + /// 说明。 + /// public string? Description { get; set; } + /// + /// 数据范围配置。 + /// public JsonElement? DataScope { get; set; } public UpsertBackofficeRoleCommand ToCommand() @@ -19,9 +40,18 @@ public sealed class UpsertBackofficeRoleDto } } +/// +/// 替换角色权限绑定请求。 +/// public sealed class ReplaceRoleBindingsDto { + /// + /// 权限编码列表。 + /// public IReadOnlyCollection PermissionCodes { get; set; } = []; + /// + /// 菜单编码列表。 + /// public IReadOnlyCollection MenuCodes { get; set; } = []; public ReplaceRoleBindingsCommand ToCommand(Guid roleId) @@ -30,8 +60,14 @@ public sealed class ReplaceRoleBindingsDto } } +/// +/// 替换用户角色请求。 +/// public sealed class ReplaceUserRolesDto { + /// + /// 角色 ID 列表。 + /// public IReadOnlyCollection RoleIds { get; set; } = []; public ReplaceUserRolesCommand ToCommand(Guid userId) diff --git a/Tiku.Api/Contracts/CatalogDtos.cs b/Tiku.Api/Contracts/CatalogDtos.cs index 4558010..b5338a8 100644 --- a/Tiku.Api/Contracts/CatalogDtos.cs +++ b/Tiku.Api/Contracts/CatalogDtos.cs @@ -3,10 +3,13 @@ using Tiku.Application.Catalog; namespace Tiku.Api.Contracts; +/// +/// 目录查询参数。 +/// public sealed class CatalogQueryDto { /// - /// 租户编码。未登录公开查询时使用;已登录时优先使用当前 token 的租户。 + /// 租户编码。 /// [StringLength(100)] public string? TenantCode { get; set; } @@ -17,7 +20,7 @@ public sealed class CatalogQueryDto public Guid? RegionId { get; set; } /// - /// 功能模块 ID。 + /// ModuleId。 /// public Guid? ModuleId { get; set; } @@ -44,24 +47,24 @@ public sealed class CatalogQueryDto public Guid? SubjectId { get; set; } /// - /// 导航节点 ID。 + /// 节点 ID。 /// public Guid? NodeId { get; set; } /// - /// 名称关键词。 + /// 关键字。 /// [StringLength(100)] public string? Keyword { get; set; } /// - /// 类型过滤,例如 cultural、professional、chapter、paper。 + /// 类型。 /// [StringLength(50)] public string? Type { get; set; } /// - /// 返回条数上限。 + /// 返回数量上限。 /// [Range(1, 2000)] public int? Limit { get; set; } diff --git a/Tiku.Api/Contracts/CommerceDtos.cs b/Tiku.Api/Contracts/CommerceDtos.cs index 51c3ae2..359fb5a 100644 --- a/Tiku.Api/Contracts/CommerceDtos.cs +++ b/Tiku.Api/Contracts/CommerceDtos.cs @@ -3,25 +3,49 @@ using Tiku.Application.Commerce; namespace Tiku.Api.Contracts; +/// +/// 创建交易订单请求 DTO。 +/// public sealed class CreateCommerceOrderDto { + /// + /// 套餐 ID。 + /// [Required] public Guid PlanId { get; set; } + /// + /// 数量。 + /// [Range(1, 99)] public int Quantity { get; set; } = 1; + /// + /// 支付方式。 + /// [StringLength(50)] public string? PayMethod { get; set; } + /// + /// 支付渠道。 + /// [StringLength(50)] public string? PayProvider { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 优惠券码。 + /// [StringLength(100)] public string? CouponCode { get; set; } + /// + /// 优惠券领取记录 ID。 + /// public Guid? CouponRedemptionId { get; set; } public CreateCommerceOrderCommand ToCommand() @@ -37,35 +61,65 @@ public sealed class CreateCommerceOrderDto } } +/// +/// 交易订单查询参数。 +/// public sealed class CommerceOrderQueryDto { + /// + /// 返回数量上限。 + /// [Range(1, 100)] public int? Limit { get; set; } + /// + /// 状态。 + /// [StringLength(32)] public string? Status { get; set; } } +/// +/// 创建交易支付请求 DTO。 +/// public sealed class CreateCommercePaymentDto { + /// + /// 订单号。 + /// [Required] [StringLength(100)] public string OrderNo { get; set; } = string.Empty; + /// + /// 服务提供方。 + /// [Required] [StringLength(50)] public string Provider { get; set; } = string.Empty; + /// + /// 方式。 + /// [Required] [StringLength(50)] public string Method { get; set; } = string.Empty; + /// + /// 微信或支付渠道 openid。 + /// [StringLength(255)] public string? OpenId { get; set; } + /// + /// 支付完成返回地址。 + /// [StringLength(2048)] public string? ReturnUrl { get; set; } + /// + /// 支付取消返回地址。 + /// [StringLength(2048)] public string? QuitUrl { get; set; } @@ -81,11 +135,20 @@ public sealed class CreateCommercePaymentDto } } +/// +/// 交易优惠券查询参数。 +/// public sealed class CommerceCouponQueryDto { + /// + /// 返回数量上限。 + /// [Range(1, 100)] public int? Limit { get; set; } + /// + /// 状态。 + /// [StringLength(32)] public string? Status { get; set; } @@ -95,8 +158,14 @@ public sealed class CommerceCouponQueryDto } } +/// +/// 领取交易优惠券请求 DTO。 +/// public sealed class ClaimCommerceCouponDto { + /// + /// 优惠券码。 + /// [Required] [StringLength(100)] public string CouponCode { get; set; } = string.Empty; @@ -107,19 +176,37 @@ public sealed class ClaimCommerceCouponDto } } +/// +/// 校验交易优惠券请求 DTO。 +/// public sealed class CheckCommerceCouponDto { + /// + /// 优惠券码。 + /// [StringLength(100)] public string? CouponCode { get; set; } + /// + /// 优惠券领取记录 ID。 + /// public Guid? CouponRedemptionId { get; set; } + /// + /// 套餐 ID。 + /// [Required] public Guid PlanId { get; set; } + /// + /// 数量。 + /// [Range(1, 99)] public int Quantity { get; set; } = 1; + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } public CheckCommerceCouponCommand ToCommand() diff --git a/Tiku.Api/Contracts/CommissionDtos.cs b/Tiku.Api/Contracts/CommissionDtos.cs index bd3e967..44aba4e 100644 --- a/Tiku.Api/Contracts/CommissionDtos.cs +++ b/Tiku.Api/Contracts/CommissionDtos.cs @@ -5,132 +5,294 @@ using Tiku.Application.Growth; namespace Tiku.Api.Contracts; +/// +/// 保存佣金配置请求。 +/// public sealed class UpdateCommissionSettingsDto { + /// + /// 默认佣金比例。 + /// [Range(0, 1)] public decimal? DefaultRate { get; set; } + /// + /// 最低结算金额,单位为分。 + /// [Range(0, int.MaxValue)] public int? MinSettlementCents { get; set; } + /// + /// 结算周期。 + /// [StringLength(50)] public string? SettlementCycle { get; set; } + /// + /// 配置内容。 + /// public JsonElement? Config { get; set; } public UpdateCommissionSettingsCommand ToCommand() => new(DefaultRate, MinSettlementCents, SettlementCycle, Config); } +/// +/// 调整成员佣金比例请求。 +/// public sealed class UpdateMemberCommissionRateDto { + /// + /// 用户 ID。 + /// [Required] public Guid UserId { get; set; } + /// + /// 成员佣金比例。 + /// [Range(0, 1)] public decimal? CommissionRate { get; set; } + /// + /// 佣金扩展配置。 + /// public JsonElement? CommissionConfig { get; set; } public UpdateMemberCommissionRateCommand ToCommand() => new(UserId, CommissionRate, CommissionConfig); } +/// +/// 佣金统计周期查询参数。 +/// public class CommissionPeriodQueryDto { + /// + /// 开始日期,格式为 yyyy-MM-dd。 + /// [RegularExpression("^\\d{4}-\\d{2}-\\d{2}$")] public string? StartDate { get; set; } + /// + /// 结束日期,格式为 yyyy-MM-dd。 + /// [RegularExpression("^\\d{4}-\\d{2}-\\d{2}$")] public string? EndDate { get; set; } + /// + /// 推荐人用户 ID。 + /// public Guid? ReferrerUserId { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } public CommissionPeriodQuery ToQuery() => new(ParseDate(StartDate), ParseDate(EndDate), ReferrerUserId, Limit); protected static DateOnly? ParseDate(string? value) => string.IsNullOrWhiteSpace(value) ? null : DateOnly.ParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture); } +/// +/// 佣金结算单查询参数。 +/// public sealed class CommissionSettlementsQueryDto { + /// + /// 状态。 + /// [StringLength(50)] public string? Status { get; set; } + /// + /// 推荐人用户 ID。 + /// public Guid? ReferrerUserId { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } public CommissionSettlementQuery ToQuery() => new(Status, ReferrerUserId, Limit); } +/// +/// 生成佣金结算单请求 DTO。 +/// public sealed class GenerateCommissionSettlementDto { + /// + /// 开始日期,格式为 yyyy-MM-dd。 + /// [Required] [RegularExpression("^\\d{4}-\\d{2}-\\d{2}$")] public string StartDate { get; set; } = string.Empty; + /// + /// 结束日期,格式为 yyyy-MM-dd。 + /// [Required] [RegularExpression("^\\d{4}-\\d{2}-\\d{2}$")] public string EndDate { get; set; } = string.Empty; + /// + /// 推荐人用户 ID。 + /// [Required] public Guid ReferrerUserId { get; set; } + /// + /// 状态。 + /// [StringLength(50)] public string? Status { get; set; } + /// + /// 备注。 + /// [StringLength(1000)] public string? Remark { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement? Metadata { get; set; } public GenerateCommissionSettlementCommand ToCommand() => new(Parse(StartDate), Parse(EndDate), ReferrerUserId, Status, Remark, Metadata); private static DateOnly Parse(string value) => DateOnly.ParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture); } +/// +/// 更新佣金结算单状态请求 DTO。 +/// public sealed class UpdateCommissionSettlementStatusDto { + /// + /// 结算单 ID。 + /// [Required] public Guid SettlementId { get; set; } + /// + /// 状态。 + /// [StringLength(50)] public string? Status { get; set; } + /// + /// 审核备注。 + /// [StringLength(1000)] public string? ReviewNote { get; set; } + /// + /// 支付方式。 + /// [StringLength(100)] public string? PaymentMethod { get; set; } + /// + /// 收款账号。 + /// [StringLength(200)] public string? PaymentAccount { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement? Metadata { get; set; } public UpdateCommissionSettlementStatusCommand ToCommand() => new(SettlementId, Status, ReviewNote, PaymentMethod, PaymentAccount, Metadata); } +/// +/// 佣金结算单导出查询参数。 +/// public sealed class CommissionSettlementExportQueryDto { + /// + /// 结算单 ID。 + /// [Required] public Guid SettlementId { get; set; } + /// + /// 导出格式。 + /// [StringLength(10)] public string? Format { get; set; } } +/// +/// 佣金结算单凭证查询参数。 +/// public sealed class CommissionSettlementProofQueryDto { + /// + /// 结算单 ID。 + /// [Required] public Guid SettlementId { get; set; } } +/// +/// 创建佣金结算凭证请求。 +/// public sealed class CreateCommissionProofDto { + /// + /// 结算单 ID。 + /// [Required] public Guid SettlementId { get; set; } + /// + /// 凭证类型。 + /// [StringLength(50)] public string? ProofType { get; set; } + /// + /// 标题。 + /// [StringLength(200)] public string? Title { get; set; } + /// + /// 说明。 + /// [StringLength(2000)] public string? Description { get; set; } + /// + /// 资产 ID。 + /// public Guid? AssetId { get; set; } + /// + /// 外部链接。 + /// [StringLength(2048)] public string? ExternalUrl { get; set; } + /// + /// 金额,单位为分。 + /// [Range(0, int.MaxValue)] public int? AmountCents { get; set; } + /// + /// 支付方式。 + /// [StringLength(100)] public string? PaymentMethod { get; set; } + /// + /// 收款账号。 + /// [StringLength(200)] public string? PaymentAccount { get; set; } + /// + /// 支付时间。 + /// public DateTimeOffset? PaidAt { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement? Metadata { get; set; } public CreateCommissionProofCommand ToCommand() => new(SettlementId, ProofType, Title, Description, AssetId, ExternalUrl, AmountCents, PaymentMethod, PaymentAccount, PaidAt, Metadata); } +/// +/// 更新佣金结算凭证状态请求。 +/// public sealed class UpdateCommissionProofStatusDto { + /// + /// 凭证 ID。 + /// [Required] public Guid ProofId { get; set; } + /// + /// 状态。 + /// [StringLength(50)] public string? Status { get; set; } + /// + /// 审核备注。 + /// [StringLength(1000)] public string? ReviewNote { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement? Metadata { get; set; } public UpdateCommissionProofStatusCommand ToCommand() => new(ProofId, Status, ReviewNote, Metadata); } diff --git a/Tiku.Api/Contracts/ContentManagementDtos.cs b/Tiku.Api/Contracts/ContentManagementDtos.cs index 60a0c45..c0e7174 100644 --- a/Tiku.Api/Contracts/ContentManagementDtos.cs +++ b/Tiku.Api/Contracts/ContentManagementDtos.cs @@ -7,37 +7,76 @@ using Tiku.Domain.Content; namespace Tiku.Api.Contracts; +/// +/// 内容管理查询参数。 +/// public sealed class ContentManagementQueryDto { + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 节点 ID。 + /// public Guid? NodeId { get; set; } + /// + /// 题集 ID。 + /// public Guid? CollectionId { get; set; } + /// + /// 父节点 ID;传 root 表示根节点。 + /// [StringLength(64)] [RegularExpression("^(root|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$")] public string? ParentId { get; set; } + /// + /// 入口类型。 + /// [StringLength(50)] public string? EntryType { get; set; } + /// + /// 题集类型。 + /// [StringLength(50)] public string? CollectionType { get; set; } + /// + /// 模式。 + /// [StringLength(50)] public string? Mode { get; set; } + /// + /// 标记类型。 + /// [StringLength(50)] public string? MarkerType { get; set; } + /// + /// 关键字。 + /// [StringLength(100)] public string? Keyword { get; set; } + /// + /// 是否包含停用数据。 + /// public bool IncludeInactive { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 1000)] public int? Limit { get; set; } @@ -59,43 +98,88 @@ public sealed class ContentManagementQueryDto } } +/// +/// 新增或更新内容条目请求 DTO。 +/// public sealed class UpsertContentEntryDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 历史系统 ID。 + /// [StringLength(64)] public string? LegacyId { get; set; } + /// + /// 入口键。 + /// [StringLength(100)] public string? EntryKey { get; set; } + /// + /// 名称。 + /// [Required] [StringLength(300)] public string Name { get; set; } = string.Empty; + /// + /// 入口类型。 + /// [StringLength(50)] public string? EntryType { get; set; } + /// + /// 图标。 + /// [StringLength(100)] public string? Icon { get; set; } + /// + /// 路由地址。 + /// [StringLength(500)] public string? Route { get; set; } + /// + /// 说明。 + /// [StringLength(2000)] public string? Description { get; set; } + /// + /// 可见性。 + /// [StringLength(50)] public string? Visibility { get; set; } + /// + /// 访问规则。 + /// public JsonElement AccessRules { get; set; } = JsonDefaults.Object(); + /// + /// 布局配置。 + /// public JsonElement LayoutConfig { get; set; } = JsonDefaults.Object(); + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 是否启用。 + /// public bool? IsActive { get; set; } public UpsertContentEntryCommand ToCommand() @@ -118,45 +202,96 @@ public sealed class UpsertContentEntryDto } } +/// +/// 新增或更新内容节点请求 DTO。 +/// public sealed class UpsertContentNodeDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 内容入口 ID。 + /// [Required] public Guid EntryId { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 父节点 ID。 + /// public Guid? ParentId { get; set; } + /// + /// 历史系统 ID。 + /// [StringLength(64)] public string? LegacyId { get; set; } + /// + /// 节点键。 + /// [StringLength(100)] public string? NodeKey { get; set; } + /// + /// 名称。 + /// [Required] [StringLength(300)] public string Name { get; set; } = string.Empty; + /// + /// 节点Type。 + /// [StringLength(50)] public string? NodeType { get; set; } + /// + /// 标记类型。 + /// [StringLength(50)] public string? MarkerType { get; set; } + /// + /// 标记配置。 + /// public JsonElement MarkerConfig { get; set; } = JsonDefaults.Object(); + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 是否启用。 + /// public bool? IsActive { get; set; } + /// + /// 是否可选择。 + /// public bool? IsSelectable { get; set; } + /// + /// 是否叶子节点。 + /// public bool? IsLeaf { get; set; } + /// + /// 访问规则。 + /// public JsonElement AccessRules { get; set; } = JsonDefaults.Object(); + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public UpsertContentNodeCommand ToCommand() @@ -181,48 +316,105 @@ public sealed class UpsertContentNodeDto } } +/// +/// 新增或更新题目题集请求 DTO。 +/// public sealed class UpsertQuestionCollectionDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 节点 ID。 + /// public Guid? NodeId { get; set; } + /// + /// 科目 ID。 + /// public Guid? SubjectId { get; set; } + /// + /// 分类 ID。 + /// public Guid? CategoryId { get; set; } + /// + /// 题库 ID。 + /// public Guid? QuestionBankId { get; set; } + /// + /// 历史系统 ID。 + /// [StringLength(64)] public string? LegacyId { get; set; } + /// + /// 名称。 + /// [Required] [StringLength(300)] public string Name { get; set; } = string.Empty; + /// + /// 题集类型。 + /// [StringLength(50)] public string? CollectionType { get; set; } + /// + /// 来源类型。 + /// [StringLength(50)] public string? SourceType { get; set; } + /// + /// 筛选条件。 + /// public JsonElement Filters { get; set; } = JsonDefaults.Object(); + /// + /// 总分。 + /// public decimal? TotalScore { get; set; } + /// + /// 时长,单位为分钟。 + /// public int? DurationMinutes { get; set; } + /// + /// 状态。 + /// [StringLength(50)] public string? Status { get; set; } + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 访问规则。 + /// public JsonElement AccessRules { get; set; } = JsonDefaults.Object(); + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public UpsertQuestionCollectionCommand ToCommand() @@ -249,23 +441,47 @@ public sealed class UpsertQuestionCollectionDto } } +/// +/// 题集题目请求 DTO。 +/// public sealed class CollectionQuestionDto { + /// + /// 题目 ID。 + /// [Required] public Guid QuestionId { get; set; } + /// + /// 来源。 + /// [Required] public QuestionSource Source { get; set; } = QuestionSource.Tenant; + /// + /// 分段键。 + /// [StringLength(100)] public string? SectionKey { get; set; } + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 分数。 + /// public decimal? Score { get; set; } + /// + /// 是否必填。 + /// public bool? Required { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public CollectionQuestionCommand ToCommand() @@ -280,11 +496,20 @@ public sealed class CollectionQuestionDto } } +/// +/// 替换题集Items请求 DTO。 +/// public sealed class ReplaceCollectionItemsDto { + /// + /// 题集 ID。 + /// [Required] public Guid CollectionId { get; set; } + /// + /// 题目列表。 + /// public IReadOnlyCollection Questions { get; set; } = []; public ReplaceCollectionItemsCommand ToCommand() @@ -295,48 +520,105 @@ public sealed class ReplaceCollectionItemsDto } } +/// +/// 新增或更新练习Blueprint请求 DTO。 +/// public sealed class UpsertPracticeBlueprintDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 节点 ID。 + /// public Guid? NodeId { get; set; } + /// + /// 题集 ID。 + /// public Guid? CollectionId { get; set; } + /// + /// 历史系统 ID。 + /// [StringLength(64)] public string? LegacyId { get; set; } + /// + /// 名称。 + /// [Required] [StringLength(300)] public string Name { get; set; } = string.Empty; + /// + /// 模式。 + /// [StringLength(50)] public string? Mode { get; set; } + /// + /// 组卷方式。 + /// [StringLength(50)] public string? AssemblyType { get; set; } + /// + /// 题目数量上限。 + /// public int? QuestionLimit { get; set; } + /// + /// 时长,单位为分钟。 + /// public int? DurationMinutes { get; set; } + /// + /// 总分。 + /// public decimal? TotalScore { get; set; } + /// + /// 及格分。 + /// public decimal? PassScore { get; set; } + /// + /// 分段配置。 + /// public JsonElement Sections { get; set; } = JsonDefaults.Array(); + /// + /// 规则配置。 + /// public JsonElement Rules { get; set; } = JsonDefaults.Object(); + /// + /// 访问规则。 + /// public JsonElement AccessRules { get; set; } = JsonDefaults.Object(); + /// + /// 状态。 + /// [StringLength(50)] public string? Status { get; set; } + /// + /// 显示顺序。 + /// public int? Order { get; set; } public UpsertPracticeBlueprintCommand ToCommand() @@ -363,12 +645,21 @@ public sealed class UpsertPracticeBlueprintDto } } +/// +/// 导入Template查询参数。 +/// public sealed class ImportTemplateQueryDto { + /// + /// 导入Type。 + /// [Required] [StringLength(50)] public string ImportType { get; set; } = string.Empty; + /// + /// 导出格式。 + /// [StringLength(10)] public string? Format { get; set; } } diff --git a/Tiku.Api/Contracts/ContentNavigationDtos.cs b/Tiku.Api/Contracts/ContentNavigationDtos.cs index 7f05db0..6156165 100644 --- a/Tiku.Api/Contracts/ContentNavigationDtos.cs +++ b/Tiku.Api/Contracts/ContentNavigationDtos.cs @@ -3,42 +3,87 @@ using Tiku.Application.Content; namespace Tiku.Api.Contracts; +/// +/// 内容Navigation查询参数。 +/// public sealed class ContentNavigationQueryDto { + /// + /// 租户编码。 + /// [StringLength(100)] public string? TenantCode { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 节点 ID。 + /// public Guid? NodeId { get; set; } + /// + /// 题集 ID。 + /// public Guid? CollectionId { get; set; } + /// + /// 父节点 ID;传 root 表示根节点。 + /// [StringLength(64)] [RegularExpression("^(root|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$")] public string? ParentId { get; set; } + /// + /// 入口类型。 + /// [StringLength(50)] public string? EntryType { get; set; } + /// + /// 题集类型。 + /// [StringLength(50)] public string? CollectionType { get; set; } + /// + /// 模式。 + /// [StringLength(50)] public string? Mode { get; set; } + /// + /// 标记类型。 + /// [StringLength(50)] public string? MarkerType { get; set; } + /// + /// 关键字。 + /// [StringLength(100)] public string? Keyword { get; set; } + /// + /// 是否包含隐藏数据。 + /// public bool IncludeHidden { get; set; } + /// + /// 是否包含停用数据。 + /// public bool IncludeInactive { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 1000)] public int? Limit { get; set; } diff --git a/Tiku.Api/Contracts/CrmDtos.cs b/Tiku.Api/Contracts/CrmDtos.cs index cb33f74..2c75056 100644 --- a/Tiku.Api/Contracts/CrmDtos.cs +++ b/Tiku.Api/Contracts/CrmDtos.cs @@ -4,35 +4,71 @@ using Tiku.Application.Growth; namespace Tiku.Api.Contracts; +/// +/// 新增或更新CRM配置请求 DTO。 +/// public sealed class UpsertCrmConfigDto { + /// + /// 是否启用。 + /// public bool Enabled { get; set; } + /// + /// 访问地址。 + /// [StringLength(2048)] public string? Url { get; set; } + /// + /// 密钥引用。 + /// [StringLength(300)] public string? SecretRef { get; set; } + /// + /// 密钥内容。 + /// public string? Secret { get; set; } + /// + /// 表单名称。 + /// [StringLength(200)] public string? FormName { get; set; } + /// + /// 考试类型。 + /// [StringLength(100)] public string? ExamType { get; set; } + /// + /// 超时时长,单位为秒。 + /// [Range(1, 120)] public int? TimeoutSeconds { get; set; } + /// + /// 延迟秒数。 + /// [Range(0, 86400)] public int? DelaySeconds { get; set; } + /// + /// 分配模式。 + /// [StringLength(50)] public string? AssignmentMode { get; set; } + /// + /// 分配池。 + /// public JsonElement? AssignmentPool { get; set; } + /// + /// 分配配置。 + /// public JsonElement? AssignmentConfig { get; set; } public UpsertCrmConfigCommand ToCommand() @@ -52,16 +88,31 @@ public sealed class UpsertCrmConfigDto } } +/// +/// CRM队列查询参数。 +/// public sealed class CrmQueueQueryDto { + /// + /// 状态。 + /// [StringLength(50)] public string? Status { get; set; } + /// + /// 队列任务 ID。 + /// public Guid? QueueId { get; set; } + /// + /// 来源。 + /// [StringLength(200)] public string? Source { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -71,10 +122,19 @@ public sealed class CrmQueueQueryDto } } +/// +/// CRM队列日志查询参数。 +/// public sealed class CrmQueueLogQueryDto { + /// + /// 队列任务 ID。 + /// public Guid? QueueId { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 200)] public int? Limit { get; set; } @@ -84,17 +144,32 @@ public sealed class CrmQueueLogQueryDto } } +/// +/// CRM队列Action请求 DTO。 +/// public sealed class CrmQueueActionDto { + /// + /// 队列任务 ID。 + /// [Required] public Guid QueueId { get; set; } + /// + /// 操作。 + /// [StringLength(50)] public string? Action { get; set; } + /// + /// 备注。 + /// [StringLength(500)] public string? Note { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement? Metadata { get; set; } public CrmQueueActionCommand ToCommand() diff --git a/Tiku.Api/Contracts/DirectContentDtos.cs b/Tiku.Api/Contracts/DirectContentDtos.cs index 6f08af9..3263aad 100644 --- a/Tiku.Api/Contracts/DirectContentDtos.cs +++ b/Tiku.Api/Contracts/DirectContentDtos.cs @@ -5,28 +5,73 @@ using Tiku.Domain.Common; namespace Tiku.Api.Contracts; +/// +/// 管理侧内容查询参数。 +/// public sealed class DirectContentQueryDto { + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 父节点 ID。 + /// public Guid? ParentId { get; set; } + /// + /// 科目 ID。 + /// public Guid? SubjectId { get; set; } + /// + /// 章节 ID。 + /// public Guid? ChapterId { get; set; } + /// + /// 单元 ID。 + /// public Guid? UnitId { get; set; } + /// + /// 院校 ID。 + /// public Guid? SchoolId { get; set; } + /// + /// 专业 ID。 + /// public Guid? MajorId { get; set; } + /// + /// 题目 ID。 + /// public Guid? QuestionId { get; set; } + /// + /// 状态。 + /// [StringLength(50)] public string? Status { get; set; } + /// + /// 关键字。 + /// [StringLength(200)] public string? Keyword { get; set; } + /// + /// 年份。 + /// [Range(1900, 3000)] public int? Year { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 1000)] public int? Limit { get; set; } @@ -50,37 +95,121 @@ public sealed class DirectContentQueryDto } } +/// +/// 管理侧题目写入请求。 +/// public sealed class DirectQuestionWriteDto { + /// + /// 题目 ID。 + /// public Guid? QuestionId { get; set; } + /// + /// 题库 ID。 + /// public Guid? QuestionBankId { get; set; } + /// + /// 科目 ID。 + /// public Guid? SubjectId { get; set; } + /// + /// 分类 ID。 + /// public Guid? CategoryId { get; set; } + /// + /// 节点 ID。 + /// public Guid? NodeId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 主题集 ID。 + /// public Guid? PrimaryCollectionId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 类型。 + /// public string? Type { get; set; } + /// + /// 类型显示名。 + /// public string? TypeLabel { get; set; } + /// + /// 难度。 + /// [Range(1, 5)] public int? Difficulty { get; set; } + /// + /// 标签列表。 + /// public JsonElement Tags { get; set; } = JsonDefaults.Array(); + /// + /// 内容。 + /// public string? Content { get; set; } + /// + /// 选项配置。 + /// public JsonElement Options { get; set; } = JsonDefaults.Array(); + /// + /// 正确选项索引。 + /// public int? CorrectOptionIndex { get; set; } + /// + /// 正确选项索引列表。 + /// public JsonElement CorrectOptionIndices { get; set; } = JsonDefaults.Array(); + /// + /// 文字答案。 + /// public string? AnswerText { get; set; } + /// + /// 解析。 + /// public string? Explanation { get; set; } + /// + /// 子题列表。 + /// public JsonElement SubQuestions { get; set; } = JsonDefaults.Array(); + /// + /// 代码语言。 + /// public string? CodeLang { get; set; } + /// + /// 代码模板。 + /// public string? CodeTemplate { get; set; } + /// + /// 媒体地址。 + /// public string? MediaUrl { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 考试标记配置。 + /// public JsonElement ExamMarkers { get; set; } = JsonDefaults.Object(); + /// + /// 来源哈希。 + /// public string? SourceHash { get; set; } + /// + /// 是否创建题目版本。 + /// public bool? CreateVersion { get; set; } public QuestionWriteCommand ToCommand(bool createVersionDefault) @@ -116,18 +245,54 @@ public sealed class DirectQuestionWriteDto } } +/// +/// 管理侧词汇单元写入请求。 +/// public sealed class DirectVocabularyUnitDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 名称。 + /// public required string Name { get; set; } + /// + /// 说明。 + /// public string? Description { get; set; } + /// + /// 单词数量。 + /// public int? WordCount { get; set; } + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 是否启用。 + /// public bool? IsActive { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public VocabularyUnitCommand ToCommand() @@ -136,22 +301,70 @@ public sealed class DirectVocabularyUnitDto } } +/// +/// 管理侧词汇单词写入请求。 +/// public sealed class DirectVocabularyWordDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 单元 ID。 + /// public Guid? UnitId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 单词。 + /// public required string Word { get; set; } + /// + /// 音标。 + /// public string? Phonetic { get; set; } + /// + /// 释义。 + /// public string? Meaning { get; set; } + /// + /// 例句。 + /// public string? Example { get; set; } + /// + /// 例句翻译。 + /// public string? ExampleTranslation { get; set; } + /// + /// 难度。 + /// public int? Difficulty { get; set; } + /// + /// 标签列表。 + /// public JsonElement Tags { get; set; } = JsonDefaults.Array(); + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 是否启用。 + /// public bool? IsActive { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public VocabularyWordCommand ToCommand() @@ -175,22 +388,70 @@ public sealed class DirectVocabularyWordDto } } +/// +/// 管理侧知识手册科目写入请求。 +/// public sealed class DirectHandbookSubjectDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 院校 ID。 + /// public Guid? SchoolId { get; set; } + /// + /// 专业 ID。 + /// public Guid? MajorId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 名称。 + /// public required string Name { get; set; } + /// + /// 类型。 + /// public string? Type { get; set; } + /// + /// 图标。 + /// public string? Icon { get; set; } + /// + /// 颜色。 + /// public string? Color { get; set; } + /// + /// 说明。 + /// public string? Description { get; set; } + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 是否启用。 + /// public bool? IsActive { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public HandbookSubjectCommand ToCommand() @@ -214,17 +475,50 @@ public sealed class DirectHandbookSubjectDto } } +/// +/// 管理侧知识手册章节写入请求。 +/// public sealed class DirectHandbookChapterDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 科目 ID。 + /// public Guid? SubjectId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 名称。 + /// public required string Name { get; set; } + /// + /// 说明。 + /// public string? Description { get; set; } + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 是否启用。 + /// public bool? IsActive { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public HandbookChapterCommand ToCommand() @@ -233,19 +527,58 @@ public sealed class DirectHandbookChapterDto } } +/// +/// 管理侧知识手册条目写入请求。 +/// public sealed class DirectHandbookEntryDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 章节 ID。 + /// public Guid? ChapterId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 标题。 + /// public required string Title { get; set; } + /// + /// 摘要。 + /// public string? Summary { get; set; } + /// + /// 内容。 + /// public string? Content { get; set; } + /// + /// 标签列表。 + /// public JsonElement Tags { get; set; } = JsonDefaults.Array(); + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 是否启用。 + /// public bool? IsActive { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public HandbookEntryCommand ToCommand() @@ -254,13 +587,34 @@ public sealed class DirectHandbookEntryDto } } +/// +/// 管理侧院校写入请求。 +/// public sealed class DirectSchoolDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 名称。 + /// public required string Name { get; set; } + /// + /// 专业考试日期。 + /// public string? ProfessionalExamDate { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public SchoolCommand ToCommand() @@ -269,16 +623,46 @@ public sealed class DirectSchoolDto } } +/// +/// 管理侧专业写入请求。 +/// public sealed class DirectMajorDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 院校 ID。 + /// public Guid? SchoolId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 名称。 + /// public required string Name { get; set; } + /// + /// 说明。 + /// public string? Description { get; set; } + /// + /// 备考建议。 + /// public string? StudyTips { get; set; } + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 是否启用。 + /// public bool? IsActive { get; set; } public MajorCommand ToCommand() @@ -287,22 +671,70 @@ public sealed class DirectMajorDto } } +/// +/// 管理侧分数线字段写入请求。 +/// public sealed class DirectScorelineFieldDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 字段键。 + /// public required string FieldKey { get; set; } + /// + /// 字段名称。 + /// public required string FieldName { get; set; } + /// + /// 字段类型。 + /// public string? FieldType { get; set; } + /// + /// 单位。 + /// public string? Unit { get; set; } + /// + /// 是否可筛选。 + /// public bool? IsFilter { get; set; } + /// + /// 是否必填。 + /// public bool? IsRequired { get; set; } + /// + /// 是否可见。 + /// public bool? IsVisible { get; set; } + /// + /// 是否参与趋势展示。 + /// public bool? IsTrend { get; set; } + /// + /// 选项配置。 + /// public JsonElement Options { get; set; } = JsonDefaults.Array(); + /// + /// 占位提示。 + /// public string? Placeholder { get; set; } + /// + /// 说明。 + /// public string? Description { get; set; } + /// + /// 显示顺序。 + /// public int? Order { get; set; } public ScorelineFieldCommand ToCommand() @@ -326,19 +758,49 @@ public sealed class DirectScorelineFieldDto } } +/// +/// 管理侧分数线记录写入请求。 +/// public sealed class DirectScorelineRecordDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 院校 ID。 + /// public Guid? SchoolId { get; set; } + /// + /// 专业 ID。 + /// public Guid? MajorId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 年份。 + /// [Range(1900, 3000)] public int Year { get; set; } + /// + /// 院校名称。 + /// public string? SchoolName { get; set; } + /// + /// 专业名称。 + /// public string? MajorName { get; set; } + /// + /// 动态字段值。 + /// public JsonElement FieldValues { get; set; } = JsonDefaults.Object(); public ScorelineRecordCommand ToCommand() @@ -356,21 +818,66 @@ public sealed class DirectScorelineRecordDto } } +/// +/// 管理侧视频写入请求。 +/// public sealed class DirectVideoDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 科目 ID。 + /// public Guid? SubjectId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 标题。 + /// public required string Title { get; set; } + /// + /// 说明。 + /// public string? Description { get; set; } + /// + /// 视频地址。 + /// public string? VideoUrl { get; set; } + /// + /// 缩略图地址。 + /// public string? ThumbnailUrl { get; set; } + /// + /// 时长,单位为秒。 + /// public int? DurationSeconds { get; set; } + /// + /// 知识标签。 + /// public JsonElement KnowledgeTags { get; set; } = JsonDefaults.Array(); + /// + /// 是否通用视频。 + /// public bool? IsGeneral { get; set; } + /// + /// 难度。 + /// public int? Difficulty { get; set; } + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 是否启用。 + /// public bool? IsActive { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public VideoExplanationCommand ToCommand() @@ -393,13 +900,34 @@ public sealed class DirectVideoDto } } +/// +/// 管理侧题目视频绑定请求。 +/// public sealed class DirectQuestionVideoDto { + /// + /// 题目 ID。 + /// public Guid QuestionId { get; set; } + /// + /// 视频 ID。 + /// public Guid VideoId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 视频类型。 + /// public string? VideoType { get; set; } + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public QuestionVideoCommand ToCommand() @@ -408,28 +936,94 @@ public sealed class DirectQuestionVideoDto } } +/// +/// 管理侧运营内容写入请求。 +/// public sealed class DirectOperationContentDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 院校 ID。 + /// public Guid? SchoolId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 标题。 + /// public string? Title { get; set; } + /// + /// 副标题。 + /// public string? Subtitle { get; set; } + /// + /// 内容。 + /// public string? Content { get; set; } + /// + /// 问题。 + /// public string? Question { get; set; } + /// + /// 答案。 + /// public string? Answer { get; set; } + /// + /// 链接地址。 + /// public string? Link { get; set; } + /// + /// 按钮文案。 + /// public string? ButtonText { get; set; } + /// + /// 按钮链接。 + /// public string? ButtonLink { get; set; } + /// + /// 背景颜色。 + /// public string? BackgroundColor { get; set; } + /// + /// 边框颜色。 + /// public string? BorderColor { get; set; } + /// + /// 考试名称。 + /// public string? ExamName { get; set; } + /// + /// 考试时间。 + /// public DateTimeOffset? ExamAt { get; set; } + /// + /// 考试类型。 + /// public string? ExamType { get; set; } + /// + /// 说明。 + /// public string? Description { get; set; } + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 是否启用。 + /// public bool? IsActive { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public OperationContentCommand ToCommand() @@ -459,27 +1053,90 @@ public sealed class DirectOperationContentDto } } +/// +/// 管理侧内容导入请求。 +/// public sealed class DirectImportDto { + /// + /// 来源格式。 + /// public string? SourceFormat { get; set; } + /// + /// 来源名称。 + /// public string? SourceName { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 科目 ID。 + /// public Guid? SubjectId { get; set; } + /// + /// 分类 ID。 + /// public Guid? CategoryId { get; set; } + /// + /// 题库 ID。 + /// public Guid? QuestionBankId { get; set; } + /// + /// 题集 ID。 + /// public Guid? CollectionId { get; set; } + /// + /// 是否异步执行。 + /// public bool? Async { get; set; } + /// + /// 数据项列表。 + /// public IReadOnlyCollection? Items { get; set; } + /// + /// 单元数据列表。 + /// public IReadOnlyCollection? Units { get; set; } + /// + /// 单词数据列表。 + /// public IReadOnlyCollection? Words { get; set; } + /// + /// 科目数据列表。 + /// public IReadOnlyCollection? Subjects { get; set; } + /// + /// 条目数据列表。 + /// public IReadOnlyCollection? Entries { get; set; } + /// + /// 字段数据列表。 + /// public IReadOnlyCollection? Fields { get; set; } + /// + /// 院校数据列表。 + /// public IReadOnlyCollection? Schools { get; set; } + /// + /// 专业数据列表。 + /// public IReadOnlyCollection? Majors { get; set; } + /// + /// 记录数据列表。 + /// public IReadOnlyCollection? Records { get; set; } + /// + /// 视频数据列表。 + /// public IReadOnlyCollection? Videos { get; set; } public SimpleImportCommand ToCommand(string importType, bool dryRun) @@ -513,7 +1170,13 @@ public sealed class DirectImportDto } } +/// +/// 管理侧内容导入任务查询请求。 +/// public sealed class DirectImportJobDto { + /// + /// 任务 ID。 + /// public Guid JobId { get; set; } } diff --git a/Tiku.Api/Contracts/HealthDtos.cs b/Tiku.Api/Contracts/HealthDtos.cs index ba56f82..d200c01 100644 --- a/Tiku.Api/Contracts/HealthDtos.cs +++ b/Tiku.Api/Contracts/HealthDtos.cs @@ -6,6 +6,9 @@ namespace Tiku.Api.Contracts; /// 服务状态,例如 ok。 /// 服务名称。 /// 检查时间。 +/// +/// 健康检查Response请求 DTO。 +/// public sealed record HealthResponseDto( string Status, string Service, diff --git a/Tiku.Api/Contracts/LearningDtos.cs b/Tiku.Api/Contracts/LearningDtos.cs index 9c86f46..572a9b4 100644 --- a/Tiku.Api/Contracts/LearningDtos.cs +++ b/Tiku.Api/Contracts/LearningDtos.cs @@ -7,14 +7,26 @@ using Tiku.Domain.Content; namespace Tiku.Api.Contracts; +/// +/// 学习Limit查询参数。 +/// public sealed class LearningLimitQueryDto { + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } + /// + /// 状态。 + /// [StringLength(50)] public string? Status { get; set; } + /// + /// 单元 ID。 + /// public Guid? UnitId { get; set; } public LearningLimitFilter ToFilter() @@ -23,18 +35,36 @@ public sealed class LearningLimitQueryDto } } +/// +/// 练习会话查询参数。 +/// public sealed class PracticeSessionQueryDto { + /// + /// 练习会话 ID。 + /// public Guid? PracticeSessionId { get; set; } + /// + /// 练习蓝图 ID。 + /// public Guid? BlueprintId { get; set; } + /// + /// 模式。 + /// [StringLength(50)] public string? Mode { get; set; } + /// + /// 状态。 + /// [StringLength(50)] public string? Status { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 200)] public int? Limit { get; set; } @@ -49,33 +79,69 @@ public sealed class PracticeSessionQueryDto } } +/// +/// 创建练习会话请求 DTO。 +/// public sealed class CreatePracticeSessionDto { + /// + /// 模式。 + /// [StringLength(50)] public string? Mode { get; set; } + /// + /// 目标类型。 + /// [StringLength(50)] public string? TargetType { get; set; } + /// + /// 目标 ID。 + /// public Guid? TargetId { get; set; } + /// + /// 练习蓝图 ID。 + /// public Guid? BlueprintId { get; set; } + /// + /// 题集 ID。 + /// public Guid? CollectionId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 题目数量上限。 + /// [Range(1, 500)] public int? QuestionLimit { get; set; } + /// + /// 时长,单位为分钟。 + /// [Range(1, 1440)] public int? DurationMinutes { get; set; } + /// + /// 总分。 + /// [Range(typeof(decimal), "0", "99999")] public decimal? TotalScore { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public PracticeSessionCommand ToCommand() @@ -95,8 +161,14 @@ public sealed class CreatePracticeSessionDto } } +/// +/// 提交练习会话请求 DTO。 +/// public sealed class SubmitPracticeSessionDto { + /// + /// 练习会话 ID。 + /// [Required] public Guid PracticeSessionId { get; set; } @@ -106,16 +178,31 @@ public sealed class SubmitPracticeSessionDto } } +/// +/// 提交答案请求 DTO。 +/// public sealed class SubmitAnswerDto { + /// + /// 会话题目 ID。 + /// [Required] public Guid SessionQuestionId { get; set; } + /// + /// 已选选项。 + /// public IReadOnlyCollection? SelectedOptions { get; set; } + /// + /// 文字答案。 + /// [StringLength(10000)] public string? AnswerText { get; set; } + /// + /// 主观题自评是否正确。 + /// public bool? SelfJudgedCorrect { get; set; } public SubmitAnswerCommand ToCommand() @@ -128,14 +215,26 @@ public sealed class SubmitAnswerDto } } +/// +/// 题目Action请求 DTO。 +/// public sealed class QuestionActionDto { + /// + /// 题目 ID。 + /// [Required] public Guid QuestionId { get; set; } + /// + /// 来源。 + /// [Required] public QuestionSource Source { get; set; } = QuestionSource.Tenant; + /// + /// 是否收藏。 + /// public bool? Favorite { get; set; } public QuestionActionCommand ToCommand() @@ -144,20 +243,38 @@ public sealed class QuestionActionDto } } +/// +/// 单词Progress请求 DTO。 +/// public sealed class WordProgressDto { + /// + /// 单词 ID。 + /// [Required] public Guid WordId { get; set; } + /// + /// 状态。 + /// [StringLength(50)] public string? Status { get; set; } + /// + /// 答对数量变化。 + /// [Range(0, 100)] public int? CorrectDelta { get; set; } + /// + /// 答错数量变化。 + /// [Range(0, 100)] public int? WrongDelta { get; set; } + /// + /// 下次复习时间。 + /// public DateTimeOffset? NextReviewAt { get; set; } public WordProgressCommand ToCommand() @@ -171,13 +288,25 @@ public sealed class WordProgressDto } } +/// +/// 收藏单词请求 DTO。 +/// public sealed class FavoriteWordDto { + /// + /// 单词 ID。 + /// [Required] public Guid WordId { get; set; } + /// + /// 是否收藏。 + /// public bool? Favorite { get; set; } + /// + /// 备注。 + /// [StringLength(1000)] public string? Note { get; set; } @@ -187,14 +316,26 @@ public sealed class FavoriteWordDto } } +/// +/// 单词Review请求 DTO。 +/// public sealed class WordReviewDto { + /// + /// 单词 ID。 + /// [Required] public Guid WordId { get; set; } + /// + /// 结果。 + /// [StringLength(50)] public string? Result { get; set; } + /// + /// 下次复习时间。 + /// public DateTimeOffset? NextReviewAt { get; set; } public WordReviewCommand ToCommand() diff --git a/Tiku.Api/Contracts/PlatformAdminDtos.cs b/Tiku.Api/Contracts/PlatformAdminDtos.cs index 5ef7a32..6751442 100644 --- a/Tiku.Api/Contracts/PlatformAdminDtos.cs +++ b/Tiku.Api/Contracts/PlatformAdminDtos.cs @@ -9,88 +9,201 @@ using Tiku.Domain.Tenancy; namespace Tiku.Api.Contracts; +/// +/// 平台管理端查询参数。 +/// public sealed class PlatformAdminQueryDto { + /// + /// 状态。 + /// [StringLength(32)] public string? Status { get; set; } + /// + /// 搜索关键字。 + /// [StringLength(200)] public string? Search { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 200)] public int? Limit { get; set; } public PlatformAdminQuery ToQuery() => new(Status, Search, Limit); } +/// +/// 创建平台租户请求 DTO。 +/// public sealed class CreatePlatformTenantDto { + /// + /// 短编码。 + /// [Required] [StringLength(100)] public string Slug { get; set; } = string.Empty; + /// + /// 名称。 + /// [Required] [StringLength(200)] public string Name { get; set; } = string.Empty; + /// + /// 法定名称。 + /// [StringLength(300)] public string? LegalName { get; set; } + /// + /// 状态。 + /// public TenantStatus Status { get; set; } = TenantStatus.Active; + /// + /// 账务状态。 + /// public BillingStatus BillingStatus { get; set; } = BillingStatus.Trial; + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); - public CreatePlatformTenantCommand ToCommand() => new(Slug, Name, LegalName, Status, BillingStatus, Metadata); + /// + /// 租户负责人邮箱。 + /// + [EmailAddress, StringLength(320)] + public string? OwnerEmail { get; set; } + + /// + /// 租户负责人手机号。 + /// + [Phone, StringLength(32)] + public string? OwnerPhone { get; set; } + + /// + /// 租户负责人姓名。 + /// + [Required, StringLength(100)] + public string OwnerName { get; set; } = string.Empty; + + /// + /// 临时密码。 + /// + [Required, StringLength(200, MinimumLength = 12)] + public string TemporaryPassword { get; set; } = string.Empty; + + public CreatePlatformTenantCommand ToCommand() => new( + Slug, Name, LegalName, Status, BillingStatus, Metadata, + OwnerEmail, OwnerPhone, OwnerName, TemporaryPassword); } +/// +/// 更新平台租户状态请求 DTO。 +/// public sealed class UpdatePlatformTenantStatusDto { + /// + /// 租户 ID。 + /// [Required] public Guid TenantId { get; set; } + /// + /// 状态。 + /// public TenantStatus Status { get; set; } = TenantStatus.Active; + /// + /// 账务状态。 + /// public BillingStatus BillingStatus { get; set; } = BillingStatus.Active; + /// + /// 原因。 + /// [StringLength(1000)] public string? Reason { get; set; } public UpdatePlatformTenantStatusCommand ToCommand() => new(TenantId, Status, BillingStatus, Reason); } +/// +/// 新增或更新平台租户账务资料请求 DTO。 +/// public sealed class UpsertPlatformTenantBillingProfileDto { + /// + /// 租户 ID。 + /// [Required] public Guid TenantId { get; set; } + /// + /// 账务名称。 + /// [StringLength(300)] public string? BillingName { get; set; } + /// + /// 税号。 + /// [StringLength(100)] public string? TaxId { get; set; } + /// + /// 联系人姓名。 + /// [StringLength(100)] public string? ContactName { get; set; } + /// + /// 联系人手机号。 + /// [StringLength(32)] public string? ContactPhone { get; set; } + /// + /// 联系人邮箱。 + /// [StringLength(320)] public string? ContactEmail { get; set; } + /// + /// 账务地址。 + /// [StringLength(1000)] public string? BillingAddress { get; set; } + /// + /// 发票抬头。 + /// [StringLength(300)] public string? InvoiceTitle { get; set; } - public TenantInvoiceTitleType? InvoiceType { get; set; } + /// + /// 发票类型。 + /// + public TenantBillingInvoiceTitleType? InvoiceType { get; set; } + /// + /// 开户银行。 + /// [StringLength(300)] public string? BankName { get; set; } + /// + /// 脱敏银行账号。 + /// [StringLength(100)] public string? BankAccountMasked { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public UpsertPlatformTenantBillingProfileCommand ToCommand() => new( @@ -108,143 +221,180 @@ public sealed class UpsertPlatformTenantBillingProfileDto Metadata); } -public sealed class UpsertPlatformSubscriptionDto -{ - [Required] - public Guid TenantId { get; set; } - - [Required] - [StringLength(100)] - public string PlanCode { get; set; } = string.Empty; - - public TenantSubscriptionStatus Status { get; set; } = TenantSubscriptionStatus.Active; - public DateTimeOffset? StartsAt { get; set; } - public DateTimeOffset? ExpiresAt { get; set; } - - [StringLength(32)] - public string? BillingCycle { get; set; } - - [Range(0, int.MaxValue)] - public int AmountCents { get; set; } - - public JsonElement Metadata { get; set; } = JsonDefaults.Object(); - - public UpsertPlatformSubscriptionCommand ToCommand() => new( - TenantId, - PlanCode, - Status, - StartsAt, - ExpiresAt, - BillingCycle, - AmountCents, - Metadata); -} - -public sealed class ReplacePlatformPlanModulesDto -{ - [Required] - public IReadOnlyCollection ModuleCodes { get; set; } = []; - - public ReplacePlatformPlanModulesCommand ToCommand(string planCode) => new(planCode, ModuleCodes); -} - -public sealed class UpsertPlatformTenantModuleOverrideDto -{ - public TenantModuleOverrideMode Mode { get; set; } - public DateTimeOffset? ExpiresAt { get; set; } - - [Required] - [StringLength(1000, MinimumLength = 1)] - public string Reason { get; set; } = string.Empty; - - public UpsertPlatformTenantModuleOverrideCommand ToCommand(Guid tenantId, string moduleCode) => - new(tenantId, moduleCode, Mode, ExpiresAt, Reason); -} - +/// +/// 新增或更新平台Staff请求 DTO。 +/// public sealed class UpsertPlatformStaffDto { + /// + /// 用户 ID。 + /// public Guid? UserId { get; set; } + /// + /// 邮箱。 + /// [StringLength(320)] public string? Email { get; set; } + /// + /// 手机号。 + /// [StringLength(32)] public string? Phone { get; set; } + /// + /// 名称。 + /// [StringLength(200)] public string? Name { get; set; } + /// + /// 状态。 + /// public UserStatus Status { get; set; } = UserStatus.Active; + /// + /// 角色 ID 列表。 + /// public IReadOnlyCollection RoleIds { get; set; } = []; public UpsertPlatformStaffCommand ToCommand() => new(UserId, Email, Phone, Name, Status, RoleIds); } +/// +/// 更新平台Staff状态请求 DTO。 +/// public sealed class UpdatePlatformStaffStatusDto { + /// + /// 用户 ID。 + /// [Required] public Guid UserId { get; set; } + /// + /// 状态。 + /// public UserStatus Status { get; set; } = UserStatus.Active; + /// + /// 原因。 + /// [StringLength(1000)] public string? Reason { get; set; } public UpdatePlatformStaffStatusCommand ToCommand() => new(UserId, Status, Reason); } +/// +/// 更新平台审计Alert状态请求 DTO。 +/// public sealed class UpdatePlatformAuditAlertStatusDto { + /// + /// 告警 ID。 + /// [Required] public Guid AlertId { get; set; } + /// + /// 状态。 + /// public PlatformAuditAlertStatus Status { get; set; } = PlatformAuditAlertStatus.Acknowledged; + /// + /// 处理备注。 + /// [StringLength(1000)] public string? ResolutionNote { get; set; } public UpdatePlatformAuditAlertStatusCommand ToCommand() => new(AlertId, Status, ResolutionNote); } -public sealed class UpsertPlatformDunningChannelDto +/// +/// 新增或更新平台DunningChannel请求 DTO。 +/// +public sealed class UpsertPlatformBillingDunningChannelDto { + /// + /// 通知渠道 ID。 + /// public Guid? ChannelId { get; set; } + /// + /// 渠道编码。 + /// [Required] [StringLength(100)] public string ChannelCode { get; set; } = string.Empty; + /// + /// 名称。 + /// [Required] [StringLength(200)] public string Name { get; set; } = string.Empty; + /// + /// 说明。 + /// [StringLength(1000)] public string? Description { get; set; } + /// + /// 是否启用。 + /// public bool Enabled { get; set; } = true; - public PlatformDunningProvider Provider { get; set; } = PlatformDunningProvider.Generic; + /// + /// 服务提供方。 + /// + public PlatformBillingDunningProvider Provider { get; set; } = PlatformBillingDunningProvider.Generic; + /// + /// Webhook 地址。 + /// [Required] [StringLength(2048)] public string WebhookUrl { get; set; } = string.Empty; + /// + /// 密钥引用。 + /// [StringLength(300)] public string? SecretRef { get; set; } + /// + /// 提醒类型列表。 + /// public IReadOnlyCollection ReminderTypes { get; set; } = ["overdue", "final_notice"]; + /// + /// 提醒渠道列表。 + /// public IReadOnlyCollection ReminderChannels { get; set; } = ["internal"]; + /// + /// 最低提醒级别。 + /// [Range(1, 20)] public int MinReminderLevel { get; set; } = 1; + /// + /// 租户 ID 列表。 + /// public IReadOnlyCollection TenantIds { get; set; } = []; + /// + /// 超时时长,单位为秒。 + /// [Range(1, 60)] public int TimeoutSeconds { get; set; } = 10; + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); - public UpsertPlatformDunningChannelCommand ToCommand() => new( + public UpsertPlatformBillingDunningChannelCommand ToCommand() => new( ChannelId, ChannelCode, Name, @@ -261,24 +411,42 @@ public sealed class UpsertPlatformDunningChannelDto Metadata); } -public sealed class DisablePlatformDunningChannelDto +/// +/// 禁用平台DunningChannel请求 DTO。 +/// +public sealed class DisablePlatformBillingDunningChannelDto { + /// + /// 通知渠道 ID。 + /// [Required] public Guid ChannelId { get; set; } + /// + /// 原因。 + /// [StringLength(1000)] public string? Reason { get; set; } - public DisablePlatformDunningChannelCommand ToCommand() => new(ChannelId, Reason); + public DisablePlatformBillingDunningChannelCommand ToCommand() => new(ChannelId, Reason); } -public sealed class RetryPlatformDunningEventDto +/// +/// 重试平台Dunning事件请求 DTO。 +/// +public sealed class RetryPlatformBillingDunningEventDto { + /// + /// 事件 ID。 + /// [Required] public Guid EventId { get; set; } + /// + /// 原因。 + /// [StringLength(1000)] public string? Reason { get; set; } - public RetryPlatformDunningEventCommand ToCommand() => new(EventId, Reason); + public RetryPlatformBillingDunningEventCommand ToCommand() => new(EventId, Reason); } diff --git a/Tiku.Api/Contracts/PointDtos.cs b/Tiku.Api/Contracts/PointDtos.cs index d52512b..5a1ddd1 100644 --- a/Tiku.Api/Contracts/PointDtos.cs +++ b/Tiku.Api/Contracts/PointDtos.cs @@ -3,14 +3,26 @@ using Tiku.Application.Points; namespace Tiku.Api.Contracts; +/// +/// 积分查询参数。 +/// public sealed class PointQueryDto { + /// + /// 返回数量上限。 + /// [Range(1, 200)] public int? Limit { get; set; } + /// + /// 状态。 + /// [StringLength(32)] public string? Status { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } public PointLimitQuery ToQuery() @@ -19,15 +31,27 @@ public sealed class PointQueryDto } } +/// +/// 领取积分任务请求 DTO。 +/// public sealed class ClaimPointTaskDto { + /// + /// 任务键。 + /// [Required] [StringLength(100)] public string TaskKey { get; set; } = string.Empty; + /// + /// 来源类型。 + /// [StringLength(100)] public string? SourceType { get; set; } + /// + /// 来源 ID。 + /// public Guid? SourceId { get; set; } public ClaimPointTaskCommand ToCommand() @@ -36,8 +60,14 @@ public sealed class ClaimPointTaskDto } } +/// +/// 创建积分兑换订单请求 DTO。 +/// public sealed class CreatePointExchangeOrderDto { + /// + /// 兑换项 ID。 + /// [Required] public Guid ItemId { get; set; } diff --git a/Tiku.Api/Contracts/ProfileDtos.cs b/Tiku.Api/Contracts/ProfileDtos.cs index 2f7e62b..45c3c9c 100644 --- a/Tiku.Api/Contracts/ProfileDtos.cs +++ b/Tiku.Api/Contracts/ProfileDtos.cs @@ -4,47 +4,107 @@ using Tiku.Application.Profile; namespace Tiku.Api.Contracts; +/// +/// 资料查询参数。 +/// public sealed class ProfileQueryDto { + /// + /// 最近记录数量上限。 + /// [Range(1, 50)] public int? RecentLimit { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 20)] public int? Limit { get; set; } + /// + /// 状态。 + /// [StringLength(32)] public string? Status { get; set; } + /// + /// 类型。 + /// [StringLength(50)] public string? Type { get; set; } + /// + /// 分类。 + /// [StringLength(50)] public string? Category { get; set; } + /// + /// 是否包含锁定资源。 + /// public bool IncludeLocked { get; set; } + /// + /// 来源类型。 + /// [StringLength(100)] public string? SourceType { get; set; } + /// + /// 开始时间。 + /// public DateTimeOffset? From { get; set; } + /// + /// 结束时间。 + /// public DateTimeOffset? To { get; set; } } +/// +/// 更新资料请求 DTO。 +/// public sealed class UpdateProfileDto { + /// + /// 名称。 + /// [StringLength(100)] public string? Name { get; set; } + /// + /// 头像预设。 + /// [StringLength(32)] public string? AvatarPreset { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 已选择院校 ID。 + /// public Guid? SelectedSchoolId { get; set; } + /// + /// 已选择专业 ID。 + /// public Guid? SelectedMajorId { get; set; } + /// + /// 统计数据。 + /// public JsonElement? Stats { get; set; } + /// + /// 进度数据。 + /// public JsonElement? Progress { get; set; } + /// + /// 模块选择数据。 + /// public JsonElement? ModuleSelections { get; set; } + /// + /// 最近活动数据。 + /// public JsonElement? RecentActivities { get; set; } public UpdateProfileCommand ToCommand() @@ -62,13 +122,22 @@ public sealed class UpdateProfileDto } } +/// +/// 通知状态请求 DTO。 +/// public sealed class NotificationStatusDto { + /// + /// 通知 ID 列表。 + /// [Required] [MinLength(1)] [MaxLength(100)] public IReadOnlyCollection NotificationIds { get; set; } = []; + /// + /// 状态。 + /// [StringLength(32)] public string? Status { get; set; } @@ -78,30 +147,60 @@ public sealed class NotificationStatusDto } } +/// +/// 提交反馈请求 DTO。 +/// public sealed class SubmitFeedbackDto { + /// + /// 题目 ID。 + /// public Guid? QuestionId { get; set; } + /// + /// 类型。 + /// [StringLength(50)] public string? Type { get; set; } + /// + /// 分类。 + /// [StringLength(100)] public string? Category { get; set; } + /// + /// 标题。 + /// [StringLength(200)] public string? Title { get; set; } + /// + /// 说明。 + /// [Required] [StringLength(5000)] public string Description { get; set; } = string.Empty; + /// + /// 优先级。 + /// [StringLength(32)] public string? Priority { get; set; } + /// + /// 联系方式。 + /// [StringLength(200)] public string? Contact { get; set; } + /// + /// 附件列表。 + /// public JsonElement Attachments { get; set; } = JsonSerializer.SerializeToElement(Array.Empty()); + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonSerializer.SerializeToElement(new { }); public SubmitFeedbackCommand ToCommand() diff --git a/Tiku.Api/Contracts/QuestionBankDtos.cs b/Tiku.Api/Contracts/QuestionBankDtos.cs index e5a4486..2378888 100644 --- a/Tiku.Api/Contracts/QuestionBankDtos.cs +++ b/Tiku.Api/Contracts/QuestionBankDtos.cs @@ -4,40 +4,82 @@ using Tiku.Domain.Content; namespace Tiku.Api.Contracts; +/// +/// 题目题库查询参数。 +/// public sealed class QuestionBankQueryDto { + /// + /// 租户编码。 + /// [StringLength(100)] public string? TenantCode { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 题库 ID。 + /// public Guid? QuestionBankId { get; set; } + /// + /// 科目 ID。 + /// public Guid? SubjectId { get; set; } + /// + /// 分类 ID。 + /// public Guid? CategoryId { get; set; } + /// + /// 节点 ID。 + /// public Guid? NodeId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 题集 ID。 + /// public Guid? CollectionId { get; set; } + /// + /// 来源。 + /// public QuestionSource? Source { get; set; } + /// + /// 类型。 + /// [StringLength(50)] public string? Type { get; set; } + /// + /// 关键字。 + /// [StringLength(100)] public string? Keyword { get; set; } /// - /// 题目 ID 列表,支持逗号分隔;最多取前 300 个。 + /// 题目 ID 列表。 /// public string? QuestionIds { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } diff --git a/Tiku.Api/Contracts/ReferralDtos.cs b/Tiku.Api/Contracts/ReferralDtos.cs index 898c474..c85e248 100644 --- a/Tiku.Api/Contracts/ReferralDtos.cs +++ b/Tiku.Api/Contracts/ReferralDtos.cs @@ -5,17 +5,32 @@ using Tiku.Application.Growth; namespace Tiku.Api.Contracts; +/// +/// 推荐租户查询参数。 +/// public sealed class ReferralTenantQueryDto { + /// + /// 租户编码。 + /// [StringLength(100)] public string? TenantCode { get; set; } } +/// +/// 推荐邀请码请求 DTO。 +/// public sealed class ReferralInviteDto { + /// + /// 渠道。 + /// [StringLength(100)] public string? Channel { get; set; } + /// + /// 落地页路径。 + /// [StringLength(2048)] public string? LandingPath { get; set; } @@ -25,11 +40,20 @@ public sealed class ReferralInviteDto } } +/// +/// 解析推荐请求 DTO。 +/// public sealed class ResolveReferralDto { + /// + /// 编码。 + /// [StringLength(100)] public string? Code { get; set; } + /// + /// 租户编码。 + /// [StringLength(100)] public string? TenantCode { get; set; } @@ -39,22 +63,43 @@ public sealed class ResolveReferralDto } } +/// +/// 记录推荐事件请求 DTO。 +/// public sealed class TrackReferralEventDto { + /// + /// 推荐码。 + /// [Required] [StringLength(100)] public string RefCode { get; set; } = string.Empty; + /// + /// 事件类型。 + /// [StringLength(100)] public string? EventType { get; set; } + /// + /// 来源。 + /// [StringLength(100)] public string? Source { get; set; } + /// + /// 目标用户 ID。 + /// public Guid? TargetUserId { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement? Metadata { get; set; } + /// + /// 租户编码。 + /// [StringLength(100)] public string? TenantCode { get; set; } @@ -64,15 +109,27 @@ public sealed class TrackReferralEventDto } } +/// +/// 绑定推荐请求 DTO。 +/// public sealed class BindReferralDto { + /// + /// 推荐码。 + /// [Required] [StringLength(100)] public string RefCode { get; set; } = string.Empty; + /// + /// 来源。 + /// [StringLength(100)] public string? Source { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement? Metadata { get; set; } public BindReferralCommand ToCommand() @@ -81,20 +138,38 @@ public sealed class BindReferralDto } } +/// +/// 推荐二维码请求 DTO。 +/// public sealed class ReferralQrcodeDto { + /// + /// 页码。 + /// [StringLength(500)] public string? Page { get; set; } + /// + /// 场景参数。 + /// [StringLength(128)] public string? Scene { get; set; } + /// + /// 服务提供方。 + /// [StringLength(100)] public string? Provider { get; set; } + /// + /// 二维码地址。 + /// [StringLength(2048)] public string? QrcodeUrl { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement? Metadata { get; set; } public ReferralQrcodeCommand ToCommand() @@ -103,10 +178,19 @@ public sealed class ReferralQrcodeDto } } +/// +/// 推荐统计查询参数。 +/// public sealed class ReferralStatsQueryDto { + /// + /// 推荐人用户 ID。 + /// public Guid? ReferrerUserId { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -116,19 +200,37 @@ public sealed class ReferralStatsQueryDto } } +/// +/// 推荐Conversion查询参数。 +/// public sealed class ReferralConversionQueryDto { + /// + /// 推荐人用户 ID。 + /// public Guid? ReferrerUserId { get; set; } + /// + /// 开始日期,格式为 yyyy-MM-dd。 + /// [RegularExpression("^\\d{4}-\\d{2}-\\d{2}$")] public string? StartDate { get; set; } + /// + /// 结束日期,格式为 yyyy-MM-dd。 + /// [RegularExpression("^\\d{4}-\\d{2}-\\d{2}$")] public string? EndDate { get; set; } + /// + /// 天数。 + /// [Range(1, 365)] public int? Days { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 100)] public int? Limit { get; set; } @@ -150,19 +252,37 @@ public sealed class ReferralConversionQueryDto } } +/// +/// 人工绑定推荐请求 DTO。 +/// public sealed class ManualBindReferralDto { + /// + /// 学生用户 ID。 + /// [Required] public Guid StudentUserId { get; set; } + /// + /// 推荐人用户 ID。 + /// [Required] public Guid ReferrerUserId { get; set; } + /// + /// 来源。 + /// [StringLength(100)] public string? Source { get; set; } + /// + /// 是否强制执行。 + /// public bool Force { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement? Metadata { get; set; } public ManualBindReferralCommand ToCommand() @@ -171,8 +291,14 @@ public sealed class ManualBindReferralDto } } +/// +/// 推荐团队查询参数。 +/// public sealed class ReferralTeamQueryDto { + /// + /// 上级成员用户 ID。 + /// public Guid? LeaderUserId { get; set; } public ReferralTeamQuery ToQuery() @@ -181,19 +307,37 @@ public sealed class ReferralTeamQueryDto } } +/// +/// 新增或更新推荐团队请求 DTO。 +/// public sealed class UpsertReferralTeamDto { + /// + /// 成员用户 ID。 + /// [Required] public Guid MemberUserId { get; set; } + /// + /// 上级成员用户 ID。 + /// public Guid? LeaderUserId { get; set; } + /// + /// 关系类型。 + /// [StringLength(50)] public string? RelationType { get; set; } + /// + /// 状态。 + /// [StringLength(50)] public string? Status { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement? Metadata { get; set; } public UpsertReferralTeamCommand ToCommand() diff --git a/Tiku.Api/Contracts/SaasBillingDtos.cs b/Tiku.Api/Contracts/SaasBillingDtos.cs new file mode 100644 index 0000000..8e7a4ce --- /dev/null +++ b/Tiku.Api/Contracts/SaasBillingDtos.cs @@ -0,0 +1,151 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json; +using Tiku.Application.PlatformBilling; +using Tiku.Domain.Platform; + +namespace Tiku.Api.Contracts; + +/// +/// 新增或更新SaaS功能请求 DTO。 +/// +public sealed record UpsertSaasFeatureDto( + Guid? Id, + [Required, MaxLength(120)] string Code, + [Required, MaxLength(200)] string Name, + [Required, MaxLength(100)] string Category, + [MaxLength(1000)] string? Description, + [Range(0, int.MaxValue)] int ReferencePriceCents, + [Required, MaxLength(10)] string Currency, + SaasFeatureStatus Status, + int SortOrder) +{ + public UpsertSaasFeatureCommand ToCommand() => new(Id, Code, Name, Category, Description, ReferencePriceCents, Currency, Status, SortOrder); +} + +/// +/// 新增或更新SaaS套餐请求 DTO。 +/// +public sealed record UpsertSaasOfferingDto( + Guid? Id, + [Required, MaxLength(120)] string Code, + [Required, MaxLength(200)] string Name, + SaasOfferingType Type, + SaasOfferingStatus Status, + [MaxLength(1000)] string? Description, + int SortOrder) +{ + public UpsertSaasOfferingCommand ToCommand() => new(Id, Code, Name, Type, Status, Description, SortOrder); +} + +/// +/// 新增或更新 SaaS 功能额度定义。 +/// +public sealed record UpsertSaasFeatureLimitDto( + Guid? Id, + [Required, MaxLength(120)] string MetricCode, + [Required, MaxLength(120)] string FeatureCode, + [Required, MaxLength(200)] string Name, + [Required, MaxLength(50)] string Unit, + SaasFeatureLimitKind Kind, + [Range(1, 100)] int WarningPercent, + bool IsHardLimit) +{ + public UpsertSaasFeatureLimitCommand ToCommand() => new( + Id, MetricCode, FeatureCode, Name, Unit, Kind, WarningPercent, IsHardLimit); +} + +/// +/// 新增或更新SaaS套餐版本请求 DTO。 +/// +public sealed record UpsertSaasOfferingVersionDto( + Guid? Id, + Guid OfferingId, + PlatformBillingCycle BillingCycle, + [Range(0, int.MaxValue)] int OriginalAmountCents, + [Range(0, int.MaxValue)] int AmountCents, + [Required, MaxLength(10)] string Currency, + DateTimeOffset? EffectiveAt, + IReadOnlyCollection? FeatureCodes, + IReadOnlyDictionary? Limits, + JsonElement Metadata) +{ + public UpsertSaasOfferingVersionCommand ToCommand() => new( + Id, OfferingId, BillingCycle, OriginalAmountCents, AmountCents, Currency, EffectiveAt, + FeatureCodes ?? [], Limits ?? new Dictionary(), Metadata); +} + +/// +/// 创建平台账务报价请求 DTO。 +/// +public sealed record CreatePlatformBillingQuoteDto( + Guid BaseOfferingVersionId, + IReadOnlyCollection? AddOnOfferingVersionIds, + PlatformBillingOrderPurpose Purpose, + [Required, MaxLength(200)] string IdempotencyKey) +{ + public CreatePlatformBillingQuoteCommand ToCommand() => new(BaseOfferingVersionId, AddOnOfferingVersionIds ?? [], Purpose, IdempotencyKey); +} + +/// +/// 创建平台账务订单请求 DTO。 +/// +public sealed record CreatePlatformBillingOrderDto(Guid QuoteId, [Required, MaxLength(200)] string IdempotencyKey) +{ + public CreatePlatformBillingOrderCommand ToCommand() => new(QuoteId, IdempotencyKey); +} + +/// +/// 创建平台账务支付请求 DTO。 +/// +public sealed record CreatePlatformBillingPaymentDto( + [Required, MaxLength(50)] string Provider, + [Required, MaxLength(50)] string Method, + [Required, MaxLength(200)] string IdempotencyKey, + string? OpenId, + string? ReturnUrl, + string? QuitUrl) +{ + public CreatePlatformBillingPaymentCommand ToCommand(string orderNo) => + new(orderNo, Provider, Method, IdempotencyKey, OpenId, ReturnUrl, QuitUrl); +} + +/// +/// 变更租户订阅请求 DTO。 +/// +public sealed record ChangeTenantSubscriptionDto( + Guid BaseOfferingVersionId, + IReadOnlyCollection? AddOnOfferingVersionIds, + [Required, MaxLength(200)] string IdempotencyKey) +{ + public ChangeTenantSubscriptionCommand ToCommand() => new(BaseOfferingVersionId, AddOnOfferingVersionIds ?? []); +} + +/// +/// Idempotent租户账务请求 DTO。 +/// +public sealed record IdempotentTenantBillingDto([Required, MaxLength(200)] string IdempotencyKey); + +/// +/// 确认人工平台支付请求 DTO。 +/// +public sealed record ConfirmManualPlatformPaymentDto( + Guid PaymentId, + string? ProviderTradeNo, + DateTimeOffset? PaidAt, + [Required, MaxLength(1000)] string Reason) +{ + public ConfirmManualPaymentCommand ToCommand() => new(PaymentId, ProviderTradeNo, PaidAt, Reason); +} + +/// +/// 新增或更新租户功能覆盖规则请求 DTO。 +/// +public sealed record UpsertTenantFeatureOverrideDto( + Guid TenantId, + [Required, MaxLength(120)] string FeatureCode, + TenantFeatureOverrideMode Mode, + DateTimeOffset? ExpiresAt, + [Required, MaxLength(1000)] string Reason) +{ + public UpsertTenantFeatureOverrideCommand ToCommand() => new(TenantId, FeatureCode, Mode, ExpiresAt, Reason); +} diff --git a/Tiku.Api/Contracts/ScorelineDtos.cs b/Tiku.Api/Contracts/ScorelineDtos.cs index 9ffc8fe..e3a35a3 100644 --- a/Tiku.Api/Contracts/ScorelineDtos.cs +++ b/Tiku.Api/Contracts/ScorelineDtos.cs @@ -3,27 +3,57 @@ using Tiku.Application.Scoreline; namespace Tiku.Api.Contracts; +/// +/// 分数线查询参数。 +/// public sealed class ScorelineQueryDto { + /// + /// 租户编码。 + /// [StringLength(100)] public string? TenantCode { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 院校 ID。 + /// public Guid? SchoolId { get; set; } + /// + /// 专业 ID。 + /// public Guid? MajorId { get; set; } + /// + /// 关键字。 + /// [StringLength(100)] public string? Keyword { get; set; } + /// + /// 年份。 + /// [Range(1900, 3000)] public int? Year { get; set; } + /// + /// 页码。 + /// [Range(1, 10000)] public int? Page { get; set; } + /// + /// 每页数量。 + /// [Range(1, 200)] public int? PageSize { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 2000)] public int? Limit { get; set; } diff --git a/Tiku.Api/Contracts/StudyContentDtos.cs b/Tiku.Api/Contracts/StudyContentDtos.cs index 6dfc483..e79ff79 100644 --- a/Tiku.Api/Contracts/StudyContentDtos.cs +++ b/Tiku.Api/Contracts/StudyContentDtos.cs @@ -3,28 +3,61 @@ using Tiku.Application.StudyContent; namespace Tiku.Api.Contracts; +/// +/// Study内容查询参数。 +/// public sealed class StudyContentQueryDto { + /// + /// 租户编码。 + /// [StringLength(100)] public string? TenantCode { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 单元 ID。 + /// public Guid? UnitId { get; set; } + /// + /// 科目 ID。 + /// public Guid? SubjectId { get; set; } + /// + /// 章节 ID。 + /// public Guid? ChapterId { get; set; } + /// + /// 内容入口 ID。 + /// public Guid? EntryId { get; set; } + /// + /// 内容节点 ID。 + /// public Guid? ContentNodeId { get; set; } + /// + /// 关键字。 + /// [StringLength(100)] public string? Keyword { get; set; } + /// + /// 是否包含正文内容。 + /// public bool IncludeContent { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 2000)] public int? Limit { get; set; } diff --git a/Tiku.Api/Contracts/TaxonomyDtos.cs b/Tiku.Api/Contracts/TaxonomyDtos.cs index 0d2dbef..37dcd2f 100644 --- a/Tiku.Api/Contracts/TaxonomyDtos.cs +++ b/Tiku.Api/Contracts/TaxonomyDtos.cs @@ -7,17 +7,41 @@ using Tiku.Domain.Content; namespace Tiku.Api.Contracts; +/// +/// 创建分类节点请求 DTO。 +/// public sealed class CreateTaxonomyNodeDto { + /// + /// 父节点 ID。 + /// public Guid? ParentId { get; set; } + /// + /// 父级来源。 + /// public QuestionSource? ParentSource { get; set; } + /// + /// 节点Type。 + /// [Required] public TaxonomyNodeType NodeType { get; set; } + /// + /// 编码。 + /// [Required, StringLength(100)] public string Code { get; set; } = string.Empty; + /// + /// 名称。 + /// [Required, StringLength(300)] public string Name { get; set; } = string.Empty; + /// + /// 排序值。 + /// public int SortOrder { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public CreateTaxonomyNodeCommand ToCommand() => new( diff --git a/Tiku.Api/Contracts/TenantAdminDirectDtos.cs b/Tiku.Api/Contracts/TenantAdminDirectDtos.cs index 8644d63..4b20b7b 100644 --- a/Tiku.Api/Contracts/TenantAdminDirectDtos.cs +++ b/Tiku.Api/Contracts/TenantAdminDirectDtos.cs @@ -5,12 +5,27 @@ using Tiku.Domain.Common; namespace Tiku.Api.Contracts; +/// +/// 租户管理端班级查询参数。 +/// public sealed class TenantAdminClassQueryDto { + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 关键字。 + /// public string? Keyword { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -20,14 +35,29 @@ public sealed class TenantAdminClassQueryDto } } +/// +/// 租户管理端班级成员查询参数。 +/// public sealed class TenantAdminClassMemberQueryDto { + /// + /// 班级 ID。 + /// [Required] public Guid ClassId { get; set; } + /// + /// 成员类型。 + /// public string? MemberType { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -37,13 +67,31 @@ public sealed class TenantAdminClassMemberQueryDto } } +/// +/// 租户管理端学生查询参数。 +/// public sealed class TenantAdminStudentQueryDto { + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 班级 ID。 + /// public Guid? ClassId { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 关键字。 + /// public string? Keyword { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -53,11 +101,23 @@ public sealed class TenantAdminStudentQueryDto } } +/// +/// 租户管理端学生活动查询参数。 +/// public sealed class TenantAdminStudentActivityQueryDto { + /// + /// 学生用户 ID。 + /// public Guid? StudentUserId { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -67,12 +127,27 @@ public sealed class TenantAdminStudentActivityQueryDto } } +/// +/// 租户管理端成员查询参数。 +/// public sealed class TenantAdminMemberQueryDto { + /// + /// 角色。 + /// public string? Role { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 关键字。 + /// public string? Keyword { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -82,12 +157,27 @@ public sealed class TenantAdminMemberQueryDto } } +/// +/// 租户管理端审计日志查询参数。 +/// public sealed class TenantAdminAuditLogQueryDto { + /// + /// 操作。 + /// public string? Action { get; set; } + /// + /// 目标类型。 + /// public string? TargetType { get; set; } + /// + /// 操作人用户 ID。 + /// public Guid? ActorUserId { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -97,11 +187,23 @@ public sealed class TenantAdminAuditLogQueryDto } } +/// +/// 租户管理端徽章查询参数。 +/// public sealed class TenantAdminBadgeQueryDto { + /// + /// 分类。 + /// public string? Category { get; set; } + /// + /// 是否包含停用数据。 + /// public bool IncludeInactive { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -111,11 +213,23 @@ public sealed class TenantAdminBadgeQueryDto } } +/// +/// 租户管理端徽章授予查询参数。 +/// public sealed class TenantAdminBadgeGrantQueryDto { + /// + /// 用户 ID。 + /// public Guid? UserId { get; set; } + /// + /// 徽章 ID。 + /// public Guid? BadgeId { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -125,12 +239,27 @@ public sealed class TenantAdminBadgeGrantQueryDto } } +/// +/// 租户管理端通知查询参数。 +/// public sealed class TenantAdminNotificationQueryDto { + /// + /// 用户 ID。 + /// public Guid? UserId { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 通知类型。 + /// public string? NotificationType { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -140,13 +269,31 @@ public sealed class TenantAdminNotificationQueryDto } } +/// +/// 租户管理端反馈查询参数。 +/// public sealed class TenantAdminFeedbackQueryDto { + /// + /// 用户 ID。 + /// public Guid? UserId { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 类型。 + /// public string? Type { get; set; } + /// + /// 关键字。 + /// public string? Keyword { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } @@ -156,16 +303,46 @@ public sealed class TenantAdminFeedbackQueryDto } } +/// +/// 新增或更新租户管理端班级请求 DTO。 +/// public sealed class UpsertTenantAdminClassDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 编码。 + /// public string? Code { get; set; } + /// + /// 名称。 + /// public required string Name { get; set; } + /// + /// 说明。 + /// public string? Description { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public UpsertTenantAdminClassCommand ToCommand() @@ -174,13 +351,34 @@ public sealed class UpsertTenantAdminClassDto } } +/// +/// 租户管理端用户查找请求 DTO。 +/// public sealed class TenantAdminUserLookupDto { + /// + /// 用户 ID。 + /// public Guid? UserId { get; set; } + /// + /// 用户名。 + /// public string? Username { get; set; } + /// + /// 邮箱。 + /// public string? Email { get; set; } + /// + /// 手机号。 + /// public string? Phone { get; set; } + /// + /// 名称。 + /// public string? Name { get; set; } + /// + /// 头像地址。 + /// public string? AvatarUrl { get; set; } public UserLookupCommand ToCommand() @@ -189,14 +387,32 @@ public sealed class TenantAdminUserLookupDto } } +/// +/// 新增或更新租户管理端班级成员请求 DTO。 +/// public sealed class UpsertTenantAdminClassMemberDto { + /// + /// 班级 ID。 + /// [Required] public Guid ClassId { get; set; } + /// + /// 用户信息。 + /// public TenantAdminUserLookupDto User { get; set; } = new(); + /// + /// 成员类型。 + /// public string? MemberType { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public UpsertTenantAdminClassMemberCommand ToCommand() @@ -205,22 +421,58 @@ public sealed class UpsertTenantAdminClassMemberDto } } +/// +/// 移除租户管理端班级成员请求 DTO。 +/// public sealed class RemoveTenantAdminClassMemberDto { + /// + /// 班级成员 ID。 + /// [Required] public Guid ClassMemberId { get; set; } } +/// +/// 新增或更新租户管理端学生请求 DTO。 +/// public sealed class UpsertTenantAdminStudentDto { + /// + /// 用户信息。 + /// public TenantAdminUserLookupDto User { get; set; } = new(); + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 已选择院校 ID。 + /// public Guid? SelectedSchoolId { get; set; } + /// + /// 已选择专业 ID。 + /// public Guid? SelectedMajorId { get; set; } + /// + /// 头像预设。 + /// public string? AvatarPreset { get; set; } + /// + /// 统计数据。 + /// public JsonElement Stats { get; set; } = JsonDefaults.Object(); + /// + /// 进度数据。 + /// public JsonElement Progress { get; set; } = JsonDefaults.Object(); + /// + /// 模块选择数据。 + /// public JsonElement ModuleSelections { get; set; } = JsonDefaults.Object(); + /// + /// 原始用户资料。 + /// public JsonElement RawProfile { get; set; } = JsonDefaults.Object(); public UpsertTenantAdminStudentCommand ToCommand() @@ -238,12 +490,24 @@ public sealed class UpsertTenantAdminStudentDto } } +/// +/// 更新租户管理端学生状态请求 DTO。 +/// public sealed class UpdateTenantAdminStudentStatusDto { + /// + /// 用户 ID。 + /// [Required] public Guid UserId { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 原因。 + /// public string? Reason { get; set; } public UpdateTenantAdminStudentStatusCommand ToCommand() @@ -252,12 +516,30 @@ public sealed class UpdateTenantAdminStudentStatusDto } } +/// +/// 租户管理端学生导入行请求 DTO。 +/// public sealed class TenantAdminStudentImportRowDto { + /// + /// 用户信息。 + /// public TenantAdminUserLookupDto User { get; set; } = new(); + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 班级 ID。 + /// public Guid? ClassId { get; set; } + /// + /// 头像预设。 + /// public string? AvatarPreset { get; set; } + /// + /// 原始用户资料。 + /// public JsonElement RawProfile { get; set; } = JsonDefaults.Object(); public TenantAdminStudentImportRowCommand ToCommand() @@ -266,8 +548,14 @@ public sealed class TenantAdminStudentImportRowDto } } +/// +/// 租户管理端学生导入请求 DTO。 +/// public sealed class TenantAdminStudentImportDto { + /// + /// 数据行列表。 + /// [Required] public IReadOnlyCollection Rows { get; set; } = []; @@ -277,11 +565,20 @@ public sealed class TenantAdminStudentImportDto } } +/// +/// 租户管理端批量分配班级请求 DTO。 +/// public sealed class TenantAdminBulkAssignClassDto { + /// + /// 班级 ID。 + /// [Required] public Guid ClassId { get; set; } + /// + /// 用户 ID 列表。 + /// [Required] public IReadOnlyCollection UserIds { get; set; } = []; @@ -291,13 +588,25 @@ public sealed class TenantAdminBulkAssignClassDto } } +/// +/// 租户管理端批量状态请求 DTO。 +/// public sealed class TenantAdminBulkStatusDto { + /// + /// 用户 ID 列表。 + /// [Required] public IReadOnlyCollection UserIds { get; set; } = []; + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 原因。 + /// [StringLength(1000)] public string? Reason { get; set; } @@ -307,30 +616,57 @@ public sealed class TenantAdminBulkStatusDto } } +/// +/// 新增或更新租户督导规则请求 DTO。 +/// public sealed class UpsertTenantSupervisionRuleDto { + /// + /// 编码。 + /// [Required] [StringLength(100)] public string Code { get; set; } = string.Empty; + /// + /// 标题。 + /// [Required] [StringLength(200)] public string Title { get; set; } = string.Empty; + /// + /// 是否启用。 + /// public bool Enabled { get; set; } = true; + /// + /// 未签到天数。 + /// [Range(0, 3650)] public int? DaysWithoutCheckIn { get; set; } + /// + /// 当天最大答题数。 + /// [Range(0, int.MaxValue)] public int? MaxQuestionsAnsweredToday { get; set; } + /// + /// 跟进类型。 + /// [StringLength(50)] public string? FollowupType { get; set; } + /// + /// 优先级。 + /// [StringLength(50)] public string? Priority { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public UpsertTenantSupervisionRuleCommand ToCommand() @@ -347,10 +683,22 @@ public sealed class UpsertTenantSupervisionRuleDto } } +/// +/// 租户督导生成请求 DTO。 +/// public sealed class TenantSupervisionGenerateDto { + /// + /// 用户 ID 列表。 + /// public IReadOnlyCollection? UserIds { get; set; } + /// + /// 分配处理人用户 ID。 + /// public Guid? AssignedToUserId { get; set; } + /// + /// 到期时间。 + /// public DateTimeOffset? DueAt { get; set; } public TenantSupervisionGenerateCommand ToCommand() @@ -359,17 +707,41 @@ public sealed class TenantSupervisionGenerateDto } } +/// +/// 新增或更新租户管理端学生Note请求 DTO。 +/// public sealed class UpsertTenantAdminStudentNoteDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 学生用户 ID。 + /// [Required] public Guid StudentUserId { get; set; } + /// + /// 备注类型。 + /// public string? NoteType { get; set; } + /// + /// 内容。 + /// public required string Content { get; set; } + /// + /// 可见性。 + /// public string? Visibility { get; set; } + /// + /// 是否置顶。 + /// public bool? IsPinned { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public UpsertTenantAdminStudentNoteCommand ToCommand() @@ -378,21 +750,57 @@ public sealed class UpsertTenantAdminStudentNoteDto } } +/// +/// 新增或更新租户管理端学生跟进请求 DTO。 +/// public sealed class UpsertTenantAdminStudentFollowupDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 学生用户 ID。 + /// [Required] public Guid StudentUserId { get; set; } + /// + /// 分配处理人用户 ID。 + /// public Guid? AssignedToUserId { get; set; } + /// + /// 班级 ID。 + /// public Guid? ClassId { get; set; } + /// + /// 标题。 + /// public required string Title { get; set; } + /// + /// 说明。 + /// public string? Description { get; set; } + /// + /// 跟进类型。 + /// public string? FollowupType { get; set; } + /// + /// 优先级。 + /// public string? Priority { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 到期时间。 + /// public DateTimeOffset? DueAt { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public UpsertTenantAdminStudentFollowupCommand ToCommand() @@ -412,12 +820,30 @@ public sealed class UpsertTenantAdminStudentFollowupDto } } +/// +/// 新增或更新租户管理端成员请求 DTO。 +/// public sealed class UpsertTenantAdminMemberDto { + /// + /// 成员关系 ID。 + /// public Guid? MembershipId { get; set; } + /// + /// 用户信息。 + /// public TenantAdminUserLookupDto User { get; set; } = new(); + /// + /// 角色。 + /// public string? Role { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 主要角色。 + /// public string? PrimaryRole { get; set; } public UpsertTenantAdminMemberCommand ToCommand() @@ -426,21 +852,54 @@ public sealed class UpsertTenantAdminMemberDto } } +/// +/// 禁用租户管理端成员请求 DTO。 +/// public sealed class DisableTenantAdminMemberDto { + /// + /// 成员关系 ID。 + /// [Required] public Guid MembershipId { get; set; } } +/// +/// 新增或更新租户品牌请求 DTO。 +/// public sealed class UpsertTenantBrandingDto { + /// + /// 品牌名称。 + /// public required string BrandName { get; set; } + /// + /// 品牌简称。 + /// public string? ShortName { get; set; } + /// + /// 品牌标语。 + /// public string? Slogan { get; set; } + /// + /// 机构名称。 + /// public string? OrganizationName { get; set; } + /// + /// Logo 地址。 + /// public string? LogoUrl { get; set; } + /// + /// 站点图标地址。 + /// public string? FaviconUrl { get; set; } + /// + /// 客服微信。 + /// public string? ServiceWechat { get; set; } + /// + /// 客服账号名称。 + /// public string? ServiceAccountName { get; set; } public UpsertTenantBrandingCommand ToCommand() @@ -457,10 +916,22 @@ public sealed class UpsertTenantBrandingDto } } +/// +/// 新增或更新租户设置请求 DTO。 +/// public sealed class UpsertTenantSettingsDto { + /// + /// 前端功能开关。 + /// public JsonElement FeatureFlags { get; set; } = JsonDefaults.Object(); + /// + /// 管理端功能开关。 + /// public JsonElement AdminFeatureFlags { get; set; } = JsonDefaults.Object(); + /// + /// 公开配置。 + /// public JsonElement PublicConfig { get; set; } = JsonDefaults.Object(); public UpsertTenantSettingsCommand ToCommand() @@ -469,10 +940,22 @@ public sealed class UpsertTenantSettingsDto } } +/// +/// 预览租户主题请求 DTO。 +/// public sealed class PreviewTenantThemeDto { + /// + /// 模板编码。 + /// public required string TemplateCode { get; set; } + /// + /// 主题配置。 + /// public JsonElement Theme { get; set; } = JsonDefaults.Object(); + /// + /// 公开资源配置。 + /// public JsonElement PublicAssets { get; set; } = JsonDefaults.Object(); public PreviewTenantThemeCommand ToCommand() @@ -481,11 +964,26 @@ public sealed class PreviewTenantThemeDto } } +/// +/// 发布租户主题请求 DTO。 +/// public sealed class PublishTenantThemeDto { + /// + /// 是否使用草稿配置。 + /// public bool? UseDraft { get; set; } + /// + /// 模板编码。 + /// public string? TemplateCode { get; set; } + /// + /// 主题配置。 + /// public JsonElement Theme { get; set; } = JsonDefaults.Object(); + /// + /// 公开资源配置。 + /// public JsonElement PublicAssets { get; set; } = JsonDefaults.Object(); public PublishTenantThemeCommand ToCommand() @@ -494,10 +992,22 @@ public sealed class PublishTenantThemeDto } } +/// +/// 创建租户域名请求 DTO。 +/// public sealed class CreateTenantDomainDto { + /// + /// 访问域名。 + /// public required string Host { get; set; } + /// + /// 域名类型。 + /// public string? DomainType { get; set; } + /// + /// 是否主域名。 + /// public bool IsPrimary { get; set; } public CreateTenantDomainCommand ToCommand() @@ -506,13 +1016,34 @@ public sealed class CreateTenantDomainDto } } +/// +/// 新增或更新租户身份源Provider请求 DTO。 +/// public sealed class UpsertTenantIdentityProviderDto { + /// + /// 服务提供方。 + /// public required string Provider { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 显示名称。 + /// public string? DisplayName { get; set; } + /// + /// 密钥引用。 + /// public string? SecretRef { get; set; } + /// + /// 优先级。 + /// public int? Priority { get; set; } + /// + /// 公开配置内容。 + /// public JsonElement ConfigPublic { get; set; } = JsonDefaults.Object(); public UpsertTenantIdentityProviderCommand ToCommand() @@ -521,21 +1052,66 @@ public sealed class UpsertTenantIdentityProviderDto } } +/// +/// 新增或更新租户管理端徽章请求 DTO。 +/// public sealed class UpsertTenantAdminBadgeDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 名称。 + /// public required string Name { get; set; } + /// + /// 说明。 + /// public string? Description { get; set; } + /// + /// 分类。 + /// public string? Category { get; set; } + /// + /// 图标地址。 + /// public string? IconUrl { get; set; } + /// + /// 级别。 + /// public int? Level { get; set; } + /// + /// 解锁类型。 + /// public string? UnlockType { get; set; } + /// + /// 条件字段。 + /// public string? ConditionField { get; set; } + /// + /// 条件运算符。 + /// public string? ConditionOperator { get; set; } + /// + /// 条件值。 + /// public decimal? ConditionValue { get; set; } + /// + /// 条件扩展配置。 + /// public JsonElement ConditionExtra { get; set; } = JsonDefaults.Object(); + /// + /// 显示顺序。 + /// public int? Order { get; set; } + /// + /// 是否启用。 + /// public bool? IsActive { get; set; } public UpsertTenantAdminBadgeCommand ToCommand() @@ -558,17 +1134,38 @@ public sealed class UpsertTenantAdminBadgeDto } } +/// +/// 授予租户管理端徽章请求 DTO。 +/// public sealed class GrantTenantAdminBadgeDto { + /// + /// 用户 ID。 + /// [Required] public Guid UserId { get; set; } + /// + /// 徽章 ID。 + /// [Required] public Guid BadgeId { get; set; } + /// + /// 历史系统 ID。 + /// public string? LegacyId { get; set; } + /// + /// 备注。 + /// public string? Note { get; set; } + /// + /// 授予时间。 + /// public DateTimeOffset? GrantedAt { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public GrantTenantAdminBadgeCommand ToCommand() @@ -577,20 +1174,56 @@ public sealed class GrantTenantAdminBadgeDto } } +/// +/// 新增或更新租户管理端通知请求 DTO。 +/// public sealed class UpsertTenantAdminNotificationDto { + /// + /// 用户 ID。 + /// [Required] public Guid UserId { get; set; } + /// + /// 通知类型。 + /// public required string NotificationType { get; set; } + /// + /// 严重级别。 + /// public string? Severity { get; set; } + /// + /// 标题。 + /// public required string Title { get; set; } + /// + /// 消息内容。 + /// public required string Message { get; set; } + /// + /// 操作按钮文案。 + /// public string? ActionLabel { get; set; } + /// + /// 操作路径。 + /// public string? ActionPath { get; set; } + /// + /// 来源类型。 + /// public string? SourceType { get; set; } + /// + /// 来源 ID。 + /// public Guid? SourceId { get; set; } + /// + /// 去重键。 + /// public string? DedupeKey { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public UpsertTenantAdminNotificationCommand ToCommand() @@ -610,15 +1243,36 @@ public sealed class UpsertTenantAdminNotificationDto } } +/// +/// 更新租户管理端反馈请求 DTO。 +/// public sealed class UpdateTenantAdminFeedbackDto { + /// + /// 反馈 ID。 + /// [Required] public Guid FeedbackId { get; set; } + /// + /// 状态。 + /// public string? Status { get; set; } + /// + /// 优先级。 + /// public string? Priority { get; set; } + /// + /// 处理结果。 + /// public string? Resolution { get; set; } + /// + /// 备注。 + /// public string? Note { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public UpdateTenantAdminFeedbackCommand ToCommand() diff --git a/Tiku.Api/Contracts/TenantCommerceDtos.cs b/Tiku.Api/Contracts/TenantCommerceDtos.cs index ee2d6bc..1c94cee 100644 --- a/Tiku.Api/Contracts/TenantCommerceDtos.cs +++ b/Tiku.Api/Contracts/TenantCommerceDtos.cs @@ -7,41 +7,83 @@ using Tiku.Domain.Tenancy; namespace Tiku.Api.Contracts; +/// +/// 租户交易查询参数。 +/// public sealed class TenantCommerceQueryDto { + /// + /// 服务提供方。 + /// [StringLength(50)] public string? Provider { get; set; } + /// + /// 状态。 + /// [StringLength(32)] public string? Status { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 200)] public int? Limit { get; set; } + /// + /// 用户 ID。 + /// public Guid? UserId { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } } +/// +/// 新增或更新支付账号请求 DTO。 +/// public sealed class UpsertPaymentAccountDto { + /// + /// 服务提供方。 + /// [Required] [StringLength(50)] public string Provider { get; set; } = string.Empty; + /// + /// 模式。 + /// [StringLength(50)] public string? Mode { get; set; } = "TenantCollect"; + /// + /// 显示名称。 + /// [StringLength(200)] public string? DisplayName { get; set; } + /// + /// 状态。 + /// public TenantExternalProviderStatus Status { get; set; } = TenantExternalProviderStatus.Disabled; + /// + /// 密钥引用。 + /// [StringLength(300)] public string? SecretRef { get; set; } + /// + /// 优先级。 + /// public int? Priority { get; set; } + /// + /// 公开配置内容。 + /// public JsonElement ConfigPublic { get; set; } = JsonDefaults.Object(); public UpsertPaymentAccountCommand ToCommand() @@ -50,25 +92,49 @@ public sealed class UpsertPaymentAccountDto } } +/// +/// 新增或更新租户Secret请求 DTO。 +/// public sealed class UpsertTenantSecretDto { + /// + /// 用途。 + /// [Required] [StringLength(80)] public string Purpose { get; set; } = "payment"; + /// + /// 服务提供方。 + /// [Required] [StringLength(50)] public string Provider { get; set; } = string.Empty; + /// + /// 密钥键。 + /// [Required] [StringLength(120)] public string SecretKey { get; set; } = string.Empty; + /// + /// 密钥引用。 + /// [StringLength(300)] public string SecretRef { get; set; } = string.Empty; + /// + /// 状态。 + /// public TenantSecretStatus Status { get; set; } = TenantSecretStatus.Active; + /// + /// 密钥载荷。 + /// public JsonElement SecretPayload { get; set; } = JsonDefaults.Object(); + /// + /// 过期时间。 + /// public DateTimeOffset? ExpiresAt { get; set; } public UpsertTenantSecretCommand ToCommand() @@ -84,32 +150,62 @@ public sealed class UpsertTenantSecretDto } } +/// +/// 创建编码批次请求 DTO。 +/// public sealed class CreateCodeBatchDto { + /// + /// 名称。 + /// [Required] [StringLength(200)] public string Name { get; set; } = string.Empty; + /// + /// 总数量。 + /// [Range(1, 1000)] public int TotalCount { get; set; } + /// + /// 天数。 + /// [Range(1, 3650)] public int Days { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 售卖类型。 + /// [StringLength(50)] public string? SaleType { get; set; } + /// + /// 渠道。 + /// [StringLength(100)] public string? Channel { get; set; } + /// + /// 默认单价,单位为分。 + /// [Range(0, int.MaxValue)] public int? DefaultUnitPriceCents { get; set; } + /// + /// 成本价,单位为分。 + /// [Range(0, int.MaxValue)] public int? CostPriceCents { get; set; } + /// + /// 备注。 + /// [StringLength(1000)] public string? Remark { get; set; } @@ -128,15 +224,27 @@ public sealed class CreateCodeBatchDto } } +/// +/// 核销激活码编码请求 DTO。 +/// public sealed class RedeemActivationCodeDto { + /// + /// 编码。 + /// [Required] [StringLength(100)] public string Code { get; set; } = string.Empty; + /// + /// 用户 ID。 + /// [Required] public Guid UserId { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } public RedeemActivationCodeCommand ToCommand() @@ -145,34 +253,76 @@ public sealed class RedeemActivationCodeDto } } +/// +/// 新增或更新积分任务请求 DTO。 +/// public sealed class UpsertPointTaskDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 任务键。 + /// [Required] [StringLength(100)] public string TaskKey { get; set; } = string.Empty; + /// + /// 标题。 + /// [Required] [StringLength(200)] public string Title { get; set; } = string.Empty; + /// + /// 说明。 + /// [StringLength(1000)] public string? Description { get; set; } + /// + /// 任务类型。 + /// public PointActivityTaskType TaskType { get; set; } = PointActivityTaskType.Manual; + /// + /// 状态。 + /// public PointActivityTaskStatus Status { get; set; } = PointActivityTaskStatus.Active; + /// + /// 积分数量。 + /// [Range(1, int.MaxValue)] public int Points { get; set; } + /// + /// 每用户最多领取次数。 + /// [Range(1, 1000)] public int MaxClaimsPerUser { get; set; } = 1; + /// + /// 开始时间。 + /// public DateTimeOffset? StartsAt { get; set; } + /// + /// 结束时间。 + /// public DateTimeOffset? EndsAt { get; set; } + /// + /// 排序值。 + /// public int SortOrder { get; set; } + /// + /// 规则配置。 + /// public JsonElement Rules { get; set; } = JsonDefaults.Object(); + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public UpsertPointTaskCommand ToCommand() @@ -194,38 +344,86 @@ public sealed class UpsertPointTaskDto } } +/// +/// 新增或更新积分兑换Item请求 DTO。 +/// public sealed class UpsertPointExchangeItemDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 地区 ID。 + /// public Guid? RegionId { get; set; } + /// + /// 兑换项键。 + /// [Required] [StringLength(100)] public string ItemKey { get; set; } = string.Empty; + /// + /// 名称。 + /// [Required] [StringLength(200)] public string Name { get; set; } = string.Empty; + /// + /// 说明。 + /// [StringLength(1000)] public string? Description { get; set; } + /// + /// 兑换项类型。 + /// public PointExchangeItemType ItemType { get; set; } = PointExchangeItemType.Entitlement; + /// + /// 状态。 + /// public PointExchangeItemStatus Status { get; set; } = PointExchangeItemStatus.Active; + /// + /// 所需积分。 + /// [Range(1, int.MaxValue)] public int PointsCost { get; set; } + /// + /// 库存数量。 + /// [Range(0, int.MaxValue)] public int? Stock { get; set; } + /// + /// 天数。 + /// [Range(0, 3650)] public int? Days { get; set; } + /// + /// 排序值。 + /// public int SortOrder { get; set; } + /// + /// 开始时间。 + /// public DateTimeOffset? StartsAt { get; set; } + /// + /// 结束时间。 + /// public DateTimeOffset? EndsAt { get; set; } + /// + /// 履约载荷。 + /// public JsonElement FulfillmentPayload { get; set; } = JsonDefaults.Object(); + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public UpsertPointExchangeItemCommand ToCommand() @@ -249,20 +447,41 @@ public sealed class UpsertPointExchangeItemDto } } +/// +/// 创建退款Request请求 DTO。 +/// public sealed class CreateRefundRequestDto { + /// + /// 订单 ID。 + /// [Required] public Guid OrderId { get; set; } + /// + /// 支付记录 ID。 + /// public Guid? PaymentId { get; set; } + /// + /// 金额,单位为分。 + /// [Range(1, int.MaxValue)] public int AmountCents { get; set; } + /// + /// 原因。 + /// [StringLength(1000)] public string? Reason { get; set; } + /// + /// 权益动作。 + /// public RefundEntitlementAction EntitlementAction { get; set; } = RefundEntitlementAction.RevokeOnSuccess; + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public CreateRefundRequestCommand ToCommand() @@ -271,16 +490,31 @@ public sealed class CreateRefundRequestDto } } +/// +/// 更新退款状态请求 DTO。 +/// public sealed class UpdateRefundStatusDto { + /// + /// 退款申请 ID。 + /// [Required] public Guid RefundRequestId { get; set; } + /// + /// 状态。 + /// public CommerceRefundStatus Status { get; set; } + /// + /// 原因。 + /// [StringLength(1000)] public string? Reason { get; set; } + /// + /// 渠道退款单号。 + /// [StringLength(200)] public string? ProviderRefundNo { get; set; } @@ -290,23 +524,47 @@ public sealed class UpdateRefundStatusDto } } +/// +/// 创建对账批次请求 DTO。 +/// public sealed class CreateReconciliationBatchDto { + /// + /// 服务提供方。 + /// [Required] [StringLength(50)] public string Provider { get; set; } = string.Empty; + /// + /// 账单日期。 + /// public DateOnly BillDate { get; set; } + /// + /// 账单类型。 + /// public ReconciliationBillType BillType { get; set; } = ReconciliationBillType.Combined; + /// + /// 来源。 + /// public ReconciliationSource Source { get; set; } = ReconciliationSource.ManualUpload; + /// + /// 来源名称。 + /// [StringLength(200)] public string? SourceName { get; set; } + /// + /// 来源哈希。 + /// [Required] [StringLength(200)] public string SourceHash { get; set; } = string.Empty; + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public CreateReconciliationBatchCommand ToCommand() @@ -315,17 +573,35 @@ public sealed class CreateReconciliationBatchDto } } +/// +/// 更新对账Issue请求 DTO。 +/// public sealed class UpdateReconciliationIssueDto { + /// + /// 工单 ID。 + /// [Required] public Guid IssueId { get; set; } + /// + /// 状态。 + /// public ReconciliationIssueStatus Status { get; set; } = ReconciliationIssueStatus.Investigating; + /// + /// 处理类型。 + /// public ReconciliationResolutionType ResolutionType { get; set; } = ReconciliationResolutionType.None; + /// + /// 备注。 + /// [StringLength(1000)] public string? Note { get; set; } + /// + /// 分配处理人。 + /// public Guid? AssignedTo { get; set; } public UpdateReconciliationIssueCommand ToCommand() @@ -334,19 +610,37 @@ public sealed class UpdateReconciliationIssueDto } } +/// +/// 预览对账导入请求 DTO。 +/// public sealed class PreviewReconciliationImportDto { + /// + /// 服务提供方。 + /// [Required] [StringLength(50)] public string Provider { get; set; } = string.Empty; + /// + /// 账单日期。 + /// public DateOnly BillDate { get; set; } + /// + /// 账单类型。 + /// public ReconciliationBillType BillType { get; set; } = ReconciliationBillType.Combined; + /// + /// 来源名称。 + /// [Required] [StringLength(300)] public string SourceName { get; set; } = string.Empty; + /// + /// 数据行列表。 + /// public JsonElement Rows { get; set; } = JsonDefaults.Array(); public PreviewReconciliationImportCommand ToPreviewCommand() => new(Provider, BillDate, BillType, SourceName, Rows); @@ -354,29 +648,68 @@ public sealed class PreviewReconciliationImportDto public ImportReconciliationCommand ToImportCommand() => new(Provider, BillDate, BillType, SourceName, Rows); } +/// +/// 创建调账凭证请求 DTO。 +/// public sealed class CreateAdjustmentVoucherDto { + /// + /// 工单 ID。 + /// public Guid? IssueId { get; set; } + /// + /// 批次 ID。 + /// public Guid? BatchId { get; set; } + /// + /// 兑换项 ID。 + /// public Guid? ItemId { get; set; } + /// + /// 订单 ID。 + /// public Guid? OrderId { get; set; } + /// + /// 支付记录 ID。 + /// public Guid? PaymentId { get; set; } + /// + /// 退款申请 ID。 + /// public Guid? RefundRequestId { get; set; } + /// + /// 方向。 + /// public CommerceAdjustmentDirection Direction { get; set; } = CommerceAdjustmentDirection.IncreaseRevenue; + /// + /// 金额,单位为分。 + /// [Range(1, int.MaxValue)] public int AmountCents { get; set; } + /// + /// 币种。 + /// [StringLength(10)] public string? Currency { get; set; } + /// + /// 原因。 + /// [Required] [StringLength(1000)] public string Reason { get; set; } = string.Empty; + /// + /// 证明资产键。 + /// [StringLength(500)] public string? ProofAssetKey { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonDefaults.Object(); public CreateAdjustmentVoucherCommand ToCommand() => new( @@ -394,56 +727,110 @@ public sealed class CreateAdjustmentVoucherDto Metadata); } +/// +/// 更新调账凭证状态请求 DTO。 +/// public sealed class UpdateAdjustmentVoucherStatusDto { + /// + /// 凭证 ID。 + /// [Required] public Guid VoucherId { get; set; } + /// + /// 状态。 + /// public CommerceAdjustmentVoucherStatus Status { get; set; } = CommerceAdjustmentVoucherStatus.PendingReview; + /// + /// 备注。 + /// [StringLength(1000)] public string? Note { get; set; } public UpdateAdjustmentVoucherStatusCommand ToCommand() => new(VoucherId, Status, Note); } +/// +/// RequestProviderBill任务请求 DTO。 +/// public sealed class RequestProviderBillJobDto { + /// + /// 服务提供方。 + /// [Required] [StringLength(50)] public string Provider { get; set; } = string.Empty; + /// + /// 账单日期。 + /// public DateOnly BillDate { get; set; } + /// + /// 账单类型。 + /// public ReconciliationBillType BillType { get; set; } = ReconciliationBillType.Combined; + /// + /// 计划运行时间。 + /// public DateTimeOffset? RunAfter { get; set; } public RequestProviderBillJobCommand ToCommand() => new(Provider, BillDate, BillType, RunAfter); } +/// +/// 退款通知请求 DTO。 +/// public sealed class RefundNotificationDto { + /// + /// 退款单号。 + /// [Required] [StringLength(100)] public string RefundNo { get; set; } = string.Empty; + /// + /// 渠道退款单号。 + /// [StringLength(200)] public string? ProviderRefundNo { get; set; } + /// + /// 状态。 + /// public CommerceRefundStatus Status { get; set; } = CommerceRefundStatus.Succeeded; + /// + /// 事件 ID。 + /// [StringLength(200)] public string? EventId { get; set; } + /// + /// 任务载荷。 + /// public JsonElement Payload { get; set; } = JsonDefaults.Object(); public RefundNotificationCommand ToCommand(string provider) => new(provider, RefundNo, ProviderRefundNo, Status, EventId, Payload); } +/// +/// 更新积分兑换订单状态请求 DTO。 +/// public sealed class UpdatePointExchangeOrderStatusDto { + /// + /// 订单 ID。 + /// [Required] public Guid OrderId { get; set; } + /// + /// 状态。 + /// public PointExchangeOrderStatus Status { get; set; } public UpdatePointExchangeOrderStatusCommand ToCommand() @@ -452,29 +839,62 @@ public sealed class UpdatePointExchangeOrderStatusDto } } +/// +/// 新增或更新租户优惠券请求 DTO。 +/// public sealed class UpsertTenantCouponDto { + /// + /// ID。 + /// public Guid? Id { get; set; } + /// + /// 编码。 + /// [Required] [StringLength(100)] public string Code { get; set; } = string.Empty; + /// + /// 套餐 ID。 + /// public Guid? PlanId { get; set; } + /// + /// 优惠类型。 + /// public DiscountType DiscountType { get; set; } = DiscountType.Fixed; + /// + /// 优惠值。 + /// [Range(typeof(decimal), "0.01", "999999")] public decimal DiscountValue { get; set; } + /// + /// 有效期开始时间。 + /// public DateTimeOffset? ValidFrom { get; set; } + /// + /// 有效期结束时间。 + /// public DateTimeOffset? ValidTo { get; set; } + /// + /// 最大使用次数。 + /// [Range(1, int.MaxValue)] public int? MaxUses { get; set; } + /// + /// 来源。 + /// [StringLength(50)] public string? Source { get; set; } + /// + /// 备注。 + /// [StringLength(1000)] public string? Remark { get; set; } diff --git a/Tiku.Api/Contracts/TenantDtos.cs b/Tiku.Api/Contracts/TenantDtos.cs index cb48059..7e1e540 100644 --- a/Tiku.Api/Contracts/TenantDtos.cs +++ b/Tiku.Api/Contracts/TenantDtos.cs @@ -12,14 +12,14 @@ namespace Tiku.Api.Contracts; public sealed class TenantResolveQueryDto { /// - /// 要解析的访问域名,例如 student.example.com。本地开发也可以直接传 localhost。 + /// 访问域名。 /// [StringLength(253)] [Description("要解析的访问域名,例如 student.example.com。本地开发也可以直接传 localhost。")] public string? Host { get; set; } /// - /// 租户编码;本地开发或无独立域名时使用,例如 master。 + /// 租户编码。 /// [StringLength(100)] [Description("租户编码;本地开发或无独立域名时使用,例如 master。")] @@ -34,6 +34,9 @@ public sealed class TenantResolveQueryDto /// 面向前端公开的功能开关。 /// 面向管理端公开的功能开关。 /// 可公开的租户配置,不包含密钥或内部配置。 +/// +/// 租户解析Response请求 DTO。 +/// public sealed record TenantResolveResponseDto( PublicTenantDto Tenant, PublicTenantBrandingDto Branding, @@ -50,6 +53,9 @@ public sealed record TenantResolveResponseDto( /// 租户状态。 /// 租户模式。 /// 当前匹配到的访问域名。 +/// +/// 公开租户请求 DTO。 +/// public sealed record PublicTenantDto( Guid Id, string Slug, @@ -70,6 +76,9 @@ public sealed record PublicTenantDto( /// 公众号或服务号名称。 /// 公开主题配置。 /// 公开资源配置。 +/// +/// 公开租户品牌请求 DTO。 +/// public sealed record PublicTenantBrandingDto( string? BrandName, string? ShortName, diff --git a/Tiku.Api/Contracts/TenantFrontendConfigDtos.cs b/Tiku.Api/Contracts/TenantFrontendConfigDtos.cs index d97da42..2f5803c 100644 --- a/Tiku.Api/Contracts/TenantFrontendConfigDtos.cs +++ b/Tiku.Api/Contracts/TenantFrontendConfigDtos.cs @@ -5,12 +5,30 @@ using Tiku.Domain.Common; namespace Tiku.Api.Contracts; +/// +/// 保存租户前端配置草稿请求。 +/// public sealed class SaveTenantFrontendConfigDraftDto { + /// + /// 品牌配置。 + /// public JsonElement Branding { get; set; } = JsonDefaults.Object(); + /// + /// 主题配置。 + /// public JsonElement Theme { get; set; } = JsonDefaults.Object(); + /// + /// 功能配置。 + /// public JsonElement Features { get; set; } = JsonDefaults.Object(); + /// + /// 导航配置。 + /// public JsonElement Navigation { get; set; } = JsonDefaults.Array(); + /// + /// 首页模块配置。 + /// public JsonElement HomeModules { get; set; } = JsonDefaults.Array(); public TenantFrontendConfigDraft ToDraft() => new( @@ -21,8 +39,14 @@ public sealed class SaveTenantFrontendConfigDraftDto HomeModules); } +/// +/// 发布租户前端配置请求。 +/// public sealed class PublishTenantFrontendConfigDto { + /// + /// 期望配置版本。 + /// [Range(1, int.MaxValue)] public int ExpectedVersion { get; set; } } diff --git a/Tiku.Api/Contracts/VideoDtos.cs b/Tiku.Api/Contracts/VideoDtos.cs index efcdc8d..dbe57fe 100644 --- a/Tiku.Api/Contracts/VideoDtos.cs +++ b/Tiku.Api/Contracts/VideoDtos.cs @@ -4,47 +4,92 @@ using Tiku.Application.Assets; namespace Tiku.Api.Contracts; +/// +/// 视频Search查询参数。 +/// public sealed class VideoSearchQueryDto { + /// + /// 关键字。 + /// [StringLength(100)] public string? Keyword { get; set; } + /// + /// 科目 ID。 + /// public Guid? SubjectId { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 200)] public int? Limit { get; set; } public VideoSearchQuery ToQuery() => new(Keyword, SubjectId, Limit); } +/// +/// 视频Play请求 DTO。 +/// public sealed class VideoPlayDto { + /// + /// 视频 ID。 + /// [Required] public Guid VideoId { get; set; } + /// + /// 题目 ID。 + /// public Guid? QuestionId { get; set; } public VideoPlayCommand ToCommand() => new(VideoId, QuestionId); } +/// +/// 视频Progress请求 DTO。 +/// public sealed class VideoProgressDto { + /// + /// 视频 ID。 + /// [Required] public Guid VideoId { get; set; } + /// + /// 题目 ID。 + /// public Guid? QuestionId { get; set; } + /// + /// 播放位置,单位为秒。 + /// [Range(0, int.MaxValue)] public int PositionSeconds { get; set; } + /// + /// 时长,单位为秒。 + /// [Range(0, int.MaxValue)] public int? DurationSeconds { get; set; } + /// + /// 已观看时长,单位为秒。 + /// [Range(0, int.MaxValue)] public int? WatchedSeconds { get; set; } + /// + /// 是否已完成。 + /// public bool? IsCompleted { get; set; } + /// + /// 扩展元数据。 + /// public JsonElement Metadata { get; set; } = JsonSerializer.SerializeToElement(new { }); public VideoProgressCommand ToCommand() @@ -60,13 +105,25 @@ public sealed class VideoProgressDto } } +/// +/// 题目视频查询参数。 +/// public sealed class QuestionVideoQueryDto { + /// + /// 题目 ID。 + /// public Guid? QuestionId { get; set; } + /// + /// 题目 ID 列表。 + /// [MaxLength(100)] public IReadOnlyCollection? QuestionIds { get; set; } + /// + /// 返回数量上限。 + /// [Range(1, 500)] public int? Limit { get; set; } diff --git a/Tiku.Api/Controllers/CatalogController.cs b/Tiku.Api/Controllers/CatalogController.cs index 822c0ab..da4f379 100644 --- a/Tiku.Api/Controllers/CatalogController.cs +++ b/Tiku.Api/Controllers/CatalogController.cs @@ -146,6 +146,7 @@ public sealed class CatalogController( } [HttpGet("question-collections")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Practice)] [EndpointSummary("查询可用题集")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -159,6 +160,7 @@ public sealed class CatalogController( } [HttpGet("question-collections/questions")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Practice)] [EndpointSummary("查询题集内题目")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -173,6 +175,7 @@ public sealed class CatalogController( } [HttpGet("practice-blueprints")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Practice)] [EndpointSummary("查询练习蓝图")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -186,6 +189,7 @@ public sealed class CatalogController( } [HttpGet("question-banks")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Practice)] [EndpointSummary("查询题库列表")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -199,6 +203,7 @@ public sealed class CatalogController( } [HttpGet("questions")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Practice)] [EndpointSummary("查询已发布题目")] [EndpointDescription("支持按题库、科目、分类、模块节点、内容入口、内容节点、题集或题目 ID 列表筛选。")] [ProducesResponseType>(StatusCodes.Status200OK)] @@ -213,6 +218,7 @@ public sealed class CatalogController( } [HttpGet("questions/{questionId:guid}")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Practice)] [EndpointSummary("查询题目详情")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -227,6 +233,7 @@ public sealed class CatalogController( } [HttpGet("questions/{questionId:guid}/versions")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Practice)] [EndpointSummary("查询题目版本")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -241,6 +248,7 @@ public sealed class CatalogController( } [HttpGet("vocabulary-units")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Vocabulary)] [EndpointSummary("查询词汇单元")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -254,6 +262,7 @@ public sealed class CatalogController( } [HttpGet("vocabulary-words")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Vocabulary)] [EndpointSummary("查询词汇单词")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -267,6 +276,7 @@ public sealed class CatalogController( } [HttpGet("handbook-subjects")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Handbook)] [EndpointSummary("查询知识手册科目")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -280,6 +290,7 @@ public sealed class CatalogController( } [HttpGet("handbook-chapters")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Handbook)] [EndpointSummary("查询知识手册章节")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -293,6 +304,7 @@ public sealed class CatalogController( } [HttpGet("handbook-entries")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Handbook)] [EndpointSummary("查询知识手册条目")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -345,6 +357,7 @@ public sealed class CatalogController( } [HttpGet("video-explanations")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Video)] [EndpointSummary("查询视频讲解")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -358,6 +371,7 @@ public sealed class CatalogController( } [HttpGet("question-videos")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Video)] [EndpointSummary("查询题目关联视频")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -371,6 +385,7 @@ public sealed class CatalogController( } [HttpGet("banners")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.SiteContent)] [EndpointSummary("查询首页横幅")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -384,6 +399,7 @@ public sealed class CatalogController( } [HttpGet("faqs")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.SiteContent)] [EndpointSummary("查询常见问题")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -397,6 +413,7 @@ public sealed class CatalogController( } [HttpGet("announcements")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.SiteContent)] [EndpointSummary("查询公告")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -410,6 +427,7 @@ public sealed class CatalogController( } [HttpGet("exam-dates")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.SiteContent)] [EndpointSummary("查询考试日期")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -423,6 +441,7 @@ public sealed class CatalogController( } [HttpGet("products")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.StudentStore)] [EndpointSummary("查询可购买产品")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -436,6 +455,7 @@ public sealed class CatalogController( } [HttpGet("svip-plans")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.StudentStore)] [EndpointSummary("查询 SVIP 套餐")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] diff --git a/Tiku.Api/Controllers/CommerceController.cs b/Tiku.Api/Controllers/CommerceController.cs index e131332..1d2cc88 100644 --- a/Tiku.Api/Controllers/CommerceController.cs +++ b/Tiku.Api/Controllers/CommerceController.cs @@ -13,6 +13,7 @@ namespace Tiku.Api.Controllers; [ApiController] [Authorize(Policy = TikuPolicies.CurrentTenantMember)] +[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.StudentStore)] [Produces("application/json")] [Route("api/commerce")] public sealed class CommerceController( diff --git a/Tiku.Api/Controllers/CommissionController.cs b/Tiku.Api/Controllers/CommissionController.cs index f152be6..91075a5 100644 --- a/Tiku.Api/Controllers/CommissionController.cs +++ b/Tiku.Api/Controllers/CommissionController.cs @@ -8,6 +8,7 @@ namespace Tiku.Api.Controllers; [ApiController] [Authorize(Policy = BackendPermissions.TenantCommissionManage)] +[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.ReferralCommission)] [Produces("application/json")] [Route("api/commission")] public sealed class CommissionController( diff --git a/Tiku.Api/Controllers/CrmController.cs b/Tiku.Api/Controllers/CrmController.cs index d2197f1..9c2b678 100644 --- a/Tiku.Api/Controllers/CrmController.cs +++ b/Tiku.Api/Controllers/CrmController.cs @@ -8,6 +8,7 @@ namespace Tiku.Api.Controllers; [ApiController] [Authorize(Policy = BackendPermissions.TenantCrmManage)] +[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Crm)] [Produces("application/json")] [Route("api/crm")] public sealed class CrmController( diff --git a/Tiku.Api/Controllers/LearningController.cs b/Tiku.Api/Controllers/LearningController.cs index 45be92b..06835aa 100644 --- a/Tiku.Api/Controllers/LearningController.cs +++ b/Tiku.Api/Controllers/LearningController.cs @@ -8,6 +8,7 @@ namespace Tiku.Api.Controllers; [ApiController] [Authorize(Policy = TikuPolicies.CurrentTenantMember)] +[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Practice)] [Produces("application/json")] [Route("api/learning")] public sealed class LearningController( diff --git a/Tiku.Api/Controllers/PlatformAdminController.cs b/Tiku.Api/Controllers/PlatformAdminController.cs index 5329bea..8613580 100644 --- a/Tiku.Api/Controllers/PlatformAdminController.cs +++ b/Tiku.Api/Controllers/PlatformAdminController.cs @@ -37,8 +37,8 @@ public sealed class PlatformAdminController( [HttpPost("tenants")] [Authorize(Policy = BackendPermissions.PlatformTenantManage)] [EndpointSummary("创建平台租户")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> CreateTenant( + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> CreateTenant( CreatePlatformTenantDto request, CancellationToken cancellationToken) { @@ -78,55 +78,6 @@ public sealed class PlatformAdminController( return Ok(await platformAdminService.UpsertTenantBillingProfileAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } - [HttpGet("plans")] - [Authorize(Policy = BackendPermissions.PlatformTenantManage)] - [EndpointSummary("查询平台 SaaS 套餐")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> Plans( - [FromQuery] PlatformAdminQueryDto query, - CancellationToken cancellationToken) - { - return Ok(await platformAdminService.GetPlansAsync(ResolveActor(), query.ToQuery(), cancellationToken)); - } - - [HttpPut("plans/{planCode}/modules")] - [Authorize(Policy = BackendPermissions.PlatformTenantManage)] - [EndpointSummary("替换 SaaS 套餐模块权益")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> ReplacePlanModules( - string planCode, - ReplacePlatformPlanModulesDto request, - CancellationToken cancellationToken) - { - return Ok(await platformAdminService.ReplacePlanModulesAsync( - ResolveActor(), request.ToCommand(planCode), cancellationToken)); - } - - [HttpPost("subscriptions")] - [Authorize(Policy = BackendPermissions.PlatformTenantManage)] - [EndpointSummary("创建或调整租户订阅")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> UpsertSubscription( - UpsertPlatformSubscriptionDto request, - CancellationToken cancellationToken) - { - return Ok(await platformAdminService.UpsertSubscriptionAsync(ResolveActor(), request.ToCommand(), cancellationToken)); - } - - [HttpPut("tenants/{tenantId:guid}/module-overrides/{moduleCode}")] - [Authorize(Policy = BackendPermissions.PlatformTenantManage)] - [EndpointSummary("设置租户模块覆盖")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> UpsertTenantModuleOverride( - Guid tenantId, - string moduleCode, - UpsertPlatformTenantModuleOverrideDto request, - CancellationToken cancellationToken) - { - return Ok(await platformAdminService.UpsertTenantModuleOverrideAsync( - ResolveActor(), request.ToCommand(tenantId, moduleCode), cancellationToken)); - } - [HttpGet("domains")] [Authorize(Policy = BackendPermissions.PlatformTenantManage)] [EndpointSummary("查询租户域名状态")] @@ -215,70 +166,70 @@ public sealed class PlatformAdminController( return Ok(await platformAdminService.UpdateAuditAlertStatusAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } - [HttpGet("dunning-notification-channels")] + [HttpGet("saas/dunning/channels")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("查询平台催缴通知渠道")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> DunningChannels( + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> BillingDunningChannels( [FromQuery] PlatformAdminQueryDto query, CancellationToken cancellationToken) { - return Ok(await platformAdminService.GetDunningChannelsAsync(ResolveActor(), query.ToQuery(), cancellationToken)); + return Ok(await platformAdminService.GetBillingDunningChannelsAsync(ResolveActor(), query.ToQuery(), cancellationToken)); } - [HttpPut("dunning-notification-channels")] + [HttpPut("saas/dunning/channels")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("创建或更新平台催缴通知渠道")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> UpsertDunningChannel( - UpsertPlatformDunningChannelDto request, + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> UpsertBillingDunningChannel( + UpsertPlatformBillingDunningChannelDto request, CancellationToken cancellationToken) { - return Ok(await platformAdminService.UpsertDunningChannelAsync(ResolveActor(), request.ToCommand(), cancellationToken)); + return Ok(await platformAdminService.UpsertBillingDunningChannelAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } - [HttpPost("dunning-notification-channels/disable")] + [HttpPost("saas/dunning/channels/disable")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("禁用平台催缴通知渠道")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> DisableDunningChannel( - DisablePlatformDunningChannelDto request, + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> DisableBillingDunningChannel( + DisablePlatformBillingDunningChannelDto request, CancellationToken cancellationToken) { - return Ok(await platformAdminService.DisableDunningChannelAsync(ResolveActor(), request.ToCommand(), cancellationToken)); + return Ok(await platformAdminService.DisableBillingDunningChannelAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } - [HttpGet("dunning-notification-events")] + [HttpGet("saas/dunning/events")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("查询平台催缴通知事件")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> DunningEvents( + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> BillingDunningEvents( [FromQuery] PlatformAdminQueryDto query, CancellationToken cancellationToken) { - return Ok(await platformAdminService.GetDunningEventsAsync(ResolveActor(), query.ToQuery(), cancellationToken)); + return Ok(await platformAdminService.GetBillingDunningEventsAsync(ResolveActor(), query.ToQuery(), cancellationToken)); } - [HttpGet("dunning-notification-events/detail")] + [HttpGet("saas/dunning/events/detail")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("查询平台催缴通知事件详情")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> DunningEventDetail( + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> BillingDunningEventDetail( [FromQuery] Guid eventId, CancellationToken cancellationToken) { - return Ok(await platformAdminService.GetDunningEventDetailAsync(ResolveActor(), eventId, cancellationToken)); + return Ok(await platformAdminService.GetBillingDunningEventDetailAsync(ResolveActor(), eventId, cancellationToken)); } - [HttpPost("dunning-notification-events/retry")] + [HttpPost("saas/dunning/events/retry")] [Authorize(Policy = BackendPermissions.PlatformBillingNotification)] [EndpointSummary("重新标记平台催缴通知事件待发送")] - [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> RetryDunningEvent( - RetryPlatformDunningEventDto request, + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task> RetryBillingDunningEvent( + RetryPlatformBillingDunningEventDto request, CancellationToken cancellationToken) { - return Ok(await platformAdminService.RetryDunningEventAsync(ResolveActor(), request.ToCommand(), cancellationToken)); + return Ok(await platformAdminService.RetryBillingDunningEventAsync(ResolveActor(), request.ToCommand(), cancellationToken)); } private PlatformAdminActor ResolveActor() diff --git a/Tiku.Api/Controllers/PlatformBillingCallbackController.cs b/Tiku.Api/Controllers/PlatformBillingCallbackController.cs new file mode 100644 index 0000000..668fa62 --- /dev/null +++ b/Tiku.Api/Controllers/PlatformBillingCallbackController.cs @@ -0,0 +1,45 @@ +using System.Text; +using System.Text.Json; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Tiku.Application.PlatformBilling; + +namespace Tiku.Api.Controllers; + +[ApiController] +[Route("api/platform-billing/callbacks")] +public sealed class PlatformBillingCallbackController( + IPlatformBillingNotificationService notificationService) : ControllerBase +{ + [AllowAnonymous] + [HttpPost("{provider}")] + public async Task Notify(string provider, CancellationToken cancellationToken) + { + Request.EnableBuffering(); + using var reader = new StreamReader(Request.Body, Encoding.UTF8, leaveOpen: true); + var rawBody = await reader.ReadToEndAsync(cancellationToken); + Request.Body.Position = 0; + JsonElement body; + try + { + body = string.IsNullOrWhiteSpace(rawBody) + ? JsonDocument.Parse("{}").RootElement.Clone() + : JsonDocument.Parse(rawBody).RootElement.Clone(); + } + catch (JsonException) + { + body = JsonDocument.Parse("{}").RootElement.Clone(); + } + var normalizedProvider = provider.Trim().ToLowerInvariant().Replace('-', '_'); + await notificationService.ProcessAsync(new PlatformBillingNotification( + normalizedProvider, + Request.Headers.ToDictionary(value => value.Key, value => value.Value.ToString(), StringComparer.OrdinalIgnoreCase), + rawBody, + body), cancellationToken); + if (normalizedProvider is "alipay" or "ali_pay") + { + return Content("success", "text/plain", Encoding.UTF8); + } + return Ok(new { code = "SUCCESS", message = "成功" }); + } +} diff --git a/Tiku.Api/Controllers/PlatformSaasController.cs b/Tiku.Api/Controllers/PlatformSaasController.cs new file mode 100644 index 0000000..2d39449 --- /dev/null +++ b/Tiku.Api/Controllers/PlatformSaasController.cs @@ -0,0 +1,98 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Tiku.Api.Contracts; +using Tiku.Application.PlatformBilling; +using Tiku.Application.Security; +using Tiku.Domain.Platform; + +namespace Tiku.Api.Controllers; + +[ApiController] +[Route("api/platform-admin/saas")] +[Authorize(Policy = TikuPolicies.PlatformBackofficeBootstrap)] +public sealed class PlatformSaasController( + ISaasCatalogAdminService catalogService, + IPlatformBillingAdminService billingService, + ICurrentUser currentUser) : ControllerBase +{ + [HttpGet("catalog")] + [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] + public Task Catalog(CancellationToken cancellationToken) => + catalogService.GetCatalogAsync(Actor(), cancellationToken); + + [HttpPut("features")] + [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] + public Task UpsertFeature(UpsertSaasFeatureDto request, CancellationToken cancellationToken) => + catalogService.UpsertFeatureAsync(Actor(), request.ToCommand(), cancellationToken); + + [HttpPut("feature-limits")] + [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] + public Task UpsertFeatureLimit( + UpsertSaasFeatureLimitDto request, + CancellationToken cancellationToken) => + catalogService.UpsertLimitDefinitionAsync(Actor(), request.ToCommand(), cancellationToken); + + [HttpPut("offerings")] + [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] + public Task UpsertOffering(UpsertSaasOfferingDto request, CancellationToken cancellationToken) => + catalogService.UpsertOfferingAsync(Actor(), request.ToCommand(), cancellationToken); + + [HttpPut("offering-versions")] + [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] + public Task UpsertVersion(UpsertSaasOfferingVersionDto request, CancellationToken cancellationToken) => + catalogService.UpsertDraftVersionAsync(Actor(), request.ToCommand(), cancellationToken); + + [HttpPost("offering-versions/{versionId:guid}/publish")] + [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] + public Task PublishVersion(Guid versionId, CancellationToken cancellationToken) => + catalogService.PublishVersionAsync(Actor(), versionId, cancellationToken); + + [HttpPost("offering-versions/{versionId:guid}/clone")] + [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] + public Task CloneVersion(Guid versionId, CancellationToken cancellationToken) => + catalogService.CloneVersionAsync(Actor(), versionId, cancellationToken); + + [HttpPost("offering-versions/{versionId:guid}/retire")] + [Authorize(Policy = BackendPermissions.PlatformSaasCatalogManage)] + public Task RetireVersion(Guid versionId, CancellationToken cancellationToken) => + catalogService.RetireVersionAsync(Actor(), versionId, cancellationToken); + + [HttpGet("orders")] + [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] + public Task> Orders(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) => + billingService.GetOrdersAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken); + + [HttpGet("payments")] + [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] + public Task> Payments(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) => + billingService.GetPaymentsAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken); + + [HttpGet("refunds")] + [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] + public Task> Refunds(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) => + billingService.GetRefundsAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken); + + [HttpGet("invoices")] + [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] + public Task> Invoices(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) => + billingService.GetInvoicesAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken); + + [HttpGet("subscriptions")] + [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] + public Task> Subscriptions(Guid? tenantId, string? status, int limit = 100, CancellationToken cancellationToken = default) => + billingService.GetSubscriptionsAsync(Actor(), new PlatformBillingAdminQuery(tenantId, status, limit), cancellationToken); + + [HttpPost("payments/manual/confirm")] + [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] + public Task ConfirmManualPayment(ConfirmManualPlatformPaymentDto request, CancellationToken cancellationToken) => + billingService.ConfirmManualPaymentAsync(Actor(), request.ToCommand(), cancellationToken); + + [HttpPut("tenant-feature-overrides")] + [Authorize(Policy = BackendPermissions.PlatformSaasBillingManage)] + public Task UpsertFeatureOverride(UpsertTenantFeatureOverrideDto request, CancellationToken cancellationToken) => + billingService.UpsertFeatureOverrideAsync(Actor(), request.ToCommand(), cancellationToken); + + private SaasCatalogActor Actor() => currentUser.UserId is { } userId + ? new SaasCatalogActor(userId) + : throw new PlatformBillingException("Platform actor was not resolved.", "platform_access_denied"); +} diff --git a/Tiku.Api/Controllers/PointsController.cs b/Tiku.Api/Controllers/PointsController.cs index e4144b5..8154a28 100644 --- a/Tiku.Api/Controllers/PointsController.cs +++ b/Tiku.Api/Controllers/PointsController.cs @@ -8,6 +8,7 @@ namespace Tiku.Api.Controllers; [ApiController] [Authorize(Policy = TikuPolicies.CurrentTenantMember)] +[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.StudentStore)] [Produces("application/json")] [Route("api/points")] public sealed class PointsController( diff --git a/Tiku.Api/Controllers/ProfileController.cs b/Tiku.Api/Controllers/ProfileController.cs index d44c1d5..f58420e 100644 --- a/Tiku.Api/Controllers/ProfileController.cs +++ b/Tiku.Api/Controllers/ProfileController.cs @@ -108,6 +108,7 @@ public sealed class ProfileController( } [HttpPost("check-in")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.StudentStore)] [EndpointSummary("每日签到")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> CheckIn(CancellationToken cancellationToken) @@ -116,6 +117,7 @@ public sealed class ProfileController( } [HttpGet("score-events")] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.StudentStore)] [EndpointSummary("查询积分流水")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> ScoreEvents( diff --git a/Tiku.Api/Controllers/QuestionVideosController.cs b/Tiku.Api/Controllers/QuestionVideosController.cs index 72226c0..ff498e3 100644 --- a/Tiku.Api/Controllers/QuestionVideosController.cs +++ b/Tiku.Api/Controllers/QuestionVideosController.cs @@ -9,6 +9,7 @@ namespace Tiku.Api.Controllers; [ApiController] [Authorize(Policy = TikuPolicies.CurrentTenantMember)] +[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Video)] [Produces("application/json")] [Route("api/questions/videos")] public sealed class QuestionVideosController( diff --git a/Tiku.Api/Controllers/ReferralController.cs b/Tiku.Api/Controllers/ReferralController.cs index 8cdcc8b..bc6205e 100644 --- a/Tiku.Api/Controllers/ReferralController.cs +++ b/Tiku.Api/Controllers/ReferralController.cs @@ -11,6 +11,7 @@ using Tiku.Infrastructure.Persistence; namespace Tiku.Api.Controllers; [ApiController] +[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.ReferralCommission)] [Produces("application/json")] [Route("api/referral")] public sealed class ReferralController( diff --git a/Tiku.Api/Controllers/ScorelineController.cs b/Tiku.Api/Controllers/ScorelineController.cs index 8781d62..f0801b0 100644 --- a/Tiku.Api/Controllers/ScorelineController.cs +++ b/Tiku.Api/Controllers/ScorelineController.cs @@ -12,6 +12,7 @@ namespace Tiku.Api.Controllers; [ApiController] [AllowAnonymous] +[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Scoreline)] [Produces("application/json")] [Route("api/scoreline")] public sealed class ScorelineController( diff --git a/Tiku.Api/Controllers/TaxonomyController.cs b/Tiku.Api/Controllers/TaxonomyController.cs index c28e828..46044bc 100644 --- a/Tiku.Api/Controllers/TaxonomyController.cs +++ b/Tiku.Api/Controllers/TaxonomyController.cs @@ -15,6 +15,7 @@ public sealed class TaxonomyController( ITaxonomyService taxonomyService) : ControllerBase { [HttpGet] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Practice)] [EndpointSummary("查询租户分类节点")] public Task> List(CancellationToken cancellationToken) { @@ -22,6 +23,7 @@ public sealed class TaxonomyController( } [HttpPost] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.PrivateQuestionBank)] [Authorize(Policy = BackendPermissions.TenantContentManage)] [EndpointSummary("创建租户分类节点")] public Task Create( diff --git a/Tiku.Api/Controllers/TenantBillingController.cs b/Tiku.Api/Controllers/TenantBillingController.cs new file mode 100644 index 0000000..3f057a9 --- /dev/null +++ b/Tiku.Api/Controllers/TenantBillingController.cs @@ -0,0 +1,72 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Tiku.Api.Contracts; +using Tiku.Application.PlatformBilling; +using Tiku.Application.Security; +using Tiku.Domain.Platform; + +namespace Tiku.Api.Controllers; + +[ApiController] +[Route("api/tenant-billing")] +[Authorize(Policy = BackendPermissions.TenantBillingManage)] +public sealed class TenantBillingController( + ITenantBillingService billingService, + ICurrentAccessContext accessContext) : ControllerBase +{ + [HttpGet("catalog")] + public async Task Catalog(CancellationToken cancellationToken) => + await billingService.GetCatalogAsync(await ActorAsync(cancellationToken), cancellationToken); + + [HttpPost("quotes")] + public async Task Quote(CreatePlatformBillingQuoteDto request, CancellationToken cancellationToken) => + await billingService.CreateQuoteAsync(await ActorAsync(cancellationToken), request.ToCommand(), cancellationToken); + + [HttpPost("orders")] + public async Task CreateOrder(CreatePlatformBillingOrderDto request, CancellationToken cancellationToken) => + await billingService.CreateOrderAsync(await ActorAsync(cancellationToken), request.ToCommand(), cancellationToken); + + [HttpPost("orders/{orderNo}/payments")] + public async Task CreatePayment(string orderNo, CreatePlatformBillingPaymentDto request, CancellationToken cancellationToken) => + await billingService.CreatePaymentAsync(await ActorAsync(cancellationToken), request.ToCommand(orderNo), cancellationToken); + + [HttpGet("orders")] + public async Task> Orders(int limit = 100, CancellationToken cancellationToken = default) => + await billingService.GetOrdersAsync(await ActorAsync(cancellationToken), limit, cancellationToken); + + [HttpGet("orders/{orderNo}")] + public async Task Order(string orderNo, CancellationToken cancellationToken) => + await billingService.GetOrderAsync(await ActorAsync(cancellationToken), orderNo, cancellationToken); + + [HttpGet("subscription")] + public async Task Subscription(CancellationToken cancellationToken) => + await billingService.GetSubscriptionAsync(await ActorAsync(cancellationToken), cancellationToken); + + [HttpPost("subscription/change")] + public async Task Change(ChangeTenantSubscriptionDto request, CancellationToken cancellationToken) => + await billingService.ChangeSubscriptionAsync(await ActorAsync(cancellationToken), request.ToCommand(), request.IdempotencyKey, cancellationToken); + + [HttpPost("subscription/renew")] + public async Task Renew(IdempotentTenantBillingDto request, CancellationToken cancellationToken) => + await billingService.RenewSubscriptionAsync(await ActorAsync(cancellationToken), request.IdempotencyKey, cancellationToken); + + [HttpPost("subscription/cancel")] + public async Task Cancel(CancellationToken cancellationToken) => + await billingService.CancelSubscriptionAsync(await ActorAsync(cancellationToken), cancellationToken); + + [HttpGet("usage")] + public async Task> Usage(CancellationToken cancellationToken) => + await billingService.GetUsageAsync(await ActorAsync(cancellationToken), cancellationToken); + + [HttpGet("invoices")] + public async Task> Invoices(int limit = 100, CancellationToken cancellationToken = default) => + await billingService.GetInvoicesAsync(await ActorAsync(cancellationToken), limit, cancellationToken); + + private async Task ActorAsync(CancellationToken cancellationToken) + { + var access = await accessContext.GetAsync(cancellationToken); + return access.UserId is { } userId && access.TenantId is { } tenantId && access.IsCurrentTenantMember + ? new TenantBillingActor(userId, tenantId) + : throw new PlatformBillingException("Tenant billing actor was not resolved.", "tenant_access_denied"); + } +} diff --git a/Tiku.Api/Controllers/TenantCommerceController.cs b/Tiku.Api/Controllers/TenantCommerceController.cs index 369b87a..5c16cb6 100644 --- a/Tiku.Api/Controllers/TenantCommerceController.cs +++ b/Tiku.Api/Controllers/TenantCommerceController.cs @@ -10,6 +10,7 @@ namespace Tiku.Api.Controllers; [ApiController] [Authorize(Policy = BackendPermissions.TenantCommerceOperate)] +[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.StudentStore)] [Produces("application/json")] [Route("api/tenant-commerce")] public sealed class TenantCommerceController( diff --git a/Tiku.Api/Controllers/TenantContentController.cs b/Tiku.Api/Controllers/TenantContentController.cs index 31a1d75..4579efe 100644 --- a/Tiku.Api/Controllers/TenantContentController.cs +++ b/Tiku.Api/Controllers/TenantContentController.cs @@ -235,6 +235,20 @@ public sealed class TenantContentController( cancellationToken)); } + [HttpDelete("assets/{assetId:guid}")] + [EndpointSummary("归档内容资产")] + [ProducesResponseType>(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> ArchiveAsset( + Guid assetId, + CancellationToken cancellationToken) + { + return Ok(await assetManagementService.ArchiveAssetAsync( + ResolveActor(), + assetId, + cancellationToken)); + } + [HttpPost("assets/sign-download")] [EndpointSummary("签发管理侧资产下载地址")] [ProducesResponseType(StatusCodes.Status200OK)] diff --git a/Tiku.Api/Controllers/TenantContentDirectController.cs b/Tiku.Api/Controllers/TenantContentDirectController.cs index ce54142..470ca8a 100644 --- a/Tiku.Api/Controllers/TenantContentDirectController.cs +++ b/Tiku.Api/Controllers/TenantContentDirectController.cs @@ -12,7 +12,6 @@ using Tiku.Domain.Content; namespace Tiku.Api.Controllers; [ApiController] -[Authorize(Policy = BackendPermissions.TenantContentManage)] [Produces("application/json")] [Route("api/tenant-content")] public sealed class TenantContentDirectController( @@ -22,7 +21,9 @@ public sealed class TenantContentDirectController( ITenantContext currentTenant) : ControllerBase { [HttpPost("questions")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantContentManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.PrivateQuestionBank)] [EndpointSummary("创建题目及首个版本")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> CreateQuestion( @@ -33,7 +34,9 @@ public sealed class TenantContentDirectController( } [HttpPatch("questions")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantContentManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.PrivateQuestionBank)] [EndpointSummary("更新题目并可选择创建新版本")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> UpdateQuestion( @@ -44,6 +47,8 @@ public sealed class TenantContentDirectController( } [HttpGet("vocabulary-units")] + [Authorize(Policy = BackendPermissions.TenantVocabularyManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Vocabulary)] [EndpointSummary("查询管理侧词汇单元")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetVocabularyUnits( @@ -54,6 +59,8 @@ public sealed class TenantContentDirectController( } [HttpPut("vocabulary-units")] + [Authorize(Policy = BackendPermissions.TenantVocabularyManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Vocabulary)] [EndpointSummary("新增或更新词汇单元")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> UpsertVocabularyUnit( @@ -64,7 +71,9 @@ public sealed class TenantContentDirectController( } [HttpGet("vocabulary-words")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantVocabularyManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Vocabulary)] [EndpointSummary("查询管理侧词汇")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetVocabularyWords( @@ -75,7 +84,9 @@ public sealed class TenantContentDirectController( } [HttpPut("vocabulary-words")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantVocabularyManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Vocabulary)] [EndpointSummary("新增或更新词汇")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> UpsertVocabularyWord( @@ -86,6 +97,8 @@ public sealed class TenantContentDirectController( } [HttpGet("handbook-subjects")] + [Authorize(Policy = BackendPermissions.TenantHandbookManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Handbook)] [EndpointSummary("查询管理侧知识手册科目")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetHandbookSubjects( @@ -96,6 +109,8 @@ public sealed class TenantContentDirectController( } [HttpPut("handbook-subjects")] + [Authorize(Policy = BackendPermissions.TenantHandbookManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Handbook)] [EndpointSummary("新增或更新知识手册科目")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> UpsertHandbookSubject( @@ -106,7 +121,9 @@ public sealed class TenantContentDirectController( } [HttpGet("handbook-chapters")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantHandbookManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Handbook)] [EndpointSummary("查询管理侧知识手册章节")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetHandbookChapters( @@ -117,7 +134,9 @@ public sealed class TenantContentDirectController( } [HttpPut("handbook-chapters")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantHandbookManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Handbook)] [EndpointSummary("新增或更新知识手册章节")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> UpsertHandbookChapter( @@ -128,7 +147,9 @@ public sealed class TenantContentDirectController( } [HttpGet("handbook-entries")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantHandbookManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Handbook)] [EndpointSummary("查询管理侧知识手册条目")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetHandbookEntries( @@ -139,7 +160,9 @@ public sealed class TenantContentDirectController( } [HttpPut("handbook-entries")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantHandbookManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Handbook)] [EndpointSummary("新增或更新知识手册条目")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> UpsertHandbookEntry( @@ -150,6 +173,8 @@ public sealed class TenantContentDirectController( } [HttpGet("scoreline/schools")] + [Authorize(Policy = BackendPermissions.TenantScorelineManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Scoreline)] [EndpointSummary("查询管理侧分数线院校")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetSchools( @@ -160,6 +185,8 @@ public sealed class TenantContentDirectController( } [HttpPut("scoreline/schools")] + [Authorize(Policy = BackendPermissions.TenantScorelineManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Scoreline)] [EndpointSummary("新增或更新分数线院校")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> UpsertSchool( @@ -170,6 +197,8 @@ public sealed class TenantContentDirectController( } [HttpGet("scoreline/majors")] + [Authorize(Policy = BackendPermissions.TenantScorelineManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Scoreline)] [EndpointSummary("查询管理侧分数线专业")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetMajors( @@ -180,6 +209,8 @@ public sealed class TenantContentDirectController( } [HttpPut("scoreline/majors")] + [Authorize(Policy = BackendPermissions.TenantScorelineManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Scoreline)] [EndpointSummary("新增或更新分数线专业")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> UpsertMajor( @@ -190,6 +221,8 @@ public sealed class TenantContentDirectController( } [HttpGet("scoreline/fields")] + [Authorize(Policy = BackendPermissions.TenantScorelineManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Scoreline)] [EndpointSummary("查询管理侧分数线字段")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetScorelineFields( @@ -200,6 +233,8 @@ public sealed class TenantContentDirectController( } [HttpPut("scoreline/fields")] + [Authorize(Policy = BackendPermissions.TenantScorelineManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Scoreline)] [EndpointSummary("新增或更新动态分数线字段")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> UpsertScorelineField( @@ -210,6 +245,8 @@ public sealed class TenantContentDirectController( } [HttpGet("scoreline/records")] + [Authorize(Policy = BackendPermissions.TenantScorelineManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Scoreline)] [EndpointSummary("查询管理侧分数线记录")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetScorelineRecords( @@ -220,6 +257,8 @@ public sealed class TenantContentDirectController( } [HttpPut("scoreline/records")] + [Authorize(Policy = BackendPermissions.TenantScorelineManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Scoreline)] [EndpointSummary("新增或更新分数线记录")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> UpsertScorelineRecord( @@ -230,6 +269,8 @@ public sealed class TenantContentDirectController( } [HttpGet("scoreline/years")] + [Authorize(Policy = BackendPermissions.TenantScorelineManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Scoreline)] [EndpointSummary("查询分数线年份")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetScorelineYears( @@ -240,6 +281,8 @@ public sealed class TenantContentDirectController( } [HttpGet("scoreline/trend")] + [Authorize(Policy = BackendPermissions.TenantScorelineManage)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Scoreline)] [EndpointSummary("查询分数线趋势摘要")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetScorelineTrend( @@ -250,7 +293,9 @@ public sealed class TenantContentDirectController( } [HttpGet("videos")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantVideoManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Video)] [EndpointSummary("查询租户视频解析")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetVideos( @@ -261,7 +306,9 @@ public sealed class TenantContentDirectController( } [HttpPut("videos")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantVideoManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Video)] [EndpointSummary("新增或更新视频解析")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> UpsertVideo( @@ -272,7 +319,9 @@ public sealed class TenantContentDirectController( } [HttpPost("question-videos")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantVideoManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Video)] [EndpointSummary("绑定题目与解析视频")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> BindQuestionVideo( @@ -283,7 +332,9 @@ public sealed class TenantContentDirectController( } [HttpGet("operations/{kind}")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantSiteContentManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.SiteContent)] [EndpointSummary("查询运营内容")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetOperationContent( @@ -295,7 +346,9 @@ public sealed class TenantContentDirectController( } [HttpPut("operations/{kind}")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantSiteContentManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.SiteContent)] [EndpointSummary("新增或更新运营内容")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> UpsertOperationContent( @@ -307,7 +360,9 @@ public sealed class TenantContentDirectController( } [HttpPost("imports/preview/{importType}")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantJobManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeatureFromRoute("importType")] [EndpointSummary("预览内容导入数据")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> PreviewImport( @@ -319,7 +374,9 @@ public sealed class TenantContentDirectController( } [HttpPost("imports/{importType}")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantJobManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] + [Tiku.Api.Security.RequireSaasFeatureFromRoute("importType")] [EndpointSummary("执行或排队内容导入")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status202Accepted)] @@ -359,7 +416,8 @@ public sealed class TenantContentDirectController( } [HttpGet("imports/detail")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantJobManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] [EndpointSummary("查询内容导入任务详情")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> GetImportDetail( @@ -370,7 +428,8 @@ public sealed class TenantContentDirectController( } [HttpGet("imports/issues")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantJobManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] [EndpointSummary("查询内容导入问题明细")] [ProducesResponseType>(StatusCodes.Status200OK)] public async Task>> GetImportIssues( @@ -381,7 +440,8 @@ public sealed class TenantContentDirectController( } [HttpPost("imports/post-check")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantJobManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] [EndpointSummary("执行内容导入后完整性检查")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> RunImportPostCheck( @@ -392,7 +452,8 @@ public sealed class TenantContentDirectController( } [HttpGet("imports/post-check")] - [Authorize(Policy = TikuPolicies.TenantContentManageAllScope)] + [Authorize(Policy = BackendPermissions.TenantJobManage)] + [Authorize(Policy = TikuPolicies.TenantAllDataScope)] [EndpointSummary("查询内容导入后检查状态")] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> GetImportPostCheck( diff --git a/Tiku.Api/Controllers/TenantOnboardingController.cs b/Tiku.Api/Controllers/TenantOnboardingController.cs new file mode 100644 index 0000000..ba3fdc7 --- /dev/null +++ b/Tiku.Api/Controllers/TenantOnboardingController.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Tiku.Application.Security; +using Tiku.Application.Tenancy; + +namespace Tiku.Api.Controllers; + +[ApiController] +[Route("api/tenant-onboarding")] +[Authorize(Policy = BackendPermissions.TenantSettingsManage)] +public sealed class TenantOnboardingController( + ITenantOnboardingService onboardingService, + ICurrentAccessContext accessContext) : ControllerBase +{ + [HttpGet("status")] + public async Task Status(CancellationToken cancellationToken) + { + var access = await accessContext.GetAsync(cancellationToken); + if (access.TenantId is not { } tenantId || !access.IsCurrentTenantMember) + { + throw new TenantExternalProviderException("Tenant onboarding access is denied.", "tenant_access_denied"); + } + return await onboardingService.GetStatusAsync(tenantId, cancellationToken); + } +} diff --git a/Tiku.Api/Controllers/VideosController.cs b/Tiku.Api/Controllers/VideosController.cs index 85fe7cd..361aa8b 100644 --- a/Tiku.Api/Controllers/VideosController.cs +++ b/Tiku.Api/Controllers/VideosController.cs @@ -9,6 +9,7 @@ namespace Tiku.Api.Controllers; [ApiController] [Authorize(Policy = TikuPolicies.CurrentTenantMember)] +[Tiku.Api.Security.RequireSaasFeature(SaasFeatureCatalog.Video)] [Produces("application/json")] [Route("api/videos")] public sealed class VideosController( diff --git a/Tiku.Api/Middleware/ExceptionHandlingMiddleware.cs b/Tiku.Api/Middleware/ExceptionHandlingMiddleware.cs index 8d2c7fe..d9f72d4 100644 --- a/Tiku.Api/Middleware/ExceptionHandlingMiddleware.cs +++ b/Tiku.Api/Middleware/ExceptionHandlingMiddleware.cs @@ -8,6 +8,7 @@ using Tiku.Application.Content; using Tiku.Application.Growth; using Tiku.Application.Points; using Tiku.Application.PlatformAdmin; +using Tiku.Application.PlatformBilling; using Tiku.Application.QuestionBanks; using Tiku.Application.Storage; using Tiku.Infrastructure.Content; @@ -56,6 +57,18 @@ public sealed class ExceptionHandlingMiddleware( return; } + if (exception is FeatureAccessException featureAccessException) + { + await WriteProblemAsync( + context, + featureAccessException.Message, + featureAccessException.Code == "feature_quota_exhausted" + ? StatusCodes.Status409Conflict + : StatusCodes.Status403Forbidden, + featureAccessException.Code); + return; + } + if (exception is TenantContextConflictException) { await WriteProblemAsync( @@ -262,6 +275,16 @@ public sealed class ExceptionHandlingMiddleware( return; } + if (exception is PlatformBillingException platformBillingException) + { + await WriteProblemAsync( + context, + platformBillingException.Message, + PlatformBillingStatusCode(platformBillingException.Code), + platformBillingException.Code); + return; + } + if (exception is CommerceException commerceException) { await WriteProblemAsync( @@ -518,6 +541,22 @@ public sealed class ExceptionHandlingMiddleware( }; } + private static int PlatformBillingStatusCode(string code) + { + return code switch + { + "tenant_not_found" => StatusCodes.Status404NotFound, + "platform_billing_public_url_missing" or "payment_provider_not_configured" or + "payment_secret_not_configured" => StatusCodes.Status503ServiceUnavailable, + "saas_offering_version_immutable" or "saas_offering_version_status_invalid" or + "platform_billing_quote_expired" or "platform_billing_order_status_invalid" or + "platform_billing_payment_status_invalid" or "platform_billing_payment_amount_mismatch" => + StatusCodes.Status409Conflict, + _ when code.EndsWith("_not_found", StringComparison.Ordinal) => StatusCodes.Status404NotFound, + _ => StatusCodes.Status400BadRequest + }; + } + private static int PointStatusCode(string code) { return code switch diff --git a/Tiku.Api/Security/AccessAuthorizationRequirements.cs b/Tiku.Api/Security/AccessAuthorizationRequirements.cs index 4576bce..be707de 100644 --- a/Tiku.Api/Security/AccessAuthorizationRequirements.cs +++ b/Tiku.Api/Security/AccessAuthorizationRequirements.cs @@ -76,7 +76,7 @@ internal sealed class CurrentAccessAuthorizationHandler(ICurrentAccessContext ac internal sealed class TenantPermissionAuthorizationHandler( ICurrentAccessContext accessContext, - ICapabilityAccessEvaluator capabilityAccessEvaluator, + IFeatureAccessService featureAccessService, IHttpContextAccessor httpContextAccessor) : AuthorizationHandler { @@ -90,24 +90,18 @@ internal sealed class TenantPermissionAuthorizationHandler( } var access = await accessContext.GetAsync(); - var moduleCode = ResolveModuleCode(requirement.PermissionCode); var operation = IsSafeMethod(httpContextAccessor.HttpContext?.Request.Method) - ? CapabilityOperation.Read - : CapabilityOperation.Write; + ? FeatureAccessOperation.Read + : FeatureAccessOperation.Write; if (access.TenantId is { } tenantId && access.HasTenantPermission(requirement.PermissionCode) && - await capabilityAccessEvaluator.IsAllowedAsync(tenantId, moduleCode, operation)) + (await featureAccessService.FilterPermissionCodesAsync( + tenantId, [requirement.PermissionCode], operation)).Contains(requirement.PermissionCode)) { context.Succeed(requirement); } } - private static string ResolveModuleCode(string permissionCode) - { - var parts = permissionCode.Split(':', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - return parts.Length >= 2 ? parts[1].ToLowerInvariant() : permissionCode.ToLowerInvariant(); - } - private static bool IsSafeMethod(string? method) => method is not null && (HttpMethods.IsGet(method) || HttpMethods.IsHead(method) || HttpMethods.IsOptions(method)); @@ -197,7 +191,7 @@ public static class AccessAuthorizationServiceCollectionExtensions public static IServiceCollection AddTikuRbacAuthorization(this IServiceCollection services) { services.AddHttpContextAccessor(); - services.TryAddScoped(); + services.TryAddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -241,6 +235,13 @@ public static class AccessAuthorizationServiceCollectionExtensions new CurrentTenantMemberRequirement(), new TenantPermissionRequirement(BackendPermissions.TenantContentManage), new AllDataScopeRequirement())); + options.AddPolicy( + TikuPolicies.TenantAllDataScope, + policy => policy + .RequireAuthenticatedUser() + .AddRequirements( + new CurrentTenantMemberRequirement(), + new AllDataScopeRequirement())); options.AddPolicy( TikuPolicies.TenantCommerceOperateAllScope, policy => policy @@ -275,17 +276,21 @@ public static class AccessAuthorizationServiceCollectionExtensions } } -internal sealed class CompatibilityCapabilityAccessEvaluator : ICapabilityAccessEvaluator +internal sealed class CompatibilityFeatureAccessService : IFeatureAccessService { - public Task IsAllowedAsync( - Guid tenantId, - string moduleCode, - CapabilityOperation operation, - CancellationToken cancellationToken = default) => Task.FromResult(true); + public Task EvaluateAsync(Guid tenantId, string featureCode, FeatureAccessOperation operation, CancellationToken cancellationToken = default) => + Task.FromResult(new FeatureAccessDecision(true, null, featureCode, operation)); - public Task> GetEnabledModulesAsync( - Guid tenantId, - CapabilityOperation operation = CapabilityOperation.Read, - CancellationToken cancellationToken = default) => - Task.FromResult>(new HashSet(StringComparer.Ordinal)); + public Task> GetEnabledFeaturesAsync(Guid tenantId, FeatureAccessOperation operation = FeatureAccessOperation.Read, CancellationToken cancellationToken = default) => + Task.FromResult>(new HashSet(SaasFeatureCatalog.All, StringComparer.Ordinal)); + + public Task> FilterPermissionCodesAsync(Guid tenantId, IEnumerable permissionCodes, FeatureAccessOperation operation = FeatureAccessOperation.Read, CancellationToken cancellationToken = default) => + Task.FromResult>(permissionCodes.ToHashSet(StringComparer.Ordinal)); + + public Task> GetQuotaSummaryAsync(Guid tenantId, CancellationToken cancellationToken = default) => + Task.FromResult>([]); + + public Task TryConsumeQuotaAsync(Guid tenantId, string metricCode, long amount, CancellationToken cancellationToken = default) => Task.FromResult(true); + + public Task ReleaseQuotaAsync(Guid tenantId, string metricCode, long amount, CancellationToken cancellationToken = default) => Task.CompletedTask; } diff --git a/Tiku.Api/Security/EndpointAuthorizationMetadata.cs b/Tiku.Api/Security/EndpointAuthorizationMetadata.cs index 9342b31..29e832a 100644 --- a/Tiku.Api/Security/EndpointAuthorizationMetadata.cs +++ b/Tiku.Api/Security/EndpointAuthorizationMetadata.cs @@ -8,6 +8,7 @@ namespace Tiku.Api.Security; public sealed record EndpointAuthorizationMetadata( string Realm, string? Module, + IReadOnlyList RequiredFeatures, string? Permission, CapabilityOperation Operation, bool RequiresAllDataScope, @@ -43,7 +44,20 @@ internal sealed class EndpointAuthorizationMetadataConvention : IApplicationMode policies.Any(policy => policy.StartsWith("tenant", StringComparison.Ordinal)) ? "tenant" : "authenticated"; - var module = permission is null ? null : ResolveModule(permission); + var module = permission is null ? null : PermissionModuleCatalog.ResolvePermissionModuleCode(permission); + var requiredFeatures = controller.Attributes.OfType() + .Concat(action.Attributes.OfType()) + .Select(attribute => attribute.FeatureCode) + .Concat(module is not null && + PermissionModuleCatalog.RequiredFeatures.TryGetValue(module, out var moduleFeature) && + moduleFeature is not null + ? [moduleFeature] + : []) + .Distinct(StringComparer.Ordinal) + .Order(StringComparer.Ordinal) + .Concat(action.Attributes.OfType() + .Select(attribute => $"route:{attribute.RouteValueName}")) + .ToArray(); var httpMethods = action.Attributes.OfType() .SelectMany(attribute => attribute.HttpMethods) .Distinct(StringComparer.OrdinalIgnoreCase) @@ -55,9 +69,11 @@ internal sealed class EndpointAuthorizationMetadataConvention : IApplicationMode var metadata = new EndpointAuthorizationMetadata( realm, module, + requiredFeatures, permission, operation, policies.Contains(TikuPolicies.TenantContentManageAllScope, StringComparer.Ordinal) || + policies.Contains(TikuPolicies.TenantAllDataScope, StringComparer.Ordinal) || policies.Contains(TikuPolicies.TenantCommerceOperateAllScope, StringComparer.Ordinal), $"{string.Join(',', httpMethods.Order(StringComparer.Ordinal))}:{route}"); foreach (var selector in action.Selectors) @@ -68,12 +84,6 @@ internal sealed class EndpointAuthorizationMetadataConvention : IApplicationMode } } - private static string ResolveModule(string permission) - { - var parts = permission.Split(':', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - return parts.Length >= 2 ? parts[1].ToLowerInvariant() : permission.ToLowerInvariant(); - } - private static bool IsSafeMethod(string method) => HttpMethods.IsGet(method) || HttpMethods.IsHead(method) || HttpMethods.IsOptions(method); } diff --git a/Tiku.Api/Security/SaasFeatureEndpointMetadata.cs b/Tiku.Api/Security/SaasFeatureEndpointMetadata.cs new file mode 100644 index 0000000..703c8b4 --- /dev/null +++ b/Tiku.Api/Security/SaasFeatureEndpointMetadata.cs @@ -0,0 +1,113 @@ +using Microsoft.AspNetCore.Mvc; +using Tiku.Application.Security; + +namespace Tiku.Api.Security; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] +public sealed class RequireSaasFeatureAttribute(string featureCode) : Attribute +{ + public string FeatureCode { get; } = featureCode; +} + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] +public sealed class RequireSaasFeatureFromRouteAttribute(string routeValueName) : Attribute +{ + public string RouteValueName { get; } = routeValueName; +} + +public sealed class SaasFeatureAccessMiddleware(RequestDelegate next) +{ + public async Task InvokeAsync( + HttpContext context, + ITenantContext tenantContext, + IFeatureAccessService featureAccessService) + { + var requirements = context.GetEndpoint()?.Metadata.GetOrderedMetadata(); + var routeRequirements = context.GetEndpoint()?.Metadata.GetOrderedMetadata(); + if ((requirements is null || requirements.Count == 0) && + (routeRequirements is null || routeRequirements.Count == 0)) + { + await next(context); + return; + } + + if (!tenantContext.IsResolved || tenantContext.IsSystem || tenantContext.TenantId is not { } tenantId) + { + // Some platform-hosted public entry points resolve their tenant from a + // signed code inside the application service. They must keep their + // existing 404 semantics when no request tenant has been established. + await next(context); + return; + } + + var operation = HttpMethods.IsGet(context.Request.Method) || + HttpMethods.IsHead(context.Request.Method) || + HttpMethods.IsOptions(context.Request.Method) + ? FeatureAccessOperation.Read + : FeatureAccessOperation.Write; + foreach (var requirement in (requirements ?? []).DistinctBy(value => value.FeatureCode, StringComparer.Ordinal)) + { + var decision = await featureAccessService.EvaluateAsync( + tenantId, + requirement.FeatureCode, + operation, + context.RequestAborted); + if (!decision.Allowed) + { + await WriteForbiddenAsync( + context, + decision.DenialCode ?? "feature_not_available", + $"The SaaS feature '{requirement.FeatureCode}' is not available."); + return; + } + } + + + foreach (var requirement in routeRequirements ?? []) + { + var routeValue = context.Request.RouteValues[requirement.RouteValueName]?.ToString(); + var featureCode = ResolveRouteFeature(routeValue); + if (featureCode is null) + { + await WriteForbiddenAsync(context, "feature_route_value_invalid", "The requested content module is not available."); + return; + } + var decision = await featureAccessService.EvaluateAsync( + tenantId, + featureCode, + operation, + context.RequestAborted); + if (!decision.Allowed) + { + await WriteForbiddenAsync( + context, + decision.DenialCode ?? "feature_not_available", + $"The SaaS feature '{featureCode}' is not available."); + return; + } + } + + await next(context); + } + + private static Task WriteForbiddenAsync(HttpContext context, string code, string title) + { + context.Response.StatusCode = StatusCodes.Status403Forbidden; + return context.Response.WriteAsJsonAsync(new ProblemDetails + { + Status = StatusCodes.Status403Forbidden, + Title = title, + Instance = context.Request.Path, + Extensions = + { + ["code"] = code, + ["traceId"] = context.TraceIdentifier + } + }); + } + + internal static string? ResolveRouteFeature(string? routeValue) + { + return SaasFeatureCatalog.ResolveContentImportFeature(routeValue); + } +} diff --git a/Tiku.Api/wwwroot/platform-admin/spec-contract.js b/Tiku.Api/wwwroot/platform-admin/spec-contract.js index 319f650..1afee13 100644 --- a/Tiku.Api/wwwroot/platform-admin/spec-contract.js +++ b/Tiku.Api/wwwroot/platform-admin/spec-contract.js @@ -86,9 +86,9 @@ ['P-08', 'PlatformAdminBillingController_confirmPayment', 'POST', '/api/platform-admin/invoices/payments/manual-confirm'], ['P-08', 'PlatformAdminBillingController_processOverdue', 'POST', '/api/platform-admin/invoices/process-overdue'], ['P-08', 'PlatformAdminBillingController_reminders', 'GET', '/api/platform-admin/invoices/reminders'], - ['P-08', 'PlatformAdminDunningChannelsController_channels', 'GET', '/api/platform-admin/dunning-notification-channels'], - ['P-08', 'PlatformAdminDunningChannelsController_upsertChannel', 'PUT', '/api/platform-admin/dunning-notification-channels'], - ['P-08', 'PlatformAdminDunningEventsController_events', 'GET', '/api/platform-admin/dunning-notification-events'], + ['P-08', 'PlatformAdminDunningChannelsController_channels', 'GET', '/api/platform-admin/saas/dunning/channels'], + ['P-08', 'PlatformAdminDunningChannelsController_upsertChannel', 'PUT', '/api/platform-admin/saas/dunning/channels'], + ['P-08', 'PlatformAdminDunningEventsController_events', 'GET', '/api/platform-admin/saas/dunning/events'], ['P-09', 'PlatformAdminQuestionBanksController_banks', 'GET', '/api/platform-admin/question-banks'], ['P-09', 'PlatformAdminQuestionBanksController_grants', 'GET', '/api/platform-admin/question-bank-grants'], ['P-09', 'PlatformAdminQuestionBanksController_upsertGrant', 'PUT', '/api/platform-admin/question-bank-grants'], diff --git a/Tiku.Application/Assets/IAssetManagementService.cs b/Tiku.Application/Assets/IAssetManagementService.cs index 2140328..11cd068 100644 --- a/Tiku.Application/Assets/IAssetManagementService.cs +++ b/Tiku.Application/Assets/IAssetManagementService.cs @@ -25,6 +25,11 @@ public interface IAssetManagementService AssetUploadConfirmCommand command, CancellationToken cancellationToken = default); + Task> ArchiveAssetAsync( + AssetManagementActor actor, + Guid assetId, + CancellationToken cancellationToken = default); + Task SignDownloadAsync( AssetManagementActor actor, AssetAccessSignCommand command, diff --git a/Tiku.Application/Backoffice/BackofficeModels.cs b/Tiku.Application/Backoffice/BackofficeModels.cs index 08dd21e..f84827c 100644 --- a/Tiku.Application/Backoffice/BackofficeModels.cs +++ b/Tiku.Application/Backoffice/BackofficeModels.cs @@ -36,14 +36,16 @@ public sealed record BackofficeBootstrap( public sealed record BackofficeUiBootstrap( IReadOnlyCollection PermissionCodes, - IReadOnlyCollection Menus); + IReadOnlyCollection Menus, + IReadOnlyCollection EnabledFeatures, + IReadOnlyCollection Quotas); public sealed record BackofficePermissionItem( Guid Id, string Code, string Name, BackendPermissionArea Area, - string Module, + string PermissionModuleCode, string? Description, int SortOrder); diff --git a/Tiku.Application/PlatformAdmin/PlatformAdminModels.cs b/Tiku.Application/PlatformAdmin/PlatformAdminModels.cs index da69b3d..663a189 100644 --- a/Tiku.Application/PlatformAdmin/PlatformAdminModels.cs +++ b/Tiku.Application/PlatformAdmin/PlatformAdminModels.cs @@ -58,12 +58,12 @@ public sealed record PlatformTenantDomainItem( public sealed record PlatformTenantSubscriptionItem( Guid Id, Guid TenantId, - string PlanCode, - TenantSubscriptionStatus Status, - DateTimeOffset? StartsAt, - DateTimeOffset? ExpiresAt, - string? BillingCycle, - int AmountCents, + Guid BaseOfferingVersionId, + TenantSaasSubscriptionStatus Status, + DateTimeOffset StartsAt, + DateTimeOffset CurrentPeriodStart, + DateTimeOffset CurrentPeriodEnd, + bool CancelAtPeriodEnd, JsonElement Metadata); public sealed record TenantBillingProfileItem( @@ -75,7 +75,7 @@ public sealed record TenantBillingProfileItem( string? ContactEmail, string? BillingAddress, string? InvoiceTitle, - TenantInvoiceTitleType? InvoiceType, + TenantBillingInvoiceTitleType? InvoiceType, string? BankName, string? BankAccountMasked, JsonElement Metadata); @@ -86,7 +86,17 @@ public sealed record CreatePlatformTenantCommand( string? LegalName, TenantStatus Status, BillingStatus BillingStatus, - JsonElement Metadata); + JsonElement Metadata, + string? OwnerEmail, + string? OwnerPhone, + string OwnerName, + string TemporaryPassword); + +public sealed record PlatformTenantProvisioningResult( + PlatformTenantItem Tenant, + Guid OwnerUserId, + string OwnerIdentifier, + bool MustChangePassword); public sealed record UpdatePlatformTenantStatusCommand( Guid TenantId, @@ -103,45 +113,11 @@ public sealed record UpsertPlatformTenantBillingProfileCommand( string? ContactEmail, string? BillingAddress, string? InvoiceTitle, - TenantInvoiceTitleType? InvoiceType, + TenantBillingInvoiceTitleType? InvoiceType, string? BankName, string? BankAccountMasked, JsonElement Metadata); -public sealed record PlatformPlanList(IReadOnlyCollection Items); - -public sealed record PlatformPlanModuleEntitlements( - string PlanCode, - IReadOnlyCollection ModuleCodes); - -public sealed record ReplacePlatformPlanModulesCommand( - string PlanCode, - IReadOnlyCollection ModuleCodes); - -public sealed record PlatformTenantModuleOverrideItem( - Guid TenantId, - string ModuleCode, - TenantModuleOverrideMode Mode, - DateTimeOffset? ExpiresAt, - string? Reason); - -public sealed record UpsertPlatformTenantModuleOverrideCommand( - Guid TenantId, - string ModuleCode, - TenantModuleOverrideMode Mode, - DateTimeOffset? ExpiresAt, - string Reason); - -public sealed record UpsertPlatformSubscriptionCommand( - Guid TenantId, - string PlanCode, - TenantSubscriptionStatus Status, - DateTimeOffset? StartsAt, - DateTimeOffset? ExpiresAt, - string? BillingCycle, - int AmountCents, - JsonElement Metadata); - public sealed record PlatformDomainList(IReadOnlyCollection Items); public sealed record PlatformDomainRecheckResult(Guid DomainId, TenantDomainStatus Status, DateTimeOffset LastCheckedAt); @@ -189,15 +165,15 @@ public sealed record UpdatePlatformAuditAlertStatusCommand( PlatformAuditAlertStatus Status, string? ResolutionNote); -public sealed record PlatformDunningChannelList(IReadOnlyCollection Items); +public sealed record PlatformBillingDunningChannelList(IReadOnlyCollection Items); -public sealed record PlatformDunningChannelItem( +public sealed record PlatformBillingDunningChannelItem( Guid Id, string ChannelCode, string Name, string? Description, bool Enabled, - PlatformDunningProvider Provider, + PlatformBillingDunningProvider Provider, string WebhookUrlMasked, string? SecretRef, IReadOnlyCollection ReminderTypes, @@ -209,13 +185,13 @@ public sealed record PlatformDunningChannelItem( DateTimeOffset CreatedAt, DateTimeOffset UpdatedAt); -public sealed record UpsertPlatformDunningChannelCommand( +public sealed record UpsertPlatformBillingDunningChannelCommand( Guid? ChannelId, string ChannelCode, string Name, string? Description, bool Enabled, - PlatformDunningProvider Provider, + PlatformBillingDunningProvider Provider, string WebhookUrl, string? SecretRef, IReadOnlyCollection ReminderTypes, @@ -225,18 +201,18 @@ public sealed record UpsertPlatformDunningChannelCommand( int TimeoutSeconds, JsonElement Metadata); -public sealed record DisablePlatformDunningChannelCommand(Guid ChannelId, string? Reason); +public sealed record DisablePlatformBillingDunningChannelCommand(Guid ChannelId, string? Reason); -public sealed record PlatformDunningEventList(IReadOnlyCollection Items); +public sealed record PlatformBillingDunningEventList(IReadOnlyCollection Items); -public sealed record PlatformDunningEventItem( +public sealed record PlatformBillingDunningEventItem( Guid Id, Guid TenantId, Guid ChannelId, Guid ReminderId, Guid InvoiceId, - PlatformDunningProvider Provider, - PlatformDunningNotificationStatus Status, + PlatformBillingDunningProvider Provider, + PlatformBillingDunningNotificationStatus Status, int Attempts, DateTimeOffset ScheduledAt, DateTimeOffset? NextAttemptAt, @@ -250,20 +226,16 @@ public sealed record PlatformDunningEventItem( DateTimeOffset CreatedAt, DateTimeOffset UpdatedAt); -public sealed record RetryPlatformDunningEventCommand(Guid EventId, string? Reason); +public sealed record RetryPlatformBillingDunningEventCommand(Guid EventId, string? Reason); public interface IPlatformAdminService { Task GetOverviewAsync(PlatformAdminActor actor, CancellationToken cancellationToken = default); Task GetTenantsAsync(PlatformAdminActor actor, PlatformAdminQuery query, CancellationToken cancellationToken = default); Task GetTenantDetailAsync(PlatformAdminActor actor, Guid tenantId, CancellationToken cancellationToken = default); - Task CreateTenantAsync(PlatformAdminActor actor, CreatePlatformTenantCommand command, CancellationToken cancellationToken = default); + Task CreateTenantAsync(PlatformAdminActor actor, CreatePlatformTenantCommand command, CancellationToken cancellationToken = default); Task UpdateTenantStatusAsync(PlatformAdminActor actor, UpdatePlatformTenantStatusCommand command, CancellationToken cancellationToken = default); Task UpsertTenantBillingProfileAsync(PlatformAdminActor actor, UpsertPlatformTenantBillingProfileCommand command, CancellationToken cancellationToken = default); - Task GetPlansAsync(PlatformAdminActor actor, PlatformAdminQuery query, CancellationToken cancellationToken = default); - Task ReplacePlanModulesAsync(PlatformAdminActor actor, ReplacePlatformPlanModulesCommand command, CancellationToken cancellationToken = default); - Task UpsertSubscriptionAsync(PlatformAdminActor actor, UpsertPlatformSubscriptionCommand command, CancellationToken cancellationToken = default); - Task UpsertTenantModuleOverrideAsync(PlatformAdminActor actor, UpsertPlatformTenantModuleOverrideCommand command, CancellationToken cancellationToken = default); Task GetDomainsAsync(PlatformAdminActor actor, PlatformAdminQuery query, CancellationToken cancellationToken = default); Task RecheckDomainAsync(PlatformAdminActor actor, Guid domainId, CancellationToken cancellationToken = default); Task GetStaffAsync(PlatformAdminActor actor, PlatformAdminQuery query, CancellationToken cancellationToken = default); @@ -272,12 +244,12 @@ public interface IPlatformAdminService Task GetAuditLogsAsync(PlatformAdminActor actor, PlatformAdminQuery query, CancellationToken cancellationToken = default); Task GetAuditAlertsAsync(PlatformAdminActor actor, PlatformAdminQuery query, CancellationToken cancellationToken = default); Task UpdateAuditAlertStatusAsync(PlatformAdminActor actor, UpdatePlatformAuditAlertStatusCommand command, CancellationToken cancellationToken = default); - Task GetDunningChannelsAsync(PlatformAdminActor actor, PlatformAdminQuery query, CancellationToken cancellationToken = default); - Task UpsertDunningChannelAsync(PlatformAdminActor actor, UpsertPlatformDunningChannelCommand command, CancellationToken cancellationToken = default); - Task DisableDunningChannelAsync(PlatformAdminActor actor, DisablePlatformDunningChannelCommand command, CancellationToken cancellationToken = default); - Task GetDunningEventsAsync(PlatformAdminActor actor, PlatformAdminQuery query, CancellationToken cancellationToken = default); - Task GetDunningEventDetailAsync(PlatformAdminActor actor, Guid eventId, CancellationToken cancellationToken = default); - Task RetryDunningEventAsync(PlatformAdminActor actor, RetryPlatformDunningEventCommand command, CancellationToken cancellationToken = default); + Task GetBillingDunningChannelsAsync(PlatformAdminActor actor, PlatformAdminQuery query, CancellationToken cancellationToken = default); + Task UpsertBillingDunningChannelAsync(PlatformAdminActor actor, UpsertPlatformBillingDunningChannelCommand command, CancellationToken cancellationToken = default); + Task DisableBillingDunningChannelAsync(PlatformAdminActor actor, DisablePlatformBillingDunningChannelCommand command, CancellationToken cancellationToken = default); + Task GetBillingDunningEventsAsync(PlatformAdminActor actor, PlatformAdminQuery query, CancellationToken cancellationToken = default); + Task GetBillingDunningEventDetailAsync(PlatformAdminActor actor, Guid eventId, CancellationToken cancellationToken = default); + Task RetryBillingDunningEventAsync(PlatformAdminActor actor, RetryPlatformBillingDunningEventCommand command, CancellationToken cancellationToken = default); } public sealed class PlatformAdminException(string message, string code) : Exception(message) diff --git a/Tiku.Application/PlatformBilling/PlatformBillingContracts.cs b/Tiku.Application/PlatformBilling/PlatformBillingContracts.cs new file mode 100644 index 0000000..dcf8f59 --- /dev/null +++ b/Tiku.Application/PlatformBilling/PlatformBillingContracts.cs @@ -0,0 +1,242 @@ +using System.Text.Json; +using Tiku.Application.Commerce; +using Tiku.Application.Security; +using Tiku.Domain.Platform; + +namespace Tiku.Application.PlatformBilling; + +public sealed record SaasCatalogActor(Guid UserId); +public sealed record TenantBillingActor(Guid UserId, Guid TenantId); + +public sealed record UpsertSaasFeatureCommand( + Guid? Id, + string Code, + string Name, + string Category, + string? Description, + int ReferencePriceCents, + string Currency, + SaasFeatureStatus Status, + int SortOrder); + +public sealed record UpsertSaasOfferingCommand( + Guid? Id, + string Code, + string Name, + SaasOfferingType Type, + SaasOfferingStatus Status, + string? Description, + int SortOrder); + +public sealed record UpsertSaasFeatureLimitCommand( + Guid? Id, + string MetricCode, + string FeatureCode, + string Name, + string Unit, + SaasFeatureLimitKind Kind, + int WarningPercent, + bool IsHardLimit); + +public sealed record UpsertSaasOfferingVersionCommand( + Guid? Id, + Guid OfferingId, + PlatformBillingCycle BillingCycle, + int OriginalAmountCents, + int AmountCents, + string Currency, + DateTimeOffset? EffectiveAt, + IReadOnlyCollection FeatureCodes, + IReadOnlyDictionary Limits, + JsonElement Metadata); + +public sealed record SaasOfferingVersionItem( + Guid Id, + Guid OfferingId, + string OfferingCode, + string OfferingName, + SaasOfferingType OfferingType, + int Version, + SaasOfferingVersionStatus Status, + PlatformBillingCycle BillingCycle, + int OriginalAmountCents, + int AmountCents, + string Currency, + DateTimeOffset? EffectiveAt, + DateTimeOffset? PublishedAt, + IReadOnlyCollection FeatureCodes, + IReadOnlyDictionary Limits); + +public sealed record SaasCatalogSnapshot( + IReadOnlyCollection Features, + IReadOnlyCollection LimitDefinitions, + IReadOnlyCollection Offerings, + IReadOnlyCollection Versions); + +public interface ISaasCatalogAdminService +{ + Task GetCatalogAsync(SaasCatalogActor actor, CancellationToken cancellationToken = default); + Task UpsertFeatureAsync(SaasCatalogActor actor, UpsertSaasFeatureCommand command, CancellationToken cancellationToken = default); + Task UpsertLimitDefinitionAsync(SaasCatalogActor actor, UpsertSaasFeatureLimitCommand command, CancellationToken cancellationToken = default); + Task UpsertOfferingAsync(SaasCatalogActor actor, UpsertSaasOfferingCommand command, CancellationToken cancellationToken = default); + Task UpsertDraftVersionAsync(SaasCatalogActor actor, UpsertSaasOfferingVersionCommand command, CancellationToken cancellationToken = default); + Task PublishVersionAsync(SaasCatalogActor actor, Guid versionId, CancellationToken cancellationToken = default); + Task CloneVersionAsync(SaasCatalogActor actor, Guid versionId, CancellationToken cancellationToken = default); + Task RetireVersionAsync(SaasCatalogActor actor, Guid versionId, CancellationToken cancellationToken = default); +} + +public sealed record TenantBillingCatalog( + IReadOnlyCollection Features, + IReadOnlyCollection BasePlans, + IReadOnlyCollection AddOns); + +public sealed record CreatePlatformBillingQuoteCommand( + Guid BaseOfferingVersionId, + IReadOnlyCollection AddOnOfferingVersionIds, + PlatformBillingOrderPurpose Purpose, + string IdempotencyKey); + +public sealed record PlatformBillingQuoteItemView( + Guid OfferingVersionId, + string OfferingCode, + string OfferingName, + PlatformBillingItemType ItemType, + int UnitAmountCents, + int AmountCents); + +public sealed record PlatformBillingQuoteView( + Guid Id, + string QuoteNo, + PlatformBillingQuoteStatus Status, + PlatformBillingOrderPurpose Purpose, + int OriginalAmountCents, + int DiscountAmountCents, + int TotalAmountCents, + string Currency, + DateTimeOffset ExpiresAt, + IReadOnlyCollection Items, + IReadOnlyCollection FeatureCodes, + IReadOnlyDictionary Limits); + +public sealed record CreatePlatformBillingOrderCommand(Guid QuoteId, string IdempotencyKey); +public sealed record CreatePlatformBillingPaymentCommand( + string OrderNo, + string Provider, + string Method, + string IdempotencyKey, + string? OpenId, + string? ReturnUrl, + string? QuitUrl); + +public sealed record PlatformBillingOrderView( + Guid Id, + string OrderNo, + PlatformBillingOrderPurpose Purpose, + PlatformBillingOrderStatus Status, + int TotalAmountCents, + string Currency, + DateTimeOffset ExpiresAt, + DateTimeOffset? PaidAt, + IReadOnlyCollection Items); + +public sealed record PlatformBillingPaymentView( + Guid Id, + string PaymentNo, + string Provider, + string Method, + PlatformBillingPaymentStatus Status, + int AmountCents, + JsonElement ClientPayload); + +public sealed record TenantSubscriptionView( + Guid Id, + TenantSaasSubscriptionStatus Status, + DateTimeOffset StartsAt, + DateTimeOffset CurrentPeriodStart, + DateTimeOffset CurrentPeriodEnd, + bool CancelAtPeriodEnd, + Guid BaseOfferingVersionId, + Guid? ScheduledBaseOfferingVersionId, + IReadOnlyCollection FeatureCodes); + +public sealed record ChangeTenantSubscriptionCommand(Guid BaseOfferingVersionId, IReadOnlyCollection AddOnOfferingVersionIds); + +public interface ITenantBillingService +{ + Task GetCatalogAsync(TenantBillingActor actor, CancellationToken cancellationToken = default); + Task CreateQuoteAsync(TenantBillingActor actor, CreatePlatformBillingQuoteCommand command, CancellationToken cancellationToken = default); + Task CreateOrderAsync(TenantBillingActor actor, CreatePlatformBillingOrderCommand command, CancellationToken cancellationToken = default); + Task CreatePaymentAsync(TenantBillingActor actor, CreatePlatformBillingPaymentCommand command, CancellationToken cancellationToken = default); + Task> GetOrdersAsync(TenantBillingActor actor, int limit, CancellationToken cancellationToken = default); + Task GetOrderAsync(TenantBillingActor actor, string orderNo, CancellationToken cancellationToken = default); + Task GetSubscriptionAsync(TenantBillingActor actor, CancellationToken cancellationToken = default); + Task ChangeSubscriptionAsync(TenantBillingActor actor, ChangeTenantSubscriptionCommand command, string idempotencyKey, CancellationToken cancellationToken = default); + Task RenewSubscriptionAsync(TenantBillingActor actor, string idempotencyKey, CancellationToken cancellationToken = default); + Task CancelSubscriptionAsync(TenantBillingActor actor, CancellationToken cancellationToken = default); + Task> GetUsageAsync(TenantBillingActor actor, CancellationToken cancellationToken = default); + Task> GetInvoicesAsync(TenantBillingActor actor, int limit, CancellationToken cancellationToken = default); +} + +public sealed record PlatformBillingAdminQuery(Guid? TenantId, string? Status, int Limit = 100); +public sealed record ConfirmManualPaymentCommand(Guid PaymentId, string? ProviderTradeNo, DateTimeOffset? PaidAt, string Reason); +public sealed record UpsertTenantFeatureOverrideCommand(Guid TenantId, string FeatureCode, TenantFeatureOverrideMode Mode, DateTimeOffset? ExpiresAt, string Reason); + +public interface IPlatformBillingAdminService +{ + Task> GetOrdersAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default); + Task> GetPaymentsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default); + Task> GetRefundsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default); + Task> GetInvoicesAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default); + Task> GetSubscriptionsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default); + Task ConfirmManualPaymentAsync(SaasCatalogActor actor, ConfirmManualPaymentCommand command, CancellationToken cancellationToken = default); + Task UpsertFeatureOverrideAsync(SaasCatalogActor actor, UpsertTenantFeatureOverrideCommand command, CancellationToken cancellationToken = default); +} + +public interface IPlatformBillingPaymentGateway +{ + Task CreatePaymentAsync(string provider, CreatePaymentProviderRequest request, CancellationToken cancellationToken = default); + Task ParseNotificationAsync(string provider, PaymentNotificationRequest request, CancellationToken cancellationToken = default); +} + +public sealed record PlatformBillingNotification( + string Provider, + IReadOnlyDictionary Headers, + string RawBody, + JsonElement Body); + +public interface IPlatformBillingNotificationService +{ + Task ProcessAsync(PlatformBillingNotification notification, CancellationToken cancellationToken = default); +} + +public interface IPlatformBillingSettlementService +{ + Task MarkPaidAsync( + Guid paymentId, + string providerEventId, + string eventType, + string? providerTradeNo, + DateTimeOffset paidAt, + JsonElement payload, + Guid? actorUserId, + CancellationToken cancellationToken = default); +} + +public sealed class SaasSubscriptionLifecycleOptions +{ + public bool Enabled { get; set; } = true; + public int BatchSize { get; set; } = 100; + public int PastDueGraceDays { get; set; } = 7; +} + +public interface ISaasSubscriptionLifecycleService +{ + Task ProcessDueAsync( + DateTimeOffset? asOf = null, + CancellationToken cancellationToken = default); +} + +public sealed class PlatformBillingException(string message, string code) : Exception(message) +{ + public string Code { get; } = code; +} diff --git a/Tiku.Application/Security/BackendPermissions.cs b/Tiku.Application/Security/BackendPermissions.cs index b3cbd00..50959a1 100644 --- a/Tiku.Application/Security/BackendPermissions.cs +++ b/Tiku.Application/Security/BackendPermissions.cs @@ -6,13 +6,19 @@ public static class BackendPermissions public const string TenantStaffManage = "tenant:staff:manage"; public const string TenantRoleManage = "tenant:role:manage"; public const string TenantStudentManage = "tenant:student:manage"; - public const string TenantContentManage = "tenant:content:manage"; + public const string TenantContentManage = "tenant:question-bank:manage"; + public const string TenantVocabularyManage = "tenant:vocabulary:manage"; + public const string TenantHandbookManage = "tenant:handbook:manage"; + public const string TenantVideoManage = "tenant:video:manage"; + public const string TenantScorelineManage = "tenant:scoreline:manage"; + public const string TenantSiteContentManage = "tenant:site-content:manage"; public const string TenantSettingsManage = "tenant:settings:manage"; public const string TenantProviderManage = "tenant:provider:manage"; public const string TenantCommerceOperate = "tenant:commerce:operate"; public const string TenantCrmManage = "tenant:crm:manage"; public const string TenantCommissionManage = "tenant:commission:manage"; public const string TenantJobManage = "tenant:job:manage"; + public const string TenantBillingManage = "tenant:billing:manage"; public const string PlatformDashboardView = "platform:dashboard:view"; public const string PlatformTenantManage = "platform:tenant:manage"; @@ -21,6 +27,8 @@ public static class BackendPermissions public const string PlatformQuestionBankManage = "platform:question-bank:manage"; public const string PlatformAuditView = "platform:audit:view"; public const string PlatformBillingNotification = "platform:billing:notification"; + public const string PlatformSaasCatalogManage = "platform:saas-catalog:manage"; + public const string PlatformSaasBillingManage = "platform:saas-billing:manage"; public static readonly IReadOnlySet Tenant = new HashSet(StringComparer.Ordinal) { @@ -29,12 +37,18 @@ public static class BackendPermissions TenantRoleManage, TenantStudentManage, TenantContentManage, + TenantVocabularyManage, + TenantHandbookManage, + TenantVideoManage, + TenantScorelineManage, + TenantSiteContentManage, TenantSettingsManage, TenantProviderManage, TenantCommerceOperate, TenantCrmManage, TenantCommissionManage, - TenantJobManage + TenantJobManage, + TenantBillingManage }; public static readonly IReadOnlySet Platform = new HashSet(StringComparer.Ordinal) @@ -45,7 +59,9 @@ public static class BackendPermissions PlatformRoleManage, PlatformQuestionBankManage, PlatformAuditView, - PlatformBillingNotification + PlatformBillingNotification, + PlatformSaasCatalogManage, + PlatformSaasBillingManage }; public static void EnsureTenant(string permissionCode) diff --git a/Tiku.Application/Security/CapabilityOperation.cs b/Tiku.Application/Security/CapabilityOperation.cs new file mode 100644 index 0000000..5709b88 --- /dev/null +++ b/Tiku.Application/Security/CapabilityOperation.cs @@ -0,0 +1,7 @@ +namespace Tiku.Application.Security; + +public enum CapabilityOperation +{ + Read, + Write +} diff --git a/Tiku.Application/Security/FeatureQuotaConsumptionExtensions.cs b/Tiku.Application/Security/FeatureQuotaConsumptionExtensions.cs new file mode 100644 index 0000000..f68a7e2 --- /dev/null +++ b/Tiku.Application/Security/FeatureQuotaConsumptionExtensions.cs @@ -0,0 +1,26 @@ +namespace Tiku.Application.Security; + +public static class FeatureQuotaConsumptionExtensions +{ + public static async Task ConsumeQuotaIfConfiguredAsync( + this IFeatureAccessService featureAccessService, + Guid tenantId, + string metricCode, + long amount = 1, + CancellationToken cancellationToken = default) + { + var configured = (await featureAccessService.GetQuotaSummaryAsync(tenantId, cancellationToken)) + .Any(item => string.Equals(item.MetricCode, metricCode, StringComparison.Ordinal)); + if (!configured) + { + return false; + } + + if (!await featureAccessService.TryConsumeQuotaAsync(tenantId, metricCode, amount, cancellationToken)) + { + throw new FeatureAccessException("The tenant feature quota has been exhausted.", "feature_quota_exhausted"); + } + + return true; + } +} diff --git a/Tiku.Application/Security/ICapabilityAccessEvaluator.cs b/Tiku.Application/Security/ICapabilityAccessEvaluator.cs deleted file mode 100644 index 0dad35a..0000000 --- a/Tiku.Application/Security/ICapabilityAccessEvaluator.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Tiku.Application.Security; - -public enum CapabilityOperation -{ - Read, - Write -} - -public interface ICapabilityAccessEvaluator -{ - Task IsAllowedAsync( - Guid tenantId, - string moduleCode, - CapabilityOperation operation, - CancellationToken cancellationToken = default); - - Task> GetEnabledModulesAsync( - Guid tenantId, - CapabilityOperation operation = CapabilityOperation.Read, - CancellationToken cancellationToken = default); -} diff --git a/Tiku.Application/Security/IFeatureAccessService.cs b/Tiku.Application/Security/IFeatureAccessService.cs new file mode 100644 index 0000000..9629f84 --- /dev/null +++ b/Tiku.Application/Security/IFeatureAccessService.cs @@ -0,0 +1,64 @@ +namespace Tiku.Application.Security; + +public enum FeatureAccessOperation +{ + Read, + Write +} + +public sealed record FeatureAccessDecision( + bool Allowed, + string? DenialCode, + string FeatureCode, + FeatureAccessOperation Operation); + +public sealed record FeatureQuotaSnapshot( + string MetricCode, + long UsedValue, + long LimitValue, + int UsedPercent, + bool Warning, + bool Exceeded, + DateTimeOffset PeriodStart, + DateTimeOffset PeriodEnd); + +public interface IFeatureAccessService +{ + Task EvaluateAsync( + Guid tenantId, + string featureCode, + FeatureAccessOperation operation, + CancellationToken cancellationToken = default); + + Task> GetEnabledFeaturesAsync( + Guid tenantId, + FeatureAccessOperation operation = FeatureAccessOperation.Read, + CancellationToken cancellationToken = default); + + Task> FilterPermissionCodesAsync( + Guid tenantId, + IEnumerable permissionCodes, + FeatureAccessOperation operation = FeatureAccessOperation.Read, + CancellationToken cancellationToken = default); + + Task> GetQuotaSummaryAsync( + Guid tenantId, + CancellationToken cancellationToken = default); + + Task TryConsumeQuotaAsync( + Guid tenantId, + string metricCode, + long amount, + CancellationToken cancellationToken = default); + + Task ReleaseQuotaAsync( + Guid tenantId, + string metricCode, + long amount, + CancellationToken cancellationToken = default); +} + +public sealed class FeatureAccessException(string message, string code) : Exception(message) +{ + public string Code { get; } = code; +} diff --git a/Tiku.Application/Security/IFeatureUsageReconciliationService.cs b/Tiku.Application/Security/IFeatureUsageReconciliationService.cs new file mode 100644 index 0000000..81d8571 --- /dev/null +++ b/Tiku.Application/Security/IFeatureUsageReconciliationService.cs @@ -0,0 +1,31 @@ +namespace Tiku.Application.Security; + +public sealed record ReconcileFeatureUsageRequest( + Guid TenantId, + SystemScopeCallerType CallerType, + string Caller, + string Reason, + string CorrelationId); + +public sealed record ReconciledFeatureUsage( + string MetricCode, + long ActualValue, + long LimitValue, + bool Warning, + bool Exceeded); + +public interface IFeatureUsageReconciliationService +{ + Task> ReconcileTenantAsync( + ReconcileFeatureUsageRequest request, + CancellationToken cancellationToken = default); + + Task ProcessDueAsync(CancellationToken cancellationToken = default); +} + +public sealed class FeatureUsageReconciliationOptions +{ + public bool Enabled { get; set; } = true; + public int BatchSize { get; set; } = 100; + public int IntervalMinutes { get; set; } = 60; +} diff --git a/Tiku.Application/Security/ProductModuleCatalog.cs b/Tiku.Application/Security/ProductModuleCatalog.cs deleted file mode 100644 index 62319d6..0000000 --- a/Tiku.Application/Security/ProductModuleCatalog.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Tiku.Application.Security; - -public static class ProductModuleCatalog -{ - public static readonly IReadOnlyDictionary All = - new Dictionary(StringComparer.Ordinal) - { - ["dashboard"] = "Dashboard", - ["staff"] = "Staff", - ["role"] = "Roles", - ["student"] = "Students", - ["content"] = "Content", - ["settings"] = "Settings", - ["provider"] = "Providers", - ["commerce"] = "Commerce", - ["crm"] = "CRM", - ["commission"] = "Commission", - ["job"] = "Background Jobs" - }; - - public static bool Contains(string moduleCode) => - All.ContainsKey(moduleCode.Trim().ToLowerInvariant()); -} diff --git a/Tiku.Application/Security/SaasFeatureCatalog.cs b/Tiku.Application/Security/SaasFeatureCatalog.cs new file mode 100644 index 0000000..a7036c8 --- /dev/null +++ b/Tiku.Application/Security/SaasFeatureCatalog.cs @@ -0,0 +1,138 @@ +namespace Tiku.Application.Security; + +public static class SaasFeatureCatalog +{ + public const string CoreBackoffice = "core.backoffice"; + public const string PrivateQuestionBank = "question_bank.private"; + public const string Practice = "learning.practice"; + public const string Assignment = "learning.assignment"; + public const string Exam = "learning.exam"; + public const string Vocabulary = "content.vocabulary"; + public const string Handbook = "content.handbook"; + public const string Video = "content.video"; + public const string Scoreline = "content.scoreline"; + public const string SiteContent = "marketing.site_content"; + public const string StudentManagement = "student.management"; + public const string StudentStore = "commerce.student_store"; + public const string Crm = "crm.followup"; + public const string ReferralCommission = "growth.referral_commission"; + public const string TeacherAi = "ai.teacher_assistant"; + + public static readonly IReadOnlySet All = new HashSet(StringComparer.Ordinal) + { + CoreBackoffice, + PrivateQuestionBank, + Practice, + Assignment, + Exam, + Vocabulary, + Handbook, + Video, + Scoreline, + SiteContent, + StudentManagement, + StudentStore, + Crm, + ReferralCommission, + TeacherAi + }; + + public static string? ResolveContentImportFeature(string? importType) + { + var normalized = importType?.Trim().ToLowerInvariant().Replace('-', '_'); + return normalized switch + { + "question" or "questions" or "question_bank" or "question_banks" => PrivateQuestionBank, + "vocabulary" or "vocabulary_unit" or "vocabulary_units" or "vocabulary_word" or "vocabulary_words" => Vocabulary, + "handbook" or "handbook_subject" or "handbook_subjects" or "handbook_chapter" or "handbook_chapters" or + "handbook_entry" or "handbook_entries" => Handbook, + "scoreline" or "scorelines" => Scoreline, + "video" or "videos" => Video, + _ => null + }; + } +} + +public static class PermissionModuleCatalog +{ + public static readonly IReadOnlyDictionary RequiredFeatures = + new Dictionary(StringComparer.Ordinal) + { + ["tenant_dashboard"] = null, + ["tenant_staff"] = null, + ["tenant_settings"] = null, + ["tenant_provider"] = null, + ["tenant_job"] = null, + ["tenant_billing"] = null, + ["tenant_student"] = SaasFeatureCatalog.StudentManagement, + ["tenant_question_bank"] = SaasFeatureCatalog.PrivateQuestionBank, + ["tenant_vocabulary"] = SaasFeatureCatalog.Vocabulary, + ["tenant_handbook"] = SaasFeatureCatalog.Handbook, + ["tenant_video"] = SaasFeatureCatalog.Video, + ["tenant_scoreline"] = SaasFeatureCatalog.Scoreline, + ["tenant_site_content"] = SaasFeatureCatalog.SiteContent, + ["tenant_commerce"] = SaasFeatureCatalog.StudentStore, + ["tenant_crm"] = SaasFeatureCatalog.Crm, + ["tenant_commission"] = SaasFeatureCatalog.ReferralCommission, + ["platform_dashboard"] = null, + ["platform_tenant"] = null, + ["platform_staff"] = null, + ["platform_content"] = null, + ["platform_audit"] = null, + ["platform_billing"] = null, + ["commerce"] = SaasFeatureCatalog.StudentStore + }; + + public static string ResolvePermissionModuleCode(string permissionCode) => permissionCode switch + { + BackendPermissions.TenantDashboardView => "tenant_dashboard", + BackendPermissions.TenantStaffManage or BackendPermissions.TenantRoleManage => "tenant_staff", + BackendPermissions.TenantStudentManage => "tenant_student", + BackendPermissions.TenantContentManage => "tenant_question_bank", + BackendPermissions.TenantVocabularyManage => "tenant_vocabulary", + BackendPermissions.TenantHandbookManage => "tenant_handbook", + BackendPermissions.TenantVideoManage => "tenant_video", + BackendPermissions.TenantScorelineManage => "tenant_scoreline", + BackendPermissions.TenantSiteContentManage => "tenant_site_content", + BackendPermissions.TenantSettingsManage => "tenant_settings", + BackendPermissions.TenantProviderManage => "tenant_provider", + BackendPermissions.TenantCommerceOperate => "tenant_commerce", + BackendPermissions.TenantCrmManage => "tenant_crm", + BackendPermissions.TenantCommissionManage => "tenant_commission", + BackendPermissions.TenantJobManage => "tenant_job", + BackendPermissions.TenantBillingManage => "tenant_billing", + BackendPermissions.PlatformDashboardView => "platform_dashboard", + BackendPermissions.PlatformTenantManage => "platform_tenant", + BackendPermissions.PlatformStaffManage or BackendPermissions.PlatformRoleManage => "platform_staff", + BackendPermissions.PlatformQuestionBankManage => "platform_content", + BackendPermissions.PlatformAuditView => "platform_audit", + BackendPermissions.PlatformBillingNotification => "platform_billing", + BackendPermissions.PlatformSaasCatalogManage or BackendPermissions.PlatformSaasBillingManage => "platform_billing", + _ when permissionCode.StartsWith("commerce:", StringComparison.Ordinal) => "commerce", + _ => throw new ArgumentOutOfRangeException(nameof(permissionCode), permissionCode, "Permission module mapping is missing.") + }; +} + +public static class SaasQuotaMetricCatalog +{ + public const string StaffCount = "staff.count"; + public const string StudentCount = "student.count"; + public const string PrivateQuestionCount = "private_question.count"; + public const string StorageBytes = "storage.bytes"; + public const string ImportCount = "import.count"; + public const string ExportCount = "export.count"; + public const string SmsCount = "sms.count"; + public const string AiCallCount = "ai.call.count"; + + public static readonly IReadOnlySet All = new HashSet(StringComparer.Ordinal) + { + StaffCount, + StudentCount, + PrivateQuestionCount, + StorageBytes, + ImportCount, + ExportCount, + SmsCount, + AiCallCount + }; +} diff --git a/Tiku.Application/Security/TikuPolicies.cs b/Tiku.Application/Security/TikuPolicies.cs index c30d7fc..1e51f8c 100644 --- a/Tiku.Application/Security/TikuPolicies.cs +++ b/Tiku.Application/Security/TikuPolicies.cs @@ -7,6 +7,7 @@ public static class TikuPolicies public const string TenantBackofficeBootstrap = "tenant_backoffice_bootstrap"; public const string PlatformBackofficeBootstrap = "platform_backoffice_bootstrap"; public const string TenantContentManageAllScope = "tenant:content:manage:all_scope"; + public const string TenantAllDataScope = "tenant:data-scope:all"; public const string TenantCommerceOperateAllScope = "tenant:commerce:operate:all_scope"; public const string TenantAdmin = "tenant_admin"; diff --git a/Tiku.Application/Tenancy/TenantFrontendConfigModels.cs b/Tiku.Application/Tenancy/TenantFrontendConfigModels.cs index 82f8bda..c621fbf 100644 --- a/Tiku.Application/Tenancy/TenantFrontendConfigModels.cs +++ b/Tiku.Application/Tenancy/TenantFrontendConfigModels.cs @@ -25,7 +25,9 @@ public sealed record TenantRuntimeBootstrap( JsonElement Theme, JsonElement Features, JsonElement Navigation, - JsonElement HomeModules); + JsonElement HomeModules, + IReadOnlyCollection EnabledFeatures, + IReadOnlyCollection LoginMethods); public interface ITenantFrontendConfigService { diff --git a/Tiku.Application/Tenancy/TenantOnboardingModels.cs b/Tiku.Application/Tenancy/TenantOnboardingModels.cs new file mode 100644 index 0000000..a36f880 --- /dev/null +++ b/Tiku.Application/Tenancy/TenantOnboardingModels.cs @@ -0,0 +1,19 @@ +namespace Tiku.Application.Tenancy; + +public sealed record TenantOnboardingStep( + string Code, + bool Required, + bool Completed, + string? Detail); + +public sealed record TenantOnboardingStatus( + Guid TenantId, + bool ReadyForStudentTraffic, + int CompletedRequiredSteps, + int RequiredSteps, + IReadOnlyCollection Steps); + +public interface ITenantOnboardingService +{ + Task GetStatusAsync(Guid tenantId, CancellationToken cancellationToken = default); +} diff --git a/Tiku.Domain/Commerce/CommerceEntities.cs b/Tiku.Domain/Commerce/CommerceEntities.cs index eaff60b..2476d40 100644 --- a/Tiku.Domain/Commerce/CommerceEntities.cs +++ b/Tiku.Domain/Commerce/CommerceEntities.cs @@ -193,28 +193,6 @@ public sealed class CouponRedemption : AuditableTenantEntity public DateTimeOffset? UsedAt { get; set; } } -public sealed class TenantSubscription : AuditableTenantEntity -{ - public string PlanCode { get; set; } = string.Empty; - public TenantSubscriptionStatus Status { get; set; } = TenantSubscriptionStatus.Trial; - public DateTimeOffset? StartsAt { get; set; } - public DateTimeOffset? ExpiresAt { get; set; } - public string? BillingCycle { get; set; } = "yearly"; - public int AmountCents { get; set; } - public JsonElement Metadata { get; set; } = JsonDefaults.Object(); -} - -public sealed class TenantUsageRecord : Entity, ITenantOwned -{ - public Guid TenantId { get; set; } - public string MetricKey { get; set; } = string.Empty; - public decimal MetricValue { get; set; } - public DateOnly PeriodStart { get; set; } - public DateOnly PeriodEnd { get; set; } - public JsonElement Metadata { get; set; } = JsonDefaults.Object(); - public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; -} - public sealed class CommerceRefundRequest : AuditableTenantEntity { public Guid OrderId { get; set; } @@ -389,7 +367,6 @@ public enum EntitlementScopeType { Tenant, Region, Module, Subject, QuestionBank public enum EntitlementStatus { Active, Revoked, Expired } public enum DiscountType { Percent, Fixed } public enum CouponRedemptionStatus { Claimed, Used, Expired, Cancelled } -public enum TenantSubscriptionStatus { Trial, Active, PastDue, Cancelled, Expired } public enum CommerceRefundStatus { Requested, Approved, Processing, Succeeded, Failed, Rejected, Cancelled } public enum RefundEntitlementAction { None, RevokeOnSuccess } public enum ReconciliationBillType { Payment, Refund, Combined } diff --git a/Tiku.Domain/Operations/OperationsEntities.cs b/Tiku.Domain/Operations/OperationsEntities.cs index 152ff02..d53fc08 100644 --- a/Tiku.Domain/Operations/OperationsEntities.cs +++ b/Tiku.Domain/Operations/OperationsEntities.cs @@ -56,7 +56,7 @@ public sealed class BackendPermission : AuditableEntity public string Code { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public BackendPermissionArea Area { get; set; } = BackendPermissionArea.Tenant; - public string Module { get; set; } = string.Empty; + public string PermissionModuleCode { get; set; } = string.Empty; public string? Description { get; set; } public bool IsSystem { get; set; } = true; public int SortOrder { get; set; } = 100; diff --git a/Tiku.Domain/Platform/PlatformOperationsEntities.cs b/Tiku.Domain/Platform/PlatformOperationsEntities.cs index ef52aa1..5a274cb 100644 --- a/Tiku.Domain/Platform/PlatformOperationsEntities.cs +++ b/Tiku.Domain/Platform/PlatformOperationsEntities.cs @@ -3,45 +3,6 @@ using Tiku.Domain.Common; namespace Tiku.Domain.Platform; -public sealed class PlatformSaasPlan : AuditableEntity -{ - public string Code { get; set; } = string.Empty; - public string Name { get; set; } = string.Empty; - public string? Description { get; set; } - public PlatformBillingCycle BillingCycle { get; set; } = PlatformBillingCycle.Yearly; - public int BaseAmountCents { get; set; } - public string Currency { get; set; } = "CNY"; - public JsonElement IncludedQuotas { get; set; } = JsonDefaults.Object(); - public JsonElement OveragePrices { get; set; } = JsonDefaults.Object(); - public JsonElement FeatureFlags { get; set; } = JsonDefaults.Object(); - public PlatformSaasPlanStatus Status { get; set; } = PlatformSaasPlanStatus.Active; - public int SortOrder { get; set; } -} - -public sealed class ProductModule : AuditableEntity -{ - public string Code { get; set; } = string.Empty; - public string Name { get; set; } = string.Empty; - public string? Description { get; set; } - public ProductModuleStatus Status { get; set; } = ProductModuleStatus.Active; - public int SortOrder { get; set; } -} - -public sealed class PlanModuleEntitlement : Entity -{ - public string PlanCode { get; set; } = string.Empty; - public string ModuleCode { get; set; } = string.Empty; - public bool Enabled { get; set; } = true; -} - -public sealed class TenantModuleOverride : AuditableTenantEntity -{ - public string ModuleCode { get; set; } = string.Empty; - public TenantModuleOverrideMode Mode { get; set; } = TenantModuleOverrideMode.Disabled; - public DateTimeOffset? ExpiresAt { get; set; } - public string? Reason { get; set; } -} - public sealed class TenantBillingProfile : IHasTimestamps, ITenantOwned { public Guid TenantId { get; set; } @@ -52,7 +13,7 @@ public sealed class TenantBillingProfile : IHasTimestamps, ITenantOwned public string? ContactEmail { get; set; } public string? BillingAddress { get; set; } public string? InvoiceTitle { get; set; } - public TenantInvoiceTitleType? InvoiceType { get; set; } + public TenantBillingInvoiceTitleType? InvoiceType { get; set; } public string? BankName { get; set; } public string? BankAccountMasked { get; set; } public JsonElement Metadata { get; set; } = JsonDefaults.Object(); @@ -60,63 +21,13 @@ public sealed class TenantBillingProfile : IHasTimestamps, ITenantOwned public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow; } -public sealed class TenantInvoice : AuditableTenantEntity -{ - public Guid? CreatedBy { get; set; } - public string InvoiceNo { get; set; } = string.Empty; - public TenantInvoiceType InvoiceType { get; set; } = TenantInvoiceType.Subscription; - public TenantInvoiceStatus Status { get; set; } = TenantInvoiceStatus.Draft; - public string Currency { get; set; } = "CNY"; - public int SubtotalCents { get; set; } - public int DiscountCents { get; set; } - public int TaxCents { get; set; } - public int TotalCents { get; set; } - public int PaidCents { get; set; } - public int BalanceCents { get; set; } - public DateOnly? BillingPeriodStart { get; set; } - public DateOnly? BillingPeriodEnd { get; set; } - public DateOnly? DueDate { get; set; } - public DateTimeOffset? IssuedAt { get; set; } - public DateTimeOffset? PaidAt { get; set; } - public string? Note { get; set; } - public JsonElement Metadata { get; set; } = JsonDefaults.Object(); -} - -public sealed class TenantInvoiceItem : Entity, ITenantOwned -{ - public Guid TenantId { get; set; } - public Guid InvoiceId { get; set; } - public string ItemType { get; set; } = "subscription"; - public Guid? ItemRefId { get; set; } - public string Description { get; set; } = string.Empty; - public decimal Quantity { get; set; } = 1; - public int UnitAmountCents { get; set; } - public int AmountCents { get; set; } - public JsonElement Metadata { get; set; } = JsonDefaults.Object(); - public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; -} - -public sealed class TenantInvoicePayment : AuditableTenantEntity -{ - public Guid InvoiceId { get; set; } - public Guid? ReceivedBy { get; set; } - public string PaymentNo { get; set; } = string.Empty; - public string Provider { get; set; } = "manual"; - public string? Method { get; set; } - public TenantInvoicePaymentStatus Status { get; set; } = TenantInvoicePaymentStatus.Paid; - public int AmountCents { get; set; } - public DateTimeOffset? PaidAt { get; set; } - public string? ProviderTradeNo { get; set; } - public JsonElement RawPayload { get; set; } = JsonDefaults.Object(); -} - -public sealed class TenantInvoiceReminder : AuditableTenantEntity +public sealed class PlatformBillingInvoiceReminder : AuditableTenantEntity { public Guid InvoiceId { get; set; } public Guid? CreatedBy { get; set; } - public TenantInvoiceReminderType ReminderType { get; set; } = TenantInvoiceReminderType.Overdue; - public TenantInvoiceReminderChannel Channel { get; set; } = TenantInvoiceReminderChannel.Manual; - public TenantInvoiceReminderStatus Status { get; set; } = TenantInvoiceReminderStatus.Pending; + public PlatformBillingInvoiceReminderType ReminderType { get; set; } = PlatformBillingInvoiceReminderType.Overdue; + public PlatformBillingInvoiceReminderChannel Channel { get; set; } = PlatformBillingInvoiceReminderChannel.Manual; + public PlatformBillingInvoiceReminderStatus Status { get; set; } = PlatformBillingInvoiceReminderStatus.Pending; public DateOnly ReminderDate { get; set; } public int ReminderLevel { get; set; } = 1; public DateOnly? DueDate { get; set; } @@ -163,13 +74,13 @@ public sealed class PlatformAuditAlert : AuditableEntity public string? ResolutionNote { get; set; } } -public sealed class PlatformDunningNotificationChannel : AuditableEntity +public sealed class PlatformBillingDunningNotificationChannel : AuditableEntity { public string ChannelCode { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string? Description { get; set; } public bool Enabled { get; set; } = true; - public PlatformDunningProvider Provider { get; set; } = PlatformDunningProvider.Generic; + public PlatformBillingDunningProvider Provider { get; set; } = PlatformBillingDunningProvider.Generic; public string WebhookUrl { get; set; } = string.Empty; public string? SecretRef { get; set; } public string[] ReminderTypes { get; set; } = ["overdue", "final_notice"]; @@ -180,13 +91,13 @@ public sealed class PlatformDunningNotificationChannel : AuditableEntity public JsonElement Metadata { get; set; } = JsonDefaults.Object(); } -public sealed class PlatformDunningNotificationEvent : AuditableTenantEntity +public sealed class PlatformBillingDunningNotificationEvent : AuditableTenantEntity { public Guid ChannelId { get; set; } public Guid ReminderId { get; set; } public Guid InvoiceId { get; set; } - public PlatformDunningProvider Provider { get; set; } = PlatformDunningProvider.Generic; - public PlatformDunningNotificationStatus Status { get; set; } = PlatformDunningNotificationStatus.Pending; + public PlatformBillingDunningProvider Provider { get; set; } = PlatformBillingDunningProvider.Generic; + public PlatformBillingDunningNotificationStatus Status { get; set; } = PlatformBillingDunningNotificationStatus.Pending; public int Attempts { get; set; } public DateTimeOffset ScheduledAt { get; set; } = DateTimeOffset.UtcNow; public DateTimeOffset? NextAttemptAt { get; set; } @@ -200,17 +111,11 @@ public sealed class PlatformDunningNotificationEvent : AuditableTenantEntity } public enum PlatformBillingCycle { Monthly, Quarterly, Yearly, OneTime } -public enum PlatformSaasPlanStatus { Active, Archived } -public enum ProductModuleStatus { Active, Archived } -public enum TenantModuleOverrideMode { Enabled, Disabled } -public enum TenantInvoiceTitleType { None, NormalVat, SpecialVat } -public enum TenantInvoiceType { Subscription, ServiceFee, UsageOverage, ManualAdjustment } -public enum TenantInvoiceStatus { Draft, Issued, Paid, Void, Overdue } -public enum TenantInvoicePaymentStatus { Pending, Paid, Failed, Refunded } -public enum TenantInvoiceReminderType { DueSoon, Overdue, FinalNotice, Manual } -public enum TenantInvoiceReminderChannel { Manual, Internal, Sms, Email, Wechat, Crm } -public enum TenantInvoiceReminderStatus { Pending, Sent, Acknowledged, Dismissed, Failed } +public enum TenantBillingInvoiceTitleType { None, NormalVat, SpecialVat } +public enum PlatformBillingInvoiceReminderType { DueSoon, Overdue, FinalNotice, Manual } +public enum PlatformBillingInvoiceReminderChannel { Manual, Internal, Sms, Email, Wechat, Crm } +public enum PlatformBillingInvoiceReminderStatus { Pending, Sent, Acknowledged, Dismissed, Failed } public enum PlatformAlertSeverity { Low, Medium, High, Critical } public enum PlatformAuditAlertStatus { Open, Acknowledged, Resolved, Ignored } -public enum PlatformDunningProvider { Generic, Dingtalk, Feishu, Wecom } -public enum PlatformDunningNotificationStatus { Pending, Processing, Sent, Retrying, Failed, Discarded } +public enum PlatformBillingDunningProvider { Generic, Dingtalk, Feishu, Wecom } +public enum PlatformBillingDunningNotificationStatus { Pending, Processing, Sent, Retrying, Failed, Discarded } diff --git a/Tiku.Domain/Platform/SaasBillingEntities.cs b/Tiku.Domain/Platform/SaasBillingEntities.cs new file mode 100644 index 0000000..6a59ce7 --- /dev/null +++ b/Tiku.Domain/Platform/SaasBillingEntities.cs @@ -0,0 +1,244 @@ +using System.Text.Json; +using Tiku.Domain.Common; +using Tiku.Domain.Operations; + +namespace Tiku.Domain.Platform; + +public sealed class SaasFeature : AuditableEntity +{ + public string Code { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string Category { get; set; } = string.Empty; + public string? Description { get; set; } + public int ReferencePriceCents { get; set; } + public string Currency { get; set; } = "CNY"; + public SaasFeatureStatus Status { get; set; } = SaasFeatureStatus.Draft; + public bool IsCore { get; set; } + public int SortOrder { get; set; } = 100; +} + +public sealed class PermissionModule : AuditableEntity +{ + public string Code { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public BackendPermissionArea Area { get; set; } = BackendPermissionArea.Tenant; + public string? RequiredFeatureCode { get; set; } + public string? Description { get; set; } + public int SortOrder { get; set; } = 100; +} + +public sealed class SaasOffering : AuditableEntity +{ + public string Code { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public SaasOfferingType Type { get; set; } = SaasOfferingType.BasePlan; + public SaasOfferingStatus Status { get; set; } = SaasOfferingStatus.Draft; + public string? Description { get; set; } + public int SortOrder { get; set; } = 100; +} + +public sealed class SaasOfferingVersion : AuditableEntity +{ + public Guid OfferingId { get; set; } + public int Version { get; set; } = 1; + public SaasOfferingVersionStatus Status { get; set; } = SaasOfferingVersionStatus.Draft; + public PlatformBillingCycle BillingCycle { get; set; } = PlatformBillingCycle.Yearly; + public int OriginalAmountCents { get; set; } + public int AmountCents { get; set; } + public string Currency { get; set; } = "CNY"; + public DateTimeOffset? EffectiveAt { get; set; } + public DateTimeOffset? PublishedAt { get; set; } + public DateTimeOffset? RetiredAt { get; set; } + public JsonElement Metadata { get; set; } = JsonDefaults.Object(); +} + +public sealed class SaasOfferingVersionFeature : Entity +{ + public Guid OfferingVersionId { get; set; } + public string FeatureCode { get; set; } = string.Empty; +} + +public sealed class SaasFeatureLimitDefinition : AuditableEntity +{ + public string MetricCode { get; set; } = string.Empty; + public string FeatureCode { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string Unit { get; set; } = "count"; + public SaasFeatureLimitKind Kind { get; set; } = SaasFeatureLimitKind.Current; + public int WarningPercent { get; set; } = 80; + public bool IsHardLimit { get; set; } = true; +} + +public sealed class SaasOfferingVersionLimit : Entity +{ + public Guid OfferingVersionId { get; set; } + public string MetricCode { get; set; } = string.Empty; + public long LimitValue { get; set; } +} + +public sealed class TenantSaasSubscription : AuditableTenantEntity +{ + public Guid BaseOfferingVersionId { get; set; } + public Guid? ScheduledBaseOfferingVersionId { get; set; } + public TenantSaasSubscriptionStatus Status { get; set; } = TenantSaasSubscriptionStatus.Trial; + public DateTimeOffset StartsAt { get; set; } + public DateTimeOffset CurrentPeriodStart { get; set; } + public DateTimeOffset CurrentPeriodEnd { get; set; } + public bool CancelAtPeriodEnd { get; set; } + public DateTimeOffset? CancelledAt { get; set; } + public long LifecycleVersion { get; set; } + public JsonElement Metadata { get; set; } = JsonDefaults.Object(); +} + +public sealed class TenantSaasSubscriptionItem : AuditableTenantEntity +{ + public Guid SubscriptionId { get; set; } + public Guid OfferingVersionId { get; set; } + public Guid? SourceOrderItemId { get; set; } + public TenantSaasSubscriptionItemType ItemType { get; set; } = TenantSaasSubscriptionItemType.BasePlan; + public TenantSaasSubscriptionItemStatus Status { get; set; } = TenantSaasSubscriptionItemStatus.Active; + public DateTimeOffset StartsAt { get; set; } + public DateTimeOffset EndsAt { get; set; } +} + +public sealed class TenantFeatureOverride : AuditableTenantEntity +{ + public string FeatureCode { get; set; } = string.Empty; + public TenantFeatureOverrideMode Mode { get; set; } = TenantFeatureOverrideMode.Disabled; + public DateTimeOffset? ExpiresAt { get; set; } + public string Reason { get; set; } = string.Empty; +} + +public sealed class TenantFeatureUsage : AuditableTenantEntity +{ + public string MetricCode { get; set; } = string.Empty; + public DateTimeOffset PeriodStart { get; set; } + public DateTimeOffset PeriodEnd { get; set; } + public long UsedValue { get; set; } + public long LimitValueSnapshot { get; set; } + public bool WarningIssued { get; set; } + public long Version { get; set; } +} + +public sealed class PlatformBillingQuote : AuditableTenantEntity +{ + public string QuoteNo { get; set; } = string.Empty; + public string IdempotencyKey { get; set; } = string.Empty; + public PlatformBillingQuoteStatus Status { get; set; } = PlatformBillingQuoteStatus.Active; + public PlatformBillingOrderPurpose Purpose { get; set; } = PlatformBillingOrderPurpose.NewSubscription; + public int OriginalAmountCents { get; set; } + public int DiscountAmountCents { get; set; } + public int TotalAmountCents { get; set; } + public string Currency { get; set; } = "CNY"; + public DateTimeOffset ExpiresAt { get; set; } + public JsonElement FeatureSnapshot { get; set; } = JsonDefaults.Array(); + public JsonElement LimitSnapshot { get; set; } = JsonDefaults.Object(); +} + +public sealed class PlatformBillingQuoteItem : Entity, ITenantOwned +{ + public Guid TenantId { get; set; } + public Guid QuoteId { get; set; } + public Guid OfferingVersionId { get; set; } + public PlatformBillingItemType ItemType { get; set; } = PlatformBillingItemType.BasePlan; + public int Quantity { get; set; } = 1; + public int UnitAmountCents { get; set; } + public int AmountCents { get; set; } + public JsonElement Snapshot { get; set; } = JsonDefaults.Object(); +} + +public sealed class PlatformBillingOrder : AuditableTenantEntity +{ + public Guid QuoteId { get; set; } + public string OrderNo { get; set; } = string.Empty; + public string IdempotencyKey { get; set; } = string.Empty; + public PlatformBillingOrderPurpose Purpose { get; set; } = PlatformBillingOrderPurpose.NewSubscription; + public PlatformBillingOrderStatus Status { get; set; } = PlatformBillingOrderStatus.PendingPayment; + public int OriginalAmountCents { get; set; } + public int DiscountAmountCents { get; set; } + public int TotalAmountCents { get; set; } + public string Currency { get; set; } = "CNY"; + public DateTimeOffset ExpiresAt { get; set; } + public DateTimeOffset? PaidAt { get; set; } + public DateTimeOffset? CancelledAt { get; set; } + public JsonElement Snapshot { get; set; } = JsonDefaults.Object(); +} + +public sealed class PlatformBillingOrderItem : Entity, ITenantOwned +{ + public Guid TenantId { get; set; } + public Guid OrderId { get; set; } + public Guid OfferingVersionId { get; set; } + public PlatformBillingItemType ItemType { get; set; } = PlatformBillingItemType.BasePlan; + public int Quantity { get; set; } = 1; + public int UnitAmountCents { get; set; } + public int AmountCents { get; set; } + public JsonElement Snapshot { get; set; } = JsonDefaults.Object(); +} + +public sealed class PlatformBillingPayment : AuditableTenantEntity +{ + public Guid OrderId { get; set; } + public string PaymentNo { get; set; } = string.Empty; + public string IdempotencyKey { get; set; } = string.Empty; + public string Provider { get; set; } = string.Empty; + public string Method { get; set; } = string.Empty; + public PlatformBillingPaymentStatus Status { get; set; } = PlatformBillingPaymentStatus.Pending; + public int AmountCents { get; set; } + public string? ProviderTradeNo { get; set; } + public DateTimeOffset? PaidAt { get; set; } + public JsonElement ClientPayload { get; set; } = JsonDefaults.Object(); +} + +public sealed class PlatformBillingPaymentEvent : Entity, ITenantOwned +{ + public Guid TenantId { get; set; } + public Guid PaymentId { get; set; } + public string Provider { get; set; } = string.Empty; + public string ProviderEventId { get; set; } = string.Empty; + public string EventType { get; set; } = string.Empty; + public JsonElement Payload { get; set; } = JsonDefaults.Object(); + public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; +} + +public sealed class PlatformBillingRefund : AuditableTenantEntity +{ + public Guid OrderId { get; set; } + public Guid? PaymentId { get; set; } + public string RefundNo { get; set; } = string.Empty; + public PlatformBillingRefundStatus Status { get; set; } = PlatformBillingRefundStatus.Requested; + public int AmountCents { get; set; } + public string Reason { get; set; } = string.Empty; + public string? ProviderRefundNo { get; set; } + public DateTimeOffset? CompletedAt { get; set; } +} + +public sealed class PlatformBillingInvoice : AuditableTenantEntity +{ + public Guid? OrderId { get; set; } + public string InvoiceNo { get; set; } = string.Empty; + public PlatformBillingInvoiceStatus Status { get; set; } = PlatformBillingInvoiceStatus.Draft; + public int TotalAmountCents { get; set; } + public string Currency { get; set; } = "CNY"; + public DateOnly? DueDate { get; set; } + public DateTimeOffset? IssuedAt { get; set; } + public DateTimeOffset? PaidAt { get; set; } + public JsonElement BillingProfileSnapshot { get; set; } = JsonDefaults.Object(); +} + +public enum SaasFeatureStatus { Draft, Active, Archived } +public enum SaasOfferingType { BasePlan, AddOn } +public enum SaasOfferingStatus { Draft, Active, Archived } +public enum SaasOfferingVersionStatus { Draft, Published, Retired } +public enum SaasFeatureLimitKind { Current, Period } +public enum TenantSaasSubscriptionStatus { Trial, Active, PastDue, Cancelled, Expired, Suspended } +public enum TenantSaasSubscriptionItemType { BasePlan, AddOn } +public enum TenantSaasSubscriptionItemStatus { Pending, Active, Scheduled, Cancelled, Expired } +public enum TenantFeatureOverrideMode { Enabled, Disabled } +public enum PlatformBillingQuoteStatus { Active, Converted, Expired, Cancelled } +public enum PlatformBillingOrderPurpose { NewSubscription, Renewal, Upgrade, Downgrade, AddOn } +public enum PlatformBillingOrderStatus { PendingPayment, Paid, Cancelled, Expired, Refunded } +public enum PlatformBillingItemType { BasePlan, AddOn } +public enum PlatformBillingPaymentStatus { Pending, Succeeded, Failed, Refunded } +public enum PlatformBillingRefundStatus { Requested, Processing, Succeeded, Failed, Cancelled } +public enum PlatformBillingInvoiceStatus { Draft, Issued, Paid, Void, Overdue } diff --git a/Tiku.Domain/Tenancy/TenantConfigurationEntities.cs b/Tiku.Domain/Tenancy/TenantConfigurationEntities.cs index b529ed0..2870f19 100644 --- a/Tiku.Domain/Tenancy/TenantConfigurationEntities.cs +++ b/Tiku.Domain/Tenancy/TenantConfigurationEntities.cs @@ -63,6 +63,7 @@ public sealed class TenantAuthPolicy : IHasTimestamps, ITenantOwned { public Guid TenantId { get; set; } public bool AllowExternalStudentSelfRegistration { get; set; } + public string[] AllowedStudentLoginMethods { get; set; } = ["password"]; public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow; } diff --git a/Tiku.Infrastructure/Assets/AssetManagementService.cs b/Tiku.Infrastructure/Assets/AssetManagementService.cs index 7fc18f3..8d0ccd4 100644 --- a/Tiku.Infrastructure/Assets/AssetManagementService.cs +++ b/Tiku.Infrastructure/Assets/AssetManagementService.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore; using Tiku.Application.Assets; using Tiku.Application.Catalog; using Tiku.Application.Content; +using Tiku.Application.Security; using Tiku.Application.Storage; using Tiku.Application.Tenancy; using Tiku.Domain.Common; @@ -15,7 +16,8 @@ namespace Tiku.Infrastructure.Assets; public sealed class AssetManagementService( TikuDbContext dbContext, IObjectStorageService objectStorageService, - ITenantExternalProviderConfigService providerConfigService) : IAssetManagementService + ITenantExternalProviderConfigService providerConfigService, + IFeatureAccessService featureAccessService) : IAssetManagementService { private const int DefaultLimit = 100; private const int MaxLimit = 500; @@ -101,6 +103,7 @@ public sealed class AssetManagementService( CancellationToken cancellationToken = default) { var asset = await ResolveManagementAssetAsync(actor, command, cancellationToken); + var accountedBytesBefore = AccountedStorageBytes(asset); asset.RegionId = command.RegionId; asset.SubjectId = command.SubjectId; asset.CategoryId = command.CategoryId; @@ -131,7 +134,11 @@ public sealed class AssetManagementService( asset.Metadata = command.Metadata.ValueKind == JsonValueKind.Undefined ? asset.Metadata : command.Metadata; asset.UpdatedBy = actor.UserId; - await dbContext.SaveChangesAsync(cancellationToken); + var accountedBytesAfter = AccountedStorageBytes(asset); + await SaveWithStorageQuotaAdjustmentAsync( + actor.TenantId, + accountedBytesAfter - accountedBytesBefore, + cancellationToken); return new ContentManagementResult(ToItem(asset)); } @@ -168,8 +175,6 @@ public sealed class AssetManagementService( asset.UploadStatus = AssetUploadStatus.Pending; asset.VerifiedAt = null; asset.VerifiedBy = null; - asset.VerifiedSizeBytes = null; - asset.VerifiedChecksumSha256 = null; asset.VerificationDetails = JsonDefaults.Object(); asset.SecurityScanStatus = AssetSecurityScanStatus.Pending; asset.PreviewStatus = ResolveInitialPreviewStatus(asset.AssetType, mimeType); @@ -216,6 +221,7 @@ public sealed class AssetManagementService( throw new AssetManagementException("Asset does not have a writable object location.", "asset_location_missing"); } + var accountedBytesBefore = AccountedStorageBytes(asset); var provider = ToObjectStorageProvider(asset.StorageProvider); var declaredMimeType = string.IsNullOrWhiteSpace(command.MimeType) ? asset.MimeType : command.MimeType.Trim(); var declaredSize = command.FileSizeBytes ?? asset.FileSizeBytes; @@ -255,21 +261,68 @@ public sealed class AssetManagementService( await dbContext.SaveChangesAsync(cancellationToken); throw new AssetManagementException("Uploaded object was not found in object storage.", "asset_upload_missing"); } + if (metadata.SizeBytes is not { } verifiedSizeBytes) + { + asset.UploadStatus = AssetUploadStatus.Failed; + asset.UpdatedBy = actor.UserId; + await dbContext.SaveChangesAsync(cancellationToken); + throw new AssetManagementException( + "Object storage did not return a verified asset size.", + "asset_upload_size_unverified"); + } asset.UploadStatus = AssetUploadStatus.Verified; asset.VerifiedAt = DateTimeOffset.UtcNow; asset.VerifiedBy = actor.UserId; - asset.VerifiedSizeBytes = metadata.SizeBytes ?? declaredSize; + asset.VerifiedSizeBytes = verifiedSizeBytes; asset.VerifiedChecksumSha256 = NormalizeChecksum(metadata.ChecksumSha256) ?? declaredChecksum; asset.MimeType = metadata.MimeType ?? declaredMimeType; - asset.FileSizeBytes = metadata.SizeBytes ?? asset.FileSizeBytes; + asset.FileSizeBytes = verifiedSizeBytes; asset.SecurityScanStatus = AssetSecurityScanStatus.Pending; asset.UpdatedBy = actor.UserId; - await dbContext.SaveChangesAsync(cancellationToken); + var accountedBytesAfter = AccountedStorageBytes(asset); + await SaveWithStorageQuotaAdjustmentAsync( + actor.TenantId, + accountedBytesAfter - accountedBytesBefore, + cancellationToken); return new AssetUploadConfirmResult(ToItem(asset), metadata); } + public async Task> ArchiveAssetAsync( + AssetManagementActor actor, + Guid assetId, + CancellationToken cancellationToken = default) + { + var asset = await dbContext.ContentAssets.SingleOrDefaultAsync( + item => item.TenantId == actor.TenantId && item.Id == assetId, + cancellationToken); + if (asset is null) + { + throw new AssetManagementException("Asset was not found.", "asset_not_found"); + } + + if (asset.Status == ContentStatus.Archived) + { + return new ContentManagementResult(ToItem(asset)); + } + + var accountedBytes = AccountedStorageBytes(asset); + asset.Status = ContentStatus.Archived; + asset.UpdatedBy = actor.UserId; + await dbContext.SaveChangesAsync(cancellationToken); + if (accountedBytes > 0) + { + await featureAccessService.ReleaseQuotaAsync( + actor.TenantId, + SaasQuotaMetricCatalog.StorageBytes, + accountedBytes, + CancellationToken.None); + } + + return new ContentManagementResult(ToItem(asset)); + } + public Task SignDownloadAsync( AssetManagementActor actor, AssetAccessSignCommand command, @@ -639,6 +692,59 @@ public sealed class AssetManagementService( asset.UpdatedAt); } + private async Task SaveWithStorageQuotaAdjustmentAsync( + Guid tenantId, + long byteDelta, + CancellationToken cancellationToken) + { + if (byteDelta > 0) + { + var reserved = await featureAccessService.TryConsumeQuotaAsync( + tenantId, + SaasQuotaMetricCatalog.StorageBytes, + byteDelta, + cancellationToken); + if (!reserved) + { + throw new FeatureAccessException( + "Tenant storage quota is exhausted.", + "feature_quota_exhausted"); + } + + try + { + await dbContext.SaveChangesAsync(cancellationToken); + } + catch + { + await featureAccessService.ReleaseQuotaAsync( + tenantId, + SaasQuotaMetricCatalog.StorageBytes, + byteDelta, + CancellationToken.None); + throw; + } + return; + } + + await dbContext.SaveChangesAsync(cancellationToken); + if (byteDelta < 0) + { + await featureAccessService.ReleaseQuotaAsync( + tenantId, + SaasQuotaMetricCatalog.StorageBytes, + -byteDelta, + CancellationToken.None); + } + } + + private static long AccountedStorageBytes(ContentAsset asset) + { + return asset.Status == ContentStatus.Active && asset.VerifiedSizeBytes is > 0 + ? asset.VerifiedSizeBytes.Value + : 0; + } + private static ContentImportJobItem ToJobItem(ContentImportJob job) { return new ContentImportJobItem( diff --git a/Tiku.Infrastructure/Auth/AuthService.cs b/Tiku.Infrastructure/Auth/AuthService.cs index 30b6893..6ef8d16 100644 --- a/Tiku.Infrastructure/Auth/AuthService.cs +++ b/Tiku.Infrastructure/Auth/AuthService.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Identity; using Microsoft.IdentityModel.Tokens; using Tiku.Application.Auth; +using Tiku.Application.Security; using Tiku.Application.Tenancy; using Tiku.Domain.Identity; using Tiku.Domain.Tenancy; @@ -19,7 +20,8 @@ public sealed class AuthService( ISmsVerificationService smsVerificationService, IAuthSessionStore sessionStore, IWechatOAuthClient wechatOAuthClient, - ITenantExternalProviderConfigService providerConfigService) : IAuthService + ITenantExternalProviderConfigService providerConfigService, + IFeatureAccessService featureAccessService) : IAuthService { private const string PasswordProvider = "password"; private const string SmsProvider = "sms"; @@ -393,6 +395,9 @@ public sealed class AuthService( throw; } + await using var transaction = dbContext.Database.CurrentTransaction is null + ? await dbContext.Database.BeginTransactionAsync(cancellationToken) + : null; var providerSubject = $"{config.AppId}:{identity.OpenId}"; var user = await UpsertWechatUserAsync( provider, @@ -408,6 +413,10 @@ public sealed class AuthService( // tenant policy and existing membership state have accepted the login. // A denied first login must not leave a user or provider identity behind. await dbContext.SaveChangesAsync(cancellationToken); + if (transaction is not null) + { + await transaction.CommitAsync(cancellationToken); + } return await CompleteSuccessfulLoginAsync( request.Realm, @@ -568,6 +577,11 @@ public sealed class AuthService( throw new TenantAccessDeniedException(); } + await featureAccessService.ConsumeQuotaIfConfiguredAsync( + tenantId, + SaasQuotaMetricCatalog.StudentCount, + cancellationToken: cancellationToken); + dbContext.TenantMemberships.Add(new TenantMembership { TenantId = tenantId, diff --git a/Tiku.Infrastructure/Auth/SmsVerificationService.cs b/Tiku.Infrastructure/Auth/SmsVerificationService.cs index 8a197ef..2affe7d 100644 --- a/Tiku.Infrastructure/Auth/SmsVerificationService.cs +++ b/Tiku.Infrastructure/Auth/SmsVerificationService.cs @@ -17,13 +17,15 @@ public sealed class SmsVerificationService( TikuDbContext dbContext, ISmsProvider smsProvider, IRedisSecurityStore redisSecurityStore, + IFeatureAccessService featureAccessService, IOptions securityOptions) : ISmsVerificationService { public SmsVerificationService( TikuDbContext dbContext, ISmsProvider smsProvider, + IFeatureAccessService featureAccessService, IOptions securityOptions) - : this(dbContext, smsProvider, new NullRedisSecurityStore(), securityOptions) + : this(dbContext, smsProvider, new NullRedisSecurityStore(), featureAccessService, securityOptions) { } @@ -40,6 +42,17 @@ public sealed class SmsVerificationService( var phone = SmsCodeHashing.NormalizePhone(request.Phone); var now = DateTimeOffset.UtcNow; await ConsumeRateLimitsAsync(request, phone, now, cancellationToken); + var quotaReserved = await featureAccessService.TryConsumeQuotaAsync( + request.TenantId, + SaasQuotaMetricCatalog.SmsCount, + 1, + cancellationToken); + if (!quotaReserved) + { + throw new FeatureAccessException( + "Tenant SMS quota is exhausted.", + "feature_quota_exhausted"); + } var code = RandomNumberGenerator .GetInt32(100000, 1000000) @@ -52,6 +65,7 @@ public sealed class SmsVerificationService( options.CodePepper); SmsProviderSendResult sendResult; + var providerAccepted = false; try { sendResult = await smsProvider.SendAsync( @@ -63,6 +77,7 @@ public sealed class SmsVerificationService( request.IpAddress, request.UserAgent), cancellationToken); + providerAccepted = true; } catch (Exception exception) when (exception is not OperationCanceledException) { @@ -98,6 +113,17 @@ public sealed class SmsVerificationService( "sms_provider_send_failed", exception); } + finally + { + if (!providerAccepted) + { + await featureAccessService.ReleaseQuotaAsync( + request.TenantId, + SaasQuotaMetricCatalog.SmsCount, + 1, + CancellationToken.None); + } + } await ExpirePreviousCodesAsync( request.TenantId, diff --git a/Tiku.Infrastructure/Backoffice/BackofficeService.cs b/Tiku.Infrastructure/Backoffice/BackofficeService.cs index f699496..9cacc61 100644 --- a/Tiku.Infrastructure/Backoffice/BackofficeService.cs +++ b/Tiku.Infrastructure/Backoffice/BackofficeService.cs @@ -4,6 +4,7 @@ using Tiku.Application.Backoffice; using Tiku.Application.Security; using Tiku.Domain.Common; using Tiku.Domain.Operations; +using Tiku.Domain.Platform; using Tiku.Domain.Tenancy; using Tiku.Infrastructure.Persistence; @@ -12,27 +13,82 @@ namespace Tiku.Infrastructure.Backoffice; internal sealed class BackofficeService( TikuDbContext dbContext, IOperationAuditService auditService, - ICapabilityAccessEvaluator capabilityAccessEvaluator) : IBackofficeService + IFeatureAccessService featureAccessService) : IBackofficeService { + private static readonly BuiltinFeature[] BuiltinFeatures = + [ + new(SaasFeatureCatalog.CoreBackoffice, "后台基础能力", "core", true, 0), + new(SaasFeatureCatalog.PrivateQuestionBank, "私有题库", "question_bank", false, 10), + new(SaasFeatureCatalog.Practice, "题库练习", "learning", false, 20), + new(SaasFeatureCatalog.Assignment, "作业", "learning", false, 30), + new(SaasFeatureCatalog.Exam, "考试", "learning", false, 40), + new(SaasFeatureCatalog.Vocabulary, "词汇", "content", false, 50), + new(SaasFeatureCatalog.Handbook, "知识手册", "content", false, 60), + new(SaasFeatureCatalog.Video, "视频", "content", false, 70), + new(SaasFeatureCatalog.Scoreline, "分数线", "content", false, 80), + new(SaasFeatureCatalog.SiteContent, "站点运营内容", "marketing", false, 90), + new(SaasFeatureCatalog.StudentManagement, "学生管理", "student", false, 100), + new(SaasFeatureCatalog.StudentStore, "学生商城", "commerce", false, 110), + new(SaasFeatureCatalog.Crm, "学生跟进", "crm", false, 120), + new(SaasFeatureCatalog.ReferralCommission, "推广与分佣", "growth", false, 130), + new(SaasFeatureCatalog.TeacherAi, "教师 AI 助手", "ai", false, 140) + ]; + + private static readonly BuiltinPermissionModule[] BuiltinPermissionModules = + [ + new("tenant_dashboard", "租户总览", BackendPermissionArea.Tenant, null, 10), + new("tenant_staff", "员工与角色", BackendPermissionArea.Tenant, null, 20), + new("tenant_settings", "租户设置", BackendPermissionArea.Tenant, null, 30), + new("tenant_provider", "外部服务", BackendPermissionArea.Tenant, null, 40), + new("tenant_job", "后台任务", BackendPermissionArea.Tenant, null, 50), + new("tenant_billing", "SaaS 账务", BackendPermissionArea.Tenant, null, 60), + new("tenant_student", "学生管理", BackendPermissionArea.Tenant, SaasFeatureCatalog.StudentManagement, 100), + new("tenant_question_bank", "私有题库", BackendPermissionArea.Tenant, SaasFeatureCatalog.PrivateQuestionBank, 110), + new("tenant_vocabulary", "词汇", BackendPermissionArea.Tenant, SaasFeatureCatalog.Vocabulary, 120), + new("tenant_handbook", "知识手册", BackendPermissionArea.Tenant, SaasFeatureCatalog.Handbook, 130), + new("tenant_video", "视频", BackendPermissionArea.Tenant, SaasFeatureCatalog.Video, 140), + new("tenant_scoreline", "分数线", BackendPermissionArea.Tenant, SaasFeatureCatalog.Scoreline, 150), + new("tenant_site_content", "站点运营内容", BackendPermissionArea.Tenant, SaasFeatureCatalog.SiteContent, 160), + new("tenant_commerce", "学生商城", BackendPermissionArea.Tenant, SaasFeatureCatalog.StudentStore, 170), + new("tenant_crm", "学生跟进", BackendPermissionArea.Tenant, SaasFeatureCatalog.Crm, 180), + new("tenant_commission", "推广分佣", BackendPermissionArea.Tenant, SaasFeatureCatalog.ReferralCommission, 190), + new("platform_dashboard", "平台总览", BackendPermissionArea.Platform, null, 200), + new("platform_tenant", "平台租户", BackendPermissionArea.Platform, null, 210), + new("platform_staff", "平台员工", BackendPermissionArea.Platform, null, 220), + new("platform_content", "公共题库", BackendPermissionArea.Platform, null, 230), + new("platform_audit", "平台审计", BackendPermissionArea.Platform, null, 240), + new("platform_billing", "平台 SaaS 商城", BackendPermissionArea.Platform, null, 250), + new("commerce", "交易运营", BackendPermissionArea.Both, SaasFeatureCatalog.StudentStore, 300) + ]; + private static readonly BuiltinPermission[] BuiltinPermissions = [ new(BackendPermissions.TenantDashboardView, "租户总览", BackendPermissionArea.Tenant, "tenant_dashboard"), new(BackendPermissions.TenantStaffManage, "租户员工管理", BackendPermissionArea.Tenant, "tenant_staff"), new(BackendPermissions.TenantRoleManage, "租户角色权限管理", BackendPermissionArea.Tenant, "tenant_staff"), new(BackendPermissions.TenantStudentManage, "学生与班级管理", BackendPermissionArea.Tenant, "tenant_student"), - new(BackendPermissions.TenantContentManage, "租户内容管理", BackendPermissionArea.Tenant, "tenant_content"), + new(BackendPermissions.TenantContentManage, "租户题库管理", BackendPermissionArea.Tenant, "tenant_question_bank"), + new(BackendPermissions.TenantVocabularyManage, "租户词汇管理", BackendPermissionArea.Tenant, "tenant_vocabulary"), + new(BackendPermissions.TenantHandbookManage, "租户手册管理", BackendPermissionArea.Tenant, "tenant_handbook"), + new(BackendPermissions.TenantVideoManage, "租户视频管理", BackendPermissionArea.Tenant, "tenant_video"), + new(BackendPermissions.TenantScorelineManage, "租户分数线管理", BackendPermissionArea.Tenant, "tenant_scoreline"), + new(BackendPermissions.TenantSiteContentManage, "租户运营内容管理", BackendPermissionArea.Tenant, "tenant_site_content"), new(BackendPermissions.TenantSettingsManage, "租户设置管理", BackendPermissionArea.Tenant, "tenant_settings"), new(BackendPermissions.TenantProviderManage, "租户外部服务配置", BackendPermissionArea.Tenant, "tenant_provider"), new(BackendPermissions.TenantCommerceOperate, "租户交易运营", BackendPermissionArea.Tenant, "tenant_commerce"), new(BackendPermissions.TenantCrmManage, "租户客户管理", BackendPermissionArea.Tenant, "tenant_crm"), new(BackendPermissions.TenantCommissionManage, "租户佣金管理", BackendPermissionArea.Tenant, "tenant_commission"), new(BackendPermissions.TenantJobManage, "租户任务管理", BackendPermissionArea.Tenant, "tenant_job"), + new(BackendPermissions.TenantBillingManage, "租户 SaaS 账务", BackendPermissionArea.Tenant, "tenant_billing"), new(BackendPermissions.PlatformDashboardView, "平台总览", BackendPermissionArea.Platform, "platform_dashboard"), new(BackendPermissions.PlatformTenantManage, "平台租户管理", BackendPermissionArea.Platform, "platform_tenant"), new(BackendPermissions.PlatformStaffManage, "平台员工管理", BackendPermissionArea.Platform, "platform_staff"), new(BackendPermissions.PlatformRoleManage, "平台角色权限管理", BackendPermissionArea.Platform, "platform_staff"), new(BackendPermissions.PlatformQuestionBankManage, "平台公共题库运营", BackendPermissionArea.Platform, "platform_content"), new(BackendPermissions.PlatformAuditView, "平台审计查询", BackendPermissionArea.Platform, "platform_audit"), + new(BackendPermissions.PlatformBillingNotification, "平台催缴通知", BackendPermissionArea.Platform, "platform_billing"), + new(BackendPermissions.PlatformSaasCatalogManage, "SaaS 商品管理", BackendPermissionArea.Platform, "platform_billing"), + new(BackendPermissions.PlatformSaasBillingManage, "SaaS 交易管理", BackendPermissionArea.Platform, "platform_billing"), new("commerce:refund:approve", "退款审核", BackendPermissionArea.Both, "commerce"), new("commerce:reconciliation:manage", "对账管理", BackendPermissionArea.Both, "commerce"), new("commerce:adjustment:manage", "调账管理", BackendPermissionArea.Both, "commerce") @@ -43,14 +99,21 @@ internal sealed class BackofficeService( new("tenant.dashboard", null, "租户总览", BackendPermissionArea.Tenant, "/tenant/dashboard", "tenant:dashboard:view", 10), new("tenant.staff", null, "员工与权限", BackendPermissionArea.Tenant, "/tenant/staff", "tenant:staff:manage", 20), new("tenant.students", null, "班级与学生", BackendPermissionArea.Tenant, "/tenant/students", "tenant:student:manage", 30), - new("tenant.content", null, "内容管理", BackendPermissionArea.Tenant, "/tenant/content", "tenant:content:manage", 40), - new("tenant.providers", null, "外部服务", BackendPermissionArea.Tenant, "/tenant/providers", "tenant:provider:manage", 50), - new("tenant.commerce", null, "交易运营", BackendPermissionArea.Tenant, "/tenant/commerce", "tenant:commerce:operate", 60), + new("tenant.question-bank", null, "私有题库", BackendPermissionArea.Tenant, "/tenant/question-bank", BackendPermissions.TenantContentManage, 40), + new("tenant.vocabulary", null, "词汇", BackendPermissionArea.Tenant, "/tenant/vocabulary", BackendPermissions.TenantVocabularyManage, 50), + new("tenant.handbook", null, "知识手册", BackendPermissionArea.Tenant, "/tenant/handbook", BackendPermissions.TenantHandbookManage, 60), + new("tenant.video", null, "视频", BackendPermissionArea.Tenant, "/tenant/video", BackendPermissions.TenantVideoManage, 70), + new("tenant.scoreline", null, "分数线", BackendPermissionArea.Tenant, "/tenant/scoreline", BackendPermissions.TenantScorelineManage, 80), + new("tenant.site-content", null, "运营内容", BackendPermissionArea.Tenant, "/tenant/site-content", BackendPermissions.TenantSiteContentManage, 90), + new("tenant.providers", null, "外部服务", BackendPermissionArea.Tenant, "/tenant/providers", BackendPermissions.TenantProviderManage, 100), + new("tenant.commerce", null, "交易运营", BackendPermissionArea.Tenant, "/tenant/commerce", BackendPermissions.TenantCommerceOperate, 110), + new("tenant.billing", null, "SaaS 账务", BackendPermissionArea.Tenant, "/tenant/billing", BackendPermissions.TenantBillingManage, 120), new("platform.dashboard", null, "平台总览", BackendPermissionArea.Platform, "/platform/dashboard", "platform:dashboard:view", 10), new("platform.tenants", null, "租户管理", BackendPermissionArea.Platform, "/platform/tenants", "platform:tenant:manage", 20), new("platform.staff", null, "平台员工", BackendPermissionArea.Platform, "/platform/staff", "platform:staff:manage", 30), new("platform.content", null, "公共题库", BackendPermissionArea.Platform, "/platform/question-banks", "platform:question-bank:manage", 40), - new("platform.audit", null, "平台审计", BackendPermissionArea.Platform, "/platform/audit", "platform:audit:view", 50) + new("platform.audit", null, "平台审计", BackendPermissionArea.Platform, "/platform/audit", "platform:audit:view", 50), + new("platform.saas", null, "SaaS 商城", BackendPermissionArea.Platform, "/platform/saas", "platform:saas-catalog:manage", 60) ]; public async Task GetTenantUiBootstrapAsync( @@ -73,7 +136,10 @@ internal sealed class BackofficeService( BackendPermissionArea.Tenant, permissionCodes, cancellationToken); - return new BackofficeUiBootstrap(permissionCodes, menus); + var features = await featureAccessService.GetEnabledFeaturesAsync( + access.TenantId.Value, FeatureAccessOperation.Read, cancellationToken); + var quotas = await featureAccessService.GetQuotaSummaryAsync(access.TenantId.Value, cancellationToken); + return new BackofficeUiBootstrap(permissionCodes, menus, features.Order(StringComparer.Ordinal).ToArray(), quotas); } public async Task GetPlatformUiBootstrapAsync( @@ -91,7 +157,7 @@ internal sealed class BackofficeService( BackendPermissionArea.Platform, permissionCodes, cancellationToken); - return new BackofficeUiBootstrap(permissionCodes, menus); + return new BackofficeUiBootstrap(permissionCodes, menus, [], []); } public async Task GetTenantBootstrapAsync( @@ -103,7 +169,7 @@ internal sealed class BackofficeService( var roles = await LoadTenantRolesAsync(tenantId, cancellationToken); var permissions = await dbContext.BackendPermissions.AsNoTracking() .Where(item => item.Area == BackendPermissionArea.Tenant || item.Area == BackendPermissionArea.Both) - .OrderBy(item => item.Module).ThenBy(item => item.SortOrder).ThenBy(item => item.Code) + .OrderBy(item => item.PermissionModuleCode).ThenBy(item => item.SortOrder).ThenBy(item => item.Code) .ToArrayAsync(cancellationToken); var enabledPermissionCodes = await FilterTenantPermissionCodesAsync( tenantId, @@ -132,7 +198,7 @@ internal sealed class BackofficeService( var roles = await LoadPlatformRolesAsync(cancellationToken); var permissions = await dbContext.BackendPermissions.AsNoTracking() .Where(item => item.Area == BackendPermissionArea.Platform || item.Area == BackendPermissionArea.Both) - .OrderBy(item => item.Module).ThenBy(item => item.SortOrder).ThenBy(item => item.Code) + .OrderBy(item => item.PermissionModuleCode).ThenBy(item => item.SortOrder).ThenBy(item => item.Code) .ToArrayAsync(cancellationToken); var menus = await dbContext.BackendMenus.AsNoTracking() .Where(item => item.IsActive && item.Area == BackendPermissionArea.Platform) @@ -313,6 +379,39 @@ internal sealed class BackofficeService( private async Task EnsureCatalogAsync(CancellationToken cancellationToken) { + foreach (var feature in BuiltinFeatures) + { + if (!await dbContext.SaasFeatures.AnyAsync(item => item.Code == feature.Code, cancellationToken)) + { + dbContext.SaasFeatures.Add(new SaasFeature + { + Code = feature.Code, + Name = feature.Name, + Category = feature.Category, + IsCore = feature.IsCore, + Status = SaasFeatureStatus.Active, + SortOrder = feature.SortOrder + }); + } + } + + foreach (var module in BuiltinPermissionModules) + { + if (!await dbContext.PermissionModules.AnyAsync(item => item.Code == module.Code, cancellationToken)) + { + dbContext.PermissionModules.Add(new PermissionModule + { + Code = module.Code, + Name = module.Name, + Area = module.Area, + RequiredFeatureCode = module.RequiredFeatureCode, + SortOrder = module.SortOrder + }); + } + } + + await dbContext.SaveChangesAsync(cancellationToken); + foreach (var permission in BuiltinPermissions) { if (await dbContext.BackendPermissions.AnyAsync(item => item.Code == permission.Code, cancellationToken)) @@ -325,7 +424,7 @@ internal sealed class BackofficeService( Code = permission.Code, Name = permission.Name, Area = permission.Area, - Module = permission.Module, + PermissionModuleCode = permission.PermissionModuleCode, IsSystem = true }); } @@ -363,18 +462,13 @@ internal sealed class BackofficeService( var normalizedPermissions = NormalizeCodes(permissionCodes); var normalizedMenus = NormalizeCodes(menuCodes); await ValidatePermissionCodesAsync(normalizedPermissions, BackendPermissionArea.Tenant, cancellationToken); - foreach (var permissionCode in normalizedPermissions) + var allowedPermissions = await featureAccessService.FilterPermissionCodesAsync( + tenantId, normalizedPermissions, FeatureAccessOperation.Write, cancellationToken); + if (allowedPermissions.Count != normalizedPermissions.Length) { - if (!await capabilityAccessEvaluator.IsAllowedAsync( - tenantId, - ResolveModuleCode(permissionCode), - CapabilityOperation.Write, - cancellationToken)) - { - throw new BackofficeException( - "One or more permissions belong to a module unavailable to this tenant.", - "capability_not_available"); - } + throw new BackofficeException( + "One or more permissions belong to a feature unavailable to this tenant.", + "feature_not_available"); } await ValidateMenuCodesAsync(normalizedMenus, BackendPermissionArea.Tenant, cancellationToken); await dbContext.TenantBackendRolePermissions.Where(item => item.TenantId == tenantId && item.RoleId == roleId).ExecuteDeleteAsync(cancellationToken); @@ -433,25 +527,12 @@ internal sealed class BackofficeService( CapabilityOperation operation, CancellationToken cancellationToken) { - var enabled = new List(); - foreach (var permissionCode in permissionCodes.Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal)) - { - if (await capabilityAccessEvaluator.IsAllowedAsync( - tenantId, - ResolveModuleCode(permissionCode), - operation, - cancellationToken)) - { - enabled.Add(permissionCode); - } - } - return enabled.ToArray(); - } - - private static string ResolveModuleCode(string permissionCode) - { - var parts = permissionCode.Split(':', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - return parts.Length >= 2 ? parts[1].ToLowerInvariant() : permissionCode.ToLowerInvariant(); + var featureOperation = operation == CapabilityOperation.Read + ? FeatureAccessOperation.Read + : FeatureAccessOperation.Write; + var enabled = await featureAccessService.FilterPermissionCodesAsync( + tenantId, permissionCodes, featureOperation, cancellationToken); + return enabled.Order(StringComparer.Ordinal).ToArray(); } private async Task ValidateMenuCodesAsync(string[] codes, BackendPermissionArea area, CancellationToken cancellationToken) @@ -544,7 +625,7 @@ internal sealed class BackofficeService( private static BackofficePermissionItem ToPermissionItem(BackendPermission item) { - return new BackofficePermissionItem(item.Id, item.Code, item.Name, item.Area, item.Module, item.Description, item.SortOrder); + return new BackofficePermissionItem(item.Id, item.Code, item.Name, item.Area, item.PermissionModuleCode, item.Description, item.SortOrder); } private static BackofficeMenuItem ToMenuItem(BackendMenu item) @@ -579,7 +660,9 @@ internal sealed class BackofficeService( menus.Where(item => item.RoleId == role.Id).Select(item => item.MenuCode).Order().ToArray()); } - private sealed record BuiltinPermission(string Code, string Name, BackendPermissionArea Area, string Module); + private sealed record BuiltinPermission(string Code, string Name, BackendPermissionArea Area, string PermissionModuleCode); + private sealed record BuiltinFeature(string Code, string Name, string Category, bool IsCore, int SortOrder); + private sealed record BuiltinPermissionModule(string Code, string Name, BackendPermissionArea Area, string? RequiredFeatureCode, int SortOrder); private sealed record BuiltinMenu(string Code, string? ParentCode, string Title, BackendPermissionArea Area, string Path, string PermissionCode, int SortOrder); } diff --git a/Tiku.Infrastructure/Bootstrap/DevelopmentPlatformAdminSeeder.cs b/Tiku.Infrastructure/Bootstrap/DevelopmentPlatformAdminSeeder.cs index ed7d657..c5add02 100644 --- a/Tiku.Infrastructure/Bootstrap/DevelopmentPlatformAdminSeeder.cs +++ b/Tiku.Infrastructure/Bootstrap/DevelopmentPlatformAdminSeeder.cs @@ -7,6 +7,7 @@ using Npgsql; using Tiku.Application.Security; using Tiku.Domain.Identity; using Tiku.Domain.Operations; +using Tiku.Domain.Platform; using Tiku.Infrastructure.Persistence; namespace Tiku.Infrastructure.Bootstrap; @@ -110,6 +111,22 @@ public static class DevelopmentPlatformAdminSeeder }; dbContext.Users.Add(user); dbContext.PlatformBackendRoles.Add(role); + var platformModuleCodes = permissionCodes + .Select(PermissionModuleCatalog.ResolvePermissionModuleCode) + .Distinct(StringComparer.Ordinal) + .ToArray(); + var existingModuleCodes = dbContext.PermissionModules + .Where(module => platformModuleCodes.Contains(module.Code)) + .Select(module => module.Code) + .ToArray(); + dbContext.PermissionModules.AddRange(platformModuleCodes + .Except(existingModuleCodes, StringComparer.Ordinal) + .Select(code => new PermissionModule + { + Code = code, + Name = code, + Area = BackendPermissionArea.Platform + })); dbContext.BackendPermissions.AddRange( permissionCodes .Where(code => !existingPermissionCodes.Contains(code)) @@ -118,7 +135,7 @@ public static class DevelopmentPlatformAdminSeeder Code = code, Name = code, Area = BackendPermissionArea.Platform, - Module = "platform", + PermissionModuleCode = PermissionModuleCatalog.ResolvePermissionModuleCode(code), Description = "Built-in platform permission.", IsSystem = true })); diff --git a/Tiku.Infrastructure/Bootstrap/PlatformAdminBootstrapper.cs b/Tiku.Infrastructure/Bootstrap/PlatformAdminBootstrapper.cs index 51864d2..965cef2 100644 --- a/Tiku.Infrastructure/Bootstrap/PlatformAdminBootstrapper.cs +++ b/Tiku.Infrastructure/Bootstrap/PlatformAdminBootstrapper.cs @@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore.Storage; using Tiku.Application.Security; using Tiku.Domain.Identity; using Tiku.Domain.Operations; +using Tiku.Domain.Platform; using Tiku.Infrastructure.Persistence; namespace Tiku.Infrastructure.Bootstrap; @@ -100,6 +101,22 @@ public sealed class PlatformAdminBootstrapper( dbContext.PlatformBackendRoles.Add(role); var platformPermissionCodes = BackendPermissions.Platform.ToArray(); + var platformModuleCodes = platformPermissionCodes + .Select(PermissionModuleCatalog.ResolvePermissionModuleCode) + .Distinct(StringComparer.Ordinal) + .ToArray(); + var existingModuleCodes = await dbContext.PermissionModules + .Where(module => platformModuleCodes.Contains(module.Code)) + .Select(module => module.Code) + .ToArrayAsync(cancellationToken); + dbContext.PermissionModules.AddRange(platformModuleCodes + .Except(existingModuleCodes, StringComparer.Ordinal) + .Select(code => new PermissionModule + { + Code = code, + Name = code, + Area = BackendPermissionArea.Platform + })); var existingPermissionCodes = await dbContext.BackendPermissions .Where(permission => platformPermissionCodes.Contains(permission.Code)) .Select(permission => permission.Code) @@ -111,7 +128,7 @@ public sealed class PlatformAdminBootstrapper( Code = permissionCode, Name = permissionCode, Area = BackendPermissionArea.Platform, - Module = "platform", + PermissionModuleCode = PermissionModuleCatalog.ResolvePermissionModuleCode(permissionCode), Description = "Built-in platform permission.", IsSystem = true }); diff --git a/Tiku.Infrastructure/Content/DirectContentService.cs b/Tiku.Infrastructure/Content/DirectContentService.cs index 5609a49..a85f3e3 100644 --- a/Tiku.Infrastructure/Content/DirectContentService.cs +++ b/Tiku.Infrastructure/Content/DirectContentService.cs @@ -20,8 +20,19 @@ namespace Tiku.Infrastructure.Content; public sealed class DirectContentService( TikuDbContext dbContext, IQuestionReferenceService questionReferenceService, - ICurrentAccessContext currentAccessContext) : IDirectContentService + ICurrentAccessContext currentAccessContext, + IFeatureAccessService featureAccessService) : IDirectContentService { + private static readonly string[] ContentPermissions = + [ + BackendPermissions.TenantContentManage, + BackendPermissions.TenantVocabularyManage, + BackendPermissions.TenantHandbookManage, + BackendPermissions.TenantVideoManage, + BackendPermissions.TenantScorelineManage, + BackendPermissions.TenantSiteContentManage, + BackendPermissions.TenantJobManage + ]; private const int DefaultLimit = 100; private const int MaxLimit = 1000; private static readonly Regex ScorelineFieldKeyRegex = new("^[A-Za-z][A-Za-z0-9_]{0,63}$", RegexOptions.Compiled); @@ -50,6 +61,13 @@ public sealed class DirectContentService( TenantId = actor.TenantId }; ApplyQuestion(question, command); + if (question.Status != QuestionStatus.Archived) + { + await featureAccessService.ConsumeQuotaIfConfiguredAsync( + actor.TenantId, + SaasQuotaMetricCatalog.PrivateQuestionCount, + cancellationToken: cancellationToken); + } dbContext.Questions.Add(question); await dbContext.SaveChangesAsync(cancellationToken); @@ -85,7 +103,19 @@ public sealed class DirectContentService( } await AssertQuestionReferencesAsync(actor.TenantId, command, cancellationToken); + await using var transaction = dbContext.Database.CurrentTransaction is null + ? await dbContext.Database.BeginTransactionAsync(cancellationToken) + : null; + var wasCounted = question.Status != QuestionStatus.Archived; ApplyQuestion(question, command); + var isCounted = question.Status != QuestionStatus.Archived; + if (!wasCounted && isCounted) + { + await featureAccessService.ConsumeQuotaIfConfiguredAsync( + actor.TenantId, + SaasQuotaMetricCatalog.PrivateQuestionCount, + cancellationToken: cancellationToken); + } QuestionVersion? version; if (command.CreateVersion || !question.CurrentVersionId.HasValue) { @@ -120,6 +150,18 @@ public sealed class DirectContentService( await SyncPrimaryCollectionItemAsync(actor, question, cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); + if (wasCounted && !isCounted) + { + await featureAccessService.ReleaseQuotaAsync( + actor.TenantId, + SaasQuotaMetricCatalog.PrivateQuestionCount, + 1, + cancellationToken); + } + if (transaction is not null) + { + await transaction.CommitAsync(cancellationToken); + } return new ContentManagementResult(ToQuestionItem(question, version)); } @@ -1815,7 +1857,7 @@ public sealed class DirectContentService( if (!access.IsCurrentTenantMember || access.UserId != actor.UserId || access.TenantId != actor.TenantId || - !access.HasTenantPermission(BackendPermissions.TenantContentManage)) + !ContentPermissions.Any(access.HasTenantPermission)) { throw new ContentManagementException("Tenant content access was denied.", "content_access_denied"); } diff --git a/Tiku.Infrastructure/DependencyInjection.cs b/Tiku.Infrastructure/DependencyInjection.cs index cda427c..327b55c 100644 --- a/Tiku.Infrastructure/DependencyInjection.cs +++ b/Tiku.Infrastructure/DependencyInjection.cs @@ -16,6 +16,7 @@ using Tiku.Application.QuestionBanks; using Tiku.Application.Profile; using Tiku.Application.Points; using Tiku.Application.PlatformAdmin; +using Tiku.Application.PlatformBilling; using Tiku.Application.Scoreline; using Tiku.Application.Storage; using Tiku.Application.StudyContent; @@ -36,6 +37,7 @@ using Tiku.Infrastructure.Persistence; using Tiku.Infrastructure.Profile; using Tiku.Infrastructure.Points; using Tiku.Infrastructure.PlatformAdmin; +using Tiku.Infrastructure.PlatformBilling; using Tiku.Infrastructure.QuestionBanks; using Tiku.Infrastructure.Scoreline; using Tiku.Infrastructure.Security; @@ -125,8 +127,19 @@ public static class DependencyInjection services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddOptions(); + services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddOptions(); services.AddScoped(); services.AddSingleton(); services.AddScoped(); diff --git a/Tiku.Infrastructure/Jobs/BackgroundJobService.cs b/Tiku.Infrastructure/Jobs/BackgroundJobService.cs index 9097647..1cc1490 100644 --- a/Tiku.Infrastructure/Jobs/BackgroundJobService.cs +++ b/Tiku.Infrastructure/Jobs/BackgroundJobService.cs @@ -16,7 +16,7 @@ namespace Tiku.Infrastructure.Jobs; internal sealed class BackgroundJobService( TikuDbContext dbContext, ITenantExecutionScope tenantExecutionScope, - ICapabilityAccessEvaluator capabilityAccessEvaluator, + IFeatureAccessService featureAccessService, IBackgroundJobDispatcher backgroundJobDispatcher) : IBackgroundJobService { private static readonly TimeSpan LeaseDuration = TimeSpan.FromMinutes(5); @@ -26,13 +26,29 @@ internal sealed class BackgroundJobService( CancellationToken cancellationToken = default) { var normalizedJobType = NormalizeJobType(command.JobType); - if (!await capabilityAccessEvaluator.IsAllowedAsync( + if (!(await featureAccessService.EvaluateAsync( command.TenantId, - ResolveCapabilityModule(normalizedJobType), - CapabilityOperation.Write, - cancellationToken)) + ResolveRequiredFeature(normalizedJobType, command.Payload), + FeatureAccessOperation.Write, + cancellationToken)).Allowed) { - throw new InvalidOperationException("Tenant capability does not allow this background job."); + throw new InvalidOperationException("Tenant feature entitlement does not allow this background job."); + } + var quotaMetric = ResolveQuotaMetric(normalizedJobType); + var quotaConsumed = false; + if (quotaMetric is not null) + { + var quota = (await featureAccessService.GetQuotaSummaryAsync(command.TenantId, cancellationToken)) + .SingleOrDefault(value => value.MetricCode == quotaMetric); + if (quota is not null) + { + quotaConsumed = await featureAccessService.TryConsumeQuotaAsync( + command.TenantId, quotaMetric, 1, cancellationToken); + if (!quotaConsumed) + { + throw new FeatureAccessException("The background job quota has been exhausted.", "feature_quota_exhausted"); + } + } } var job = new BackgroundJob { @@ -43,6 +59,18 @@ internal sealed class BackgroundJobService( MaxRetries = Math.Clamp(command.MaxRetries, 0, 20) }; dbContext.BackgroundJobs.Add(job); + try + { + await dbContext.SaveChangesAsync(cancellationToken); + } + catch + { + if (quotaConsumed && quotaMetric is not null) + { + await featureAccessService.ReleaseQuotaAsync(command.TenantId, quotaMetric, 1, CancellationToken.None); + } + throw; + } if (job.RunAfter is null && backgroundJobDispatcher.IsEnabled) { await backgroundJobDispatcher.DispatchAsync( @@ -52,10 +80,16 @@ internal sealed class BackgroundJobService( job.Id.ToString("N"), cancellationToken); } - await dbContext.SaveChangesAsync(cancellationToken); return ToItem(job); } + private static string? ResolveQuotaMetric(string jobType) => jobType switch + { + "content_import" => SaasQuotaMetricCatalog.ImportCount, + "content_export" => SaasQuotaMetricCatalog.ExportCount, + _ => null + }; + public async Task ProcessPendingAsync( string workerId, int batchSize, @@ -118,15 +152,15 @@ internal sealed class BackgroundJobService( { return false; } - if (!await capabilityAccessEvaluator.IsAllowedAsync( + if (!(await featureAccessService.EvaluateAsync( job.TenantId, - ResolveCapabilityModule(job.JobType), - CapabilityOperation.Write, - cancellationToken)) + ResolveRequiredFeature(job.JobType, job.Payload), + FeatureAccessOperation.Write, + cancellationToken)).Allowed) { job.Status = BackgroundJobStatus.Failed; job.CompletedAt = DateTimeOffset.UtcNow; - job.LastError = "Tenant capability was revoked before job execution."; + job.LastError = "Tenant feature entitlement was revoked before job execution."; await dbContext.SaveChangesAsync(cancellationToken); return true; } @@ -205,7 +239,7 @@ internal sealed class BackgroundJobService( "content_export" => await ProcessContentExportAsync(scopedDbContext, job, cancellationToken), "content_import" => await ProcessContentImportAsync(scopedProvider, job, cancellationToken), "asset_security_scan" => throw new NotSupportedException("asset_security_scan requires a configured scanner provider before it can write scan results."), - "statistics_aggregation" => await ProcessStatisticsAggregationAsync(scopedDbContext, job, cancellationToken), + "statistics_aggregation" => await ProcessStatisticsAggregationAsync(scopedProvider, scopedDbContext, job, cancellationToken), "commerce_reconciliation" => await ProcessCommerceReconciliationAsync(scopedDbContext, job, cancellationToken), "tenant_domain_recheck" => await ProcessTenantDomainRecheckAsync(scopedProvider, cancellationToken), _ => throw new InvalidOperationException($"Unsupported background job type '{job.JobType}'.") @@ -380,6 +414,7 @@ internal sealed class BackgroundJobService( } private async Task ProcessStatisticsAggregationAsync( + IServiceProvider scopedProvider, TikuDbContext scopedDbContext, BackgroundJob job, CancellationToken cancellationToken) @@ -398,12 +433,22 @@ internal sealed class BackgroundJobService( item.Status == OrderStatus.PartiallyRefunded || item.Status == OrderStatus.Refunded)) .SumAsync(item => item.AmountCents - item.RefundedAmountCents, cancellationToken); + var quotaUsage = await scopedProvider.GetRequiredService() + .ReconcileTenantAsync( + new ReconcileFeatureUsageRequest( + job.TenantId, + SystemScopeCallerType.Worker, + nameof(BackgroundJobService), + "Reconcile tenant current feature usage during statistics aggregation", + $"feature-usage-{job.Id:N}"), + cancellationToken); return JsonSerializer.SerializeToElement(new { since, activeLearnerCount, paidOrderCount, - revenueCents + revenueCents, + quotaUsage }); } @@ -412,13 +457,14 @@ internal sealed class BackgroundJobService( return jobType.Trim().ToLowerInvariant(); } - private static string ResolveCapabilityModule(string jobType) => jobType switch + private static string ResolveRequiredFeature(string jobType, JsonElement payload) => jobType switch { - "content_export" or "content_import" or "asset_security_scan" => "content", - "statistics_aggregation" => "dashboard", - "commerce_reconciliation" => "commerce", - "tenant_domain_recheck" => "settings", - _ => "job" + "content_import" => SaasFeatureCatalog.ResolveContentImportFeature(GetJsonString(payload, "importType")) + ?? throw new InvalidOperationException("Background content import type is not supported."), + "content_export" or "asset_security_scan" => SaasFeatureCatalog.PrivateQuestionBank, + "commerce_reconciliation" => SaasFeatureCatalog.StudentStore, + "statistics_aggregation" or "tenant_domain_recheck" => SaasFeatureCatalog.CoreBackoffice, + _ => SaasFeatureCatalog.CoreBackoffice }; private static string NormalizeProvider(string? provider) diff --git a/Tiku.Infrastructure/Persistence/Configurations/CommerceConfigurations.cs b/Tiku.Infrastructure/Persistence/Configurations/CommerceConfigurations.cs index be95043..115fa7a 100644 --- a/Tiku.Infrastructure/Persistence/Configurations/CommerceConfigurations.cs +++ b/Tiku.Infrastructure/Persistence/Configurations/CommerceConfigurations.cs @@ -299,35 +299,6 @@ internal sealed class CouponRedemptionConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ConfigureTenantEntity("tenant_subscriptions"); - builder.ConfigureTimestamps(); - builder.Property(entity => entity.PlanCode).HasMaxLength(100); - builder.Property(entity => entity.Status).HasSnakeCaseEnum(); - builder.Property(entity => entity.BillingCycle).HasMaxLength(50); - builder.Property(entity => entity.Metadata).IsJson("{}"); - builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.ExpiresAt }); - } -} - -internal sealed class TenantUsageRecordConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ConfigureEntity("tenant_usage_records"); - builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id }); - builder.Property(entity => entity.MetricKey).HasMaxLength(100); - builder.Property(entity => entity.MetricValue).HasPrecision(18, 4); - builder.Property(entity => entity.Metadata).IsJson("{}"); - builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()"); - builder.HasIndex(entity => new { entity.TenantId, entity.MetricKey, entity.PeriodStart, entity.PeriodEnd }); - builder.HasOne().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade); - } -} - internal sealed class CommerceRefundRequestConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) diff --git a/Tiku.Infrastructure/Persistence/Configurations/OperationsConfigurations.cs b/Tiku.Infrastructure/Persistence/Configurations/OperationsConfigurations.cs index ba4de0f..7efaada 100644 --- a/Tiku.Infrastructure/Persistence/Configurations/OperationsConfigurations.cs +++ b/Tiku.Infrastructure/Persistence/Configurations/OperationsConfigurations.cs @@ -4,6 +4,7 @@ using Tiku.Domain.Catalog; using Tiku.Domain.Content; using Tiku.Domain.Identity; using Tiku.Domain.Operations; +using Tiku.Domain.Platform; using Tiku.Domain.QuestionBanks; using Tiku.Domain.Tenancy; @@ -89,10 +90,14 @@ internal sealed class BackendPermissionConfiguration : IEntityTypeConfiguration< builder.Property(entity => entity.Code).HasMaxLength(160); builder.Property(entity => entity.Name).HasMaxLength(200); builder.Property(entity => entity.Area).HasSnakeCaseEnum(); - builder.Property(entity => entity.Module).HasMaxLength(100); + builder.Property(entity => entity.PermissionModuleCode).HasMaxLength(120); builder.Property(entity => entity.Description).HasMaxLength(1000); builder.HasIndex(entity => entity.Code).IsUnique(); - builder.HasIndex(entity => new { entity.Area, entity.Module, entity.SortOrder }); + builder.HasIndex(entity => new { entity.Area, entity.PermissionModuleCode, entity.SortOrder }); + builder.HasOne().WithMany() + .HasPrincipalKey(entity => entity.Code) + .HasForeignKey(entity => entity.PermissionModuleCode) + .OnDelete(DeleteBehavior.Restrict); } } diff --git a/Tiku.Infrastructure/Persistence/Configurations/PlatformOperationsConfigurations.cs b/Tiku.Infrastructure/Persistence/Configurations/PlatformOperationsConfigurations.cs index 59da03c..439bfd1 100644 --- a/Tiku.Infrastructure/Persistence/Configurations/PlatformOperationsConfigurations.cs +++ b/Tiku.Infrastructure/Persistence/Configurations/PlatformOperationsConfigurations.cs @@ -7,76 +7,6 @@ using Tiku.Domain.Tenancy; namespace Tiku.Infrastructure.Persistence.Configurations; -internal sealed class PlatformSaasPlanConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ConfigureEntity("platform_saas_plans"); - builder.ConfigureTimestamps(); - builder.Property(entity => entity.Code).HasMaxLength(100); - builder.Property(entity => entity.Name).HasMaxLength(200); - builder.Property(entity => entity.BillingCycle).HasSnakeCaseEnum(); - builder.Property(entity => entity.Currency).HasMaxLength(10); - builder.Property(entity => entity.IncludedQuotas).IsJson("{}"); - builder.Property(entity => entity.OveragePrices).IsJson("{}"); - builder.Property(entity => entity.FeatureFlags).IsJson("{}"); - builder.Property(entity => entity.Status).HasSnakeCaseEnum(); - builder.HasIndex(entity => entity.Code).IsUnique(); - builder.HasIndex(entity => new { entity.Status, entity.SortOrder }); - builder.ToTable(table => table.HasCheckConstraint("ck_platform_saas_plans_amount", "base_amount_cents >= 0")); - } -} - -internal sealed class ProductModuleConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ConfigureEntity("product_modules"); - builder.ConfigureTimestamps(); - builder.Property(entity => entity.Code).HasMaxLength(100); - builder.Property(entity => entity.Name).HasMaxLength(200); - builder.Property(entity => entity.Description).HasMaxLength(1000); - builder.Property(entity => entity.Status).HasSnakeCaseEnum(); - builder.HasIndex(entity => entity.Code).IsUnique(); - } -} - -internal sealed class PlanModuleEntitlementConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ConfigureEntity("plan_module_entitlements"); - builder.Property(entity => entity.PlanCode).HasMaxLength(100); - builder.Property(entity => entity.ModuleCode).HasMaxLength(100); - builder.HasIndex(entity => new { entity.PlanCode, entity.ModuleCode }).IsUnique(); - builder.HasOne().WithMany() - .HasPrincipalKey(entity => entity.Code) - .HasForeignKey(entity => entity.PlanCode) - .OnDelete(DeleteBehavior.Cascade); - builder.HasOne().WithMany() - .HasPrincipalKey(entity => entity.Code) - .HasForeignKey(entity => entity.ModuleCode) - .OnDelete(DeleteBehavior.Cascade); - } -} - -internal sealed class TenantModuleOverrideConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ConfigureTenantEntity("tenant_module_overrides"); - builder.ConfigureTimestamps(); - builder.Property(entity => entity.ModuleCode).HasMaxLength(100); - builder.Property(entity => entity.Mode).HasSnakeCaseEnum(); - builder.Property(entity => entity.Reason).HasMaxLength(1000); - builder.HasIndex(entity => new { entity.TenantId, entity.ModuleCode }).IsUnique(); - builder.HasOne().WithMany() - .HasPrincipalKey(entity => entity.Code) - .HasForeignKey(entity => entity.ModuleCode) - .OnDelete(DeleteBehavior.Restrict); - } -} - internal sealed class TenantBillingProfileConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) @@ -98,84 +28,11 @@ internal sealed class TenantBillingProfileConfiguration : IEntityTypeConfigurati } } -internal sealed class TenantInvoiceConfiguration : IEntityTypeConfiguration +internal sealed class PlatformBillingInvoiceReminderConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) + public void Configure(EntityTypeBuilder builder) { - builder.ConfigureTenantEntity("tenant_invoices"); - builder.ConfigureTimestamps(); - builder.Property(entity => entity.InvoiceNo).HasMaxLength(100); - builder.Property(entity => entity.InvoiceType).HasSnakeCaseEnum(); - builder.Property(entity => entity.Status).HasSnakeCaseEnum(); - builder.Property(entity => entity.Currency).HasMaxLength(10); - builder.Property(entity => entity.Metadata).IsJson("{}"); - builder.HasIndex(entity => new { entity.TenantId, entity.InvoiceNo }).IsUnique(); - builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.DueDate }); - builder.HasIndex(entity => new { entity.TenantId, entity.BillingPeriodStart, entity.BillingPeriodEnd }) - .IsUnique() - .HasFilter("invoice_type = 'usage_overage' and status <> 'void' and metadata->>'source' = 'usage_overage_auto'"); - builder.ToTable(table => - { - table.HasCheckConstraint("ck_tenant_invoices_amounts", "subtotal_cents >= 0 and discount_cents >= 0 and tax_cents >= 0 and total_cents >= 0 and paid_cents >= 0 and balance_cents >= 0"); - table.HasCheckConstraint("ck_tenant_invoices_period", "billing_period_end is null or billing_period_start is null or billing_period_end >= billing_period_start"); - }); - builder.HasOne().WithMany().HasForeignKey(entity => entity.CreatedBy).OnDelete(DeleteBehavior.SetNull); - } -} - -internal sealed class TenantInvoiceItemConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ConfigureEntity("tenant_invoice_items"); - builder.HasAlternateKey(entity => new { entity.TenantId, entity.Id }); - builder.Property(entity => entity.ItemType).HasMaxLength(100); - builder.Property(entity => entity.Description).HasMaxLength(500); - builder.Property(entity => entity.Quantity).HasPrecision(12, 2); - builder.Property(entity => entity.Metadata).IsJson("{}"); - builder.Property(entity => entity.CreatedAt).HasDefaultValueSql("now()"); - builder.HasIndex(entity => entity.InvoiceId); - builder.ToTable(table => - { - table.HasCheckConstraint("ck_tenant_invoice_items_quantity", "quantity > 0"); - table.HasCheckConstraint("ck_tenant_invoice_items_amounts", "unit_amount_cents >= 0 and amount_cents >= 0"); - }); - builder.HasOne().WithMany().HasForeignKey(entity => entity.TenantId).OnDelete(DeleteBehavior.Cascade); - builder.HasOne().WithMany() - .HasForeignKey(entity => new { entity.TenantId, entity.InvoiceId }) - .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) - .OnDelete(DeleteBehavior.Cascade); - } -} - -internal sealed class TenantInvoicePaymentConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ConfigureTenantEntity("tenant_invoice_payments"); - builder.ConfigureTimestamps(); - builder.Property(entity => entity.PaymentNo).HasMaxLength(100); - builder.Property(entity => entity.Provider).HasMaxLength(50); - builder.Property(entity => entity.Method).HasMaxLength(50); - builder.Property(entity => entity.Status).HasSnakeCaseEnum(); - builder.Property(entity => entity.ProviderTradeNo).HasMaxLength(200); - builder.Property(entity => entity.RawPayload).IsJson("{}"); - builder.HasIndex(entity => new { entity.TenantId, entity.PaymentNo }).IsUnique(); - builder.HasIndex(entity => new { entity.InvoiceId, entity.Status }); - builder.ToTable(table => table.HasCheckConstraint("ck_tenant_invoice_payments_amount", "amount_cents >= 0")); - builder.HasOne().WithMany() - .HasForeignKey(entity => new { entity.TenantId, entity.InvoiceId }) - .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) - .OnDelete(DeleteBehavior.Cascade); - builder.HasOne().WithMany().HasForeignKey(entity => entity.ReceivedBy).OnDelete(DeleteBehavior.SetNull); - } -} - -internal sealed class TenantInvoiceReminderConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ConfigureTenantEntity("tenant_invoice_reminders"); + builder.ConfigureTenantEntity("platform_billing_invoice_reminders"); builder.ConfigureTimestamps(); builder.Property(entity => entity.ReminderType).HasSnakeCaseEnum(); builder.Property(entity => entity.Channel).HasSnakeCaseEnum(); @@ -186,10 +43,10 @@ internal sealed class TenantInvoiceReminderConfiguration : IEntityTypeConfigurat builder.HasIndex(entity => new { entity.InvoiceId, entity.ReminderDate }); builder.ToTable(table => { - table.HasCheckConstraint("ck_tenant_invoice_reminders_level", "reminder_level between 1 and 20"); - table.HasCheckConstraint("ck_tenant_invoice_reminders_balance", "balance_cents_snapshot >= 0"); + table.HasCheckConstraint("ck_platform_billing_invoice_reminders_level", "reminder_level between 1 and 20"); + table.HasCheckConstraint("ck_platform_billing_invoice_reminders_balance", "balance_cents_snapshot >= 0"); }); - builder.HasOne().WithMany() + builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.InvoiceId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) .OnDelete(DeleteBehavior.Cascade); @@ -243,11 +100,11 @@ internal sealed class PlatformAuditAlertConfiguration : IEntityTypeConfiguration } } -internal sealed class PlatformDunningNotificationChannelConfiguration : IEntityTypeConfiguration +internal sealed class PlatformBillingDunningNotificationChannelConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) + public void Configure(EntityTypeBuilder builder) { - builder.ConfigureEntity("platform_dunning_notification_channels"); + builder.ConfigureEntity("platform_billing_dunning_notification_channels"); builder.ConfigureTimestamps(); builder.Property(entity => entity.ChannelCode).HasMaxLength(100); builder.Property(entity => entity.Name).HasMaxLength(200); @@ -262,17 +119,17 @@ internal sealed class PlatformDunningNotificationChannelConfiguration : IEntityT builder.HasIndex(entity => new { entity.Enabled, entity.MinReminderLevel, entity.ChannelCode }); builder.ToTable(table => { - table.HasCheckConstraint("ck_platform_dunning_channels_level", "min_reminder_level between 1 and 20"); - table.HasCheckConstraint("ck_platform_dunning_channels_timeout", "timeout_seconds between 1 and 60"); + table.HasCheckConstraint("ck_platform_billing_dunning_channels_level", "min_reminder_level between 1 and 20"); + table.HasCheckConstraint("ck_platform_billing_dunning_channels_timeout", "timeout_seconds between 1 and 60"); }); } } -internal sealed class PlatformDunningNotificationEventConfiguration : IEntityTypeConfiguration +internal sealed class PlatformBillingDunningNotificationEventConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) + public void Configure(EntityTypeBuilder builder) { - builder.ConfigureTenantEntity("platform_dunning_notification_events"); + builder.ConfigureTenantEntity("platform_billing_dunning_notification_events"); builder.ConfigureTimestamps(); builder.Property(entity => entity.Provider).HasSnakeCaseEnum(); builder.Property(entity => entity.Status).HasSnakeCaseEnum(); @@ -284,15 +141,20 @@ internal sealed class PlatformDunningNotificationEventConfiguration : IEntityTyp builder.HasIndex(entity => new { entity.ReminderId, entity.Status, entity.CreatedAt }); builder.HasIndex(entity => new { entity.InvoiceId, entity.Status, entity.CreatedAt }); builder.HasIndex(entity => new { entity.TenantId, entity.Status, entity.CreatedAt }); - builder.ToTable(table => table.HasCheckConstraint("ck_platform_dunning_events_attempts", "attempts >= 0")); - builder.HasOne().WithMany().HasForeignKey(entity => entity.ChannelId).OnDelete(DeleteBehavior.Cascade); - builder.HasOne().WithMany() + builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_dunning_events_attempts", "attempts >= 0")); + builder.HasOne().WithMany() + .HasForeignKey(entity => entity.ChannelId) + .HasConstraintName("fk_platform_billing_dunning_events_channel") + .OnDelete(DeleteBehavior.Cascade); + builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.ReminderId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) + .HasConstraintName("fk_platform_billing_dunning_events_reminder") .OnDelete(DeleteBehavior.Cascade); - builder.HasOne().WithMany() + builder.HasOne().WithMany() .HasForeignKey(entity => new { entity.TenantId, entity.InvoiceId }) .HasPrincipalKey(entity => new { entity.TenantId, entity.Id }) + .HasConstraintName("fk_platform_billing_dunning_events_invoice") .OnDelete(DeleteBehavior.Cascade); } } diff --git a/Tiku.Infrastructure/Persistence/Configurations/SaasBillingConfigurations.cs b/Tiku.Infrastructure/Persistence/Configurations/SaasBillingConfigurations.cs new file mode 100644 index 0000000..2d99b62 --- /dev/null +++ b/Tiku.Infrastructure/Persistence/Configurations/SaasBillingConfigurations.cs @@ -0,0 +1,349 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Tiku.Domain.Platform; +using Tiku.Domain.Tenancy; + +namespace Tiku.Infrastructure.Persistence.Configurations; + +internal sealed class SaasFeatureConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureEntity("saas_features"); + builder.ConfigureTimestamps(); + builder.Property(value => value.Code).HasMaxLength(120); + builder.Property(value => value.Name).HasMaxLength(200); + builder.Property(value => value.Category).HasMaxLength(100); + builder.Property(value => value.Description).HasMaxLength(1000); + builder.Property(value => value.Currency).HasMaxLength(10); + builder.Property(value => value.Status).HasSnakeCaseEnum(); + builder.HasIndex(value => value.Code).IsUnique(); + builder.HasIndex(value => new { value.Status, value.Category, value.SortOrder }); + builder.ToTable(table => table.HasCheckConstraint("ck_saas_features_reference_price", "reference_price_cents >= 0")); + } +} + +internal sealed class PermissionModuleConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureEntity("permission_modules"); + builder.ConfigureTimestamps(); + builder.Property(value => value.Code).HasMaxLength(120); + builder.Property(value => value.Name).HasMaxLength(200); + builder.Property(value => value.Area).HasSnakeCaseEnum(); + builder.Property(value => value.RequiredFeatureCode).HasMaxLength(120); + builder.Property(value => value.Description).HasMaxLength(1000); + builder.HasIndex(value => value.Code).IsUnique(); + builder.HasIndex(value => new { value.Area, value.SortOrder }); + builder.HasOne().WithMany() + .HasPrincipalKey(value => value.Code) + .HasForeignKey(value => value.RequiredFeatureCode) + .OnDelete(DeleteBehavior.Restrict); + } +} + +internal sealed class SaasOfferingConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureEntity("saas_offerings"); + builder.ConfigureTimestamps(); + builder.Property(value => value.Code).HasMaxLength(120); + builder.Property(value => value.Name).HasMaxLength(200); + builder.Property(value => value.Type).HasSnakeCaseEnum(); + builder.Property(value => value.Status).HasSnakeCaseEnum(); + builder.Property(value => value.Description).HasMaxLength(1000); + builder.HasIndex(value => value.Code).IsUnique(); + builder.HasIndex(value => new { value.Type, value.Status, value.SortOrder }); + } +} + +internal sealed class SaasOfferingVersionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureEntity("saas_offering_versions"); + builder.ConfigureTimestamps(); + builder.Property(value => value.Status).HasSnakeCaseEnum(); + builder.Property(value => value.BillingCycle).HasSnakeCaseEnum(); + builder.Property(value => value.Currency).HasMaxLength(10); + builder.Property(value => value.Metadata).IsJson("{}"); + builder.HasIndex(value => new { value.OfferingId, value.Version }).IsUnique(); + builder.HasIndex(value => new { value.OfferingId, value.Status, value.EffectiveAt }); + builder.HasOne().WithMany().HasForeignKey(value => value.OfferingId).OnDelete(DeleteBehavior.Cascade); + builder.ToTable(table => + { + table.HasCheckConstraint("ck_saas_offering_versions_version", "version > 0"); + table.HasCheckConstraint("ck_saas_offering_versions_amount", "original_amount_cents >= 0 and amount_cents >= 0 and amount_cents <= original_amount_cents"); + }); + } +} + +internal sealed class SaasOfferingVersionFeatureConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureEntity("saas_offering_version_features"); + builder.Property(value => value.FeatureCode).HasMaxLength(120); + builder.HasIndex(value => new { value.OfferingVersionId, value.FeatureCode }).IsUnique(); + builder.HasOne().WithMany().HasForeignKey(value => value.OfferingVersionId).OnDelete(DeleteBehavior.Cascade); + builder.HasOne().WithMany().HasPrincipalKey(value => value.Code).HasForeignKey(value => value.FeatureCode).OnDelete(DeleteBehavior.Restrict); + } +} + +internal sealed class SaasFeatureLimitDefinitionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureEntity("saas_feature_limit_definitions"); + builder.ConfigureTimestamps(); + builder.Property(value => value.MetricCode).HasMaxLength(120); + builder.Property(value => value.FeatureCode).HasMaxLength(120); + builder.Property(value => value.Name).HasMaxLength(200); + builder.Property(value => value.Unit).HasMaxLength(50); + builder.Property(value => value.Kind).HasSnakeCaseEnum(); + builder.HasIndex(value => value.MetricCode).IsUnique(); + builder.HasIndex(value => new { value.FeatureCode, value.Kind }); + builder.HasOne().WithMany().HasPrincipalKey(value => value.Code).HasForeignKey(value => value.FeatureCode).OnDelete(DeleteBehavior.Cascade); + builder.ToTable(table => table.HasCheckConstraint("ck_saas_feature_limit_warning", "warning_percent between 1 and 100")); + } +} + +internal sealed class SaasOfferingVersionLimitConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureEntity("saas_offering_version_limits"); + builder.Property(value => value.MetricCode).HasMaxLength(120); + builder.HasIndex(value => new { value.OfferingVersionId, value.MetricCode }).IsUnique(); + builder.HasOne().WithMany().HasForeignKey(value => value.OfferingVersionId).OnDelete(DeleteBehavior.Cascade); + builder.HasOne().WithMany().HasPrincipalKey(value => value.MetricCode).HasForeignKey(value => value.MetricCode).OnDelete(DeleteBehavior.Restrict); + builder.ToTable(table => table.HasCheckConstraint("ck_saas_offering_version_limits_value", "limit_value >= 0")); + } +} + +internal sealed class TenantSaasSubscriptionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureTenantEntity("tenant_saas_subscriptions"); + builder.ConfigureTimestamps(); + builder.Property(value => value.Status).HasSnakeCaseEnum(); + builder.Property(value => value.LifecycleVersion).IsConcurrencyToken(); + builder.Property(value => value.Metadata).IsJson("{}"); + builder.HasIndex(value => new { value.TenantId, value.Status, value.CurrentPeriodEnd }); + builder.HasIndex(value => value.TenantId).IsUnique().HasFilter("status in ('trial', 'active', 'past_due', 'suspended')"); + builder.HasOne().WithMany().HasForeignKey(value => value.BaseOfferingVersionId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(value => value.ScheduledBaseOfferingVersionId).OnDelete(DeleteBehavior.Restrict); + builder.ToTable(table => table.HasCheckConstraint("ck_tenant_saas_subscriptions_period", "current_period_end > current_period_start and current_period_start >= starts_at")); + } +} + +internal sealed class TenantSaasSubscriptionItemConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureTenantEntity("tenant_saas_subscription_items"); + builder.ConfigureTimestamps(); + builder.Property(value => value.ItemType).HasSnakeCaseEnum(); + builder.Property(value => value.Status).HasSnakeCaseEnum(); + builder.HasIndex(value => new { value.TenantId, value.SubscriptionId, value.OfferingVersionId, value.Status }); + builder.HasIndex(value => new { value.TenantId, value.SubscriptionId, value.ItemType }) + .HasDatabaseName("ux_tenant_saas_subscription_items_current_base") + .IsUnique() + .HasFilter("item_type = 'base_plan' and status = 'active'"); + builder.HasIndex(value => new { value.TenantId, value.SubscriptionId, value.Status }) + .HasDatabaseName("ux_tenant_saas_subscription_items_scheduled_base") + .IsUnique() + .HasFilter("item_type = 'base_plan' and status = 'scheduled'"); + builder.HasOne().WithMany() + .HasForeignKey(value => new { value.TenantId, value.SubscriptionId }) + .HasPrincipalKey(value => new { value.TenantId, value.Id }) + .OnDelete(DeleteBehavior.Cascade); + builder.HasOne().WithMany().HasForeignKey(value => value.OfferingVersionId).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany() + .HasForeignKey(value => new { value.TenantId, value.SourceOrderItemId }) + .HasPrincipalKey(value => new { value.TenantId, value.Id }) + .OnDelete(DeleteBehavior.Restrict); + builder.ToTable(table => table.HasCheckConstraint("ck_tenant_saas_subscription_items_period", "ends_at > starts_at")); + } +} + +internal sealed class TenantFeatureOverrideConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureTenantEntity("tenant_feature_overrides"); + builder.ConfigureTimestamps(); + builder.Property(value => value.FeatureCode).HasMaxLength(120); + builder.Property(value => value.Mode).HasSnakeCaseEnum(); + builder.Property(value => value.Reason).HasMaxLength(1000); + builder.HasIndex(value => new { value.TenantId, value.FeatureCode }).IsUnique(); + builder.HasOne().WithMany().HasPrincipalKey(value => value.Code).HasForeignKey(value => value.FeatureCode).OnDelete(DeleteBehavior.Restrict); + } +} + +internal sealed class TenantFeatureUsageConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureTenantEntity("tenant_feature_usage"); + builder.ConfigureTimestamps(); + builder.Property(value => value.MetricCode).HasMaxLength(120); + builder.Property(value => value.Version).IsConcurrencyToken(); + builder.HasIndex(value => new { value.TenantId, value.MetricCode, value.PeriodStart, value.PeriodEnd }).IsUnique(); + builder.HasOne().WithMany().HasPrincipalKey(value => value.MetricCode).HasForeignKey(value => value.MetricCode).OnDelete(DeleteBehavior.Restrict); + builder.ToTable(table => + { + table.HasCheckConstraint("ck_tenant_feature_usage_values", "used_value >= 0 and limit_value_snapshot >= 0"); + table.HasCheckConstraint("ck_tenant_feature_usage_period", "period_end > period_start"); + }); + } +} + +internal sealed class PlatformBillingQuoteConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureTenantEntity("platform_billing_quotes"); + builder.ConfigureTimestamps(); + builder.Property(value => value.QuoteNo).HasMaxLength(100); + builder.Property(value => value.IdempotencyKey).HasMaxLength(200); + builder.Property(value => value.Status).HasSnakeCaseEnum(); + builder.Property(value => value.Purpose).HasSnakeCaseEnum(); + builder.Property(value => value.Currency).HasMaxLength(10); + builder.Property(value => value.FeatureSnapshot).IsJson("[]"); + builder.Property(value => value.LimitSnapshot).IsJson("{}"); + builder.HasIndex(value => new { value.TenantId, value.QuoteNo }).IsUnique(); + builder.HasIndex(value => new { value.TenantId, value.IdempotencyKey }).IsUnique(); + builder.HasIndex(value => new { value.TenantId, value.Status, value.ExpiresAt }); + builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_quotes_amounts", "original_amount_cents >= 0 and discount_amount_cents >= 0 and total_amount_cents >= 0 and total_amount_cents = original_amount_cents - discount_amount_cents")); + } +} + +internal sealed class PlatformBillingQuoteItemConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureEntity("platform_billing_quote_items"); + builder.HasAlternateKey(value => new { value.TenantId, value.Id }); + builder.Property(value => value.ItemType).HasSnakeCaseEnum(); + builder.Property(value => value.Snapshot).IsJson("{}"); + builder.HasIndex(value => new { value.TenantId, value.QuoteId, value.OfferingVersionId }).IsUnique(); + builder.HasOne().WithMany().HasForeignKey(value => value.TenantId).OnDelete(DeleteBehavior.Cascade); + builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.QuoteId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Cascade); + builder.HasOne().WithMany().HasForeignKey(value => value.OfferingVersionId).OnDelete(DeleteBehavior.Restrict); + builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_quote_items_amounts", "quantity > 0 and unit_amount_cents >= 0 and amount_cents >= 0")); + } +} + +internal sealed class PlatformBillingOrderConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureTenantEntity("platform_billing_orders"); + builder.ConfigureTimestamps(); + builder.Property(value => value.OrderNo).HasMaxLength(100); + builder.Property(value => value.IdempotencyKey).HasMaxLength(200); + builder.Property(value => value.Purpose).HasSnakeCaseEnum(); + builder.Property(value => value.Status).HasSnakeCaseEnum(); + builder.Property(value => value.Currency).HasMaxLength(10); + builder.Property(value => value.Snapshot).IsJson("{}"); + builder.HasIndex(value => new { value.TenantId, value.OrderNo }).IsUnique(); + builder.HasIndex(value => new { value.TenantId, value.IdempotencyKey }).IsUnique(); + builder.HasIndex(value => new { value.TenantId, value.Status, value.CreatedAt }); + builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.QuoteId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Restrict); + builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_orders_amounts", "original_amount_cents >= 0 and discount_amount_cents >= 0 and total_amount_cents >= 0 and total_amount_cents = original_amount_cents - discount_amount_cents")); + } +} + +internal sealed class PlatformBillingOrderItemConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureEntity("platform_billing_order_items"); + builder.HasAlternateKey(value => new { value.TenantId, value.Id }); + builder.Property(value => value.ItemType).HasSnakeCaseEnum(); + builder.Property(value => value.Snapshot).IsJson("{}"); + builder.HasIndex(value => new { value.TenantId, value.OrderId, value.OfferingVersionId }).IsUnique(); + builder.HasOne().WithMany().HasForeignKey(value => value.TenantId).OnDelete(DeleteBehavior.Cascade); + builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.OrderId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Cascade); + builder.HasOne().WithMany().HasForeignKey(value => value.OfferingVersionId).OnDelete(DeleteBehavior.Restrict); + builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_order_items_amounts", "quantity > 0 and unit_amount_cents >= 0 and amount_cents >= 0")); + } +} + +internal sealed class PlatformBillingPaymentConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureTenantEntity("platform_billing_payments"); + builder.ConfigureTimestamps(); + builder.Property(value => value.PaymentNo).HasMaxLength(100); + builder.Property(value => value.IdempotencyKey).HasMaxLength(200); + builder.Property(value => value.Provider).HasMaxLength(50); + builder.Property(value => value.Method).HasMaxLength(50); + builder.Property(value => value.Status).HasSnakeCaseEnum(); + builder.Property(value => value.ProviderTradeNo).HasMaxLength(200); + builder.Property(value => value.ClientPayload).IsJson("{}"); + builder.HasIndex(value => new { value.TenantId, value.PaymentNo }).IsUnique(); + builder.HasIndex(value => new { value.TenantId, value.IdempotencyKey }).IsUnique(); + builder.HasIndex(value => new { value.TenantId, value.OrderId, value.Status }); + builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.OrderId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Cascade); + builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_payments_amount", "amount_cents >= 0")); + } +} + +internal sealed class PlatformBillingPaymentEventConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureEntity("platform_billing_payment_events"); + builder.HasAlternateKey(value => new { value.TenantId, value.Id }); + builder.Property(value => value.Provider).HasMaxLength(50); + builder.Property(value => value.ProviderEventId).HasMaxLength(200); + builder.Property(value => value.EventType).HasMaxLength(100); + builder.Property(value => value.Payload).IsJson("{}"); + builder.Property(value => value.CreatedAt).HasDefaultValueSql("now()"); + builder.HasIndex(value => new { value.TenantId, value.Provider, value.ProviderEventId }).IsUnique(); + builder.HasIndex(value => new { value.TenantId, value.PaymentId, value.CreatedAt }); + builder.HasOne().WithMany().HasForeignKey(value => value.TenantId).OnDelete(DeleteBehavior.Cascade); + builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.PaymentId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Cascade); + } +} + +internal sealed class PlatformBillingRefundConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureTenantEntity("platform_billing_refunds"); + builder.ConfigureTimestamps(); + builder.Property(value => value.RefundNo).HasMaxLength(100); + builder.Property(value => value.Status).HasSnakeCaseEnum(); + builder.Property(value => value.Reason).HasMaxLength(1000); + builder.Property(value => value.ProviderRefundNo).HasMaxLength(200); + builder.HasIndex(value => new { value.TenantId, value.RefundNo }).IsUnique(); + builder.HasIndex(value => new { value.TenantId, value.OrderId, value.Status }); + builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.OrderId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Restrict); + builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.PaymentId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Restrict); + builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_refunds_amount", "amount_cents > 0")); + } +} + +internal sealed class PlatformBillingInvoiceConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ConfigureTenantEntity("platform_billing_invoices"); + builder.ConfigureTimestamps(); + builder.Property(value => value.InvoiceNo).HasMaxLength(100); + builder.Property(value => value.Status).HasSnakeCaseEnum(); + builder.Property(value => value.Currency).HasMaxLength(10); + builder.Property(value => value.BillingProfileSnapshot).IsJson("{}"); + builder.HasIndex(value => new { value.TenantId, value.InvoiceNo }).IsUnique(); + builder.HasIndex(value => new { value.TenantId, value.Status, value.DueDate }); + builder.HasOne().WithMany().HasForeignKey(value => new { value.TenantId, value.OrderId }).HasPrincipalKey(value => new { value.TenantId, value.Id }).OnDelete(DeleteBehavior.Restrict); + builder.ToTable(table => table.HasCheckConstraint("ck_platform_billing_invoices_amount", "total_amount_cents >= 0")); + } +} diff --git a/Tiku.Infrastructure/Persistence/Configurations/TenancyConfigurations.cs b/Tiku.Infrastructure/Persistence/Configurations/TenancyConfigurations.cs index 2bc3c9b..bcc8169 100644 --- a/Tiku.Infrastructure/Persistence/Configurations/TenancyConfigurations.cs +++ b/Tiku.Infrastructure/Persistence/Configurations/TenancyConfigurations.cs @@ -123,6 +123,9 @@ internal sealed class TenantAuthPolicyConfiguration : IEntityTypeConfiguration entity.TenantId); builder.ConfigureTimestamps(); builder.Property(entity => entity.AllowExternalStudentSelfRegistration).HasDefaultValue(false); + builder.Property(entity => entity.AllowedStudentLoginMethods) + .HasColumnType("text[]") + .HasDefaultValueSql("ARRAY['password']::text[]"); builder.HasOne().WithOne() .HasForeignKey(entity => entity.TenantId) .OnDelete(DeleteBehavior.Cascade); diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260728031410_InitialSchema.Designer.cs b/Tiku.Infrastructure/Persistence/Migrations/20260728031410_InitialSchema.Designer.cs deleted file mode 100644 index b3b18ae..0000000 --- a/Tiku.Infrastructure/Persistence/Migrations/20260728031410_InitialSchema.Designer.cs +++ /dev/null @@ -1,17657 +0,0 @@ -// -using System; -using System.Text.Json; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Tiku.Infrastructure.Persistence; - -#nullable disable - -namespace Tiku.Infrastructure.Persistence.Migrations -{ - [DbContext(typeof(TikuDbContext))] - [Migration("20260728031410_InitialSchema")] - partial class InitialSchema - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.10") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "citext"); - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "ltree"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FriendlyName") - .HasColumnType("text") - .HasColumnName("friendly_name"); - - b.Property("Xml") - .HasColumnType("text") - .HasColumnName("xml"); - - b.HasKey("Id") - .HasName("pk_data_protection_keys"); - - b.ToTable("data_protection_keys", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text") - .HasColumnName("claim_type"); - - b.Property("ClaimValue") - .HasColumnType("text") - .HasColumnName("claim_value"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_claims"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_claims_user_id"); - - b.ToTable("user_claims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text") - .HasColumnName("login_provider"); - - b.Property("ProviderKey") - .HasColumnType("text") - .HasColumnName("provider_key"); - - b.Property("ProviderDisplayName") - .HasColumnType("text") - .HasColumnName("provider_display_name"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("LoginProvider", "ProviderKey") - .HasName("pk_user_logins"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_logins_user_id"); - - b.ToTable("user_logins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("LoginProvider") - .HasColumnType("text") - .HasColumnName("login_provider"); - - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); - - b.Property("Value") - .HasColumnType("text") - .HasColumnName("value"); - - b.HasKey("UserId", "LoginProvider", "Name") - .HasName("pk_user_tokens"); - - b.ToTable("user_tokens", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CategoryType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("category_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("SvipQuestionLimit") - .HasColumnType("integer") - .HasColumnName("svip_question_limit"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_categories"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_categories_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_categories_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_categories_tenant_id_node_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_categories_tenant_id_subject_id"); - - b.ToTable("categories", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Major", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StudyTips") - .HasColumnType("text") - .HasColumnName("study_tips"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_majors"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_majors_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_majors_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_majors_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_majors_tenant_id_school_id"); - - b.ToTable("majors", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ModuleNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyModuleId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_module_id"); - - b.Property("LegacyParentId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_parent_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("Path") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("path"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_module_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_module_nodes_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_module_nodes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_module_nodes_tenant_id_module_id"); - - b.HasIndex("TenantId", "ParentId") - .HasDatabaseName("ix_module_nodes_tenant_id_parent_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_module_nodes_tenant_id_region_id"); - - b.ToTable("module_nodes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.QuestionTaxonomyAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsPrimary") - .HasColumnType("boolean") - .HasColumnName("is_primary"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("TaxonomyNodeId") - .HasColumnType("uuid") - .HasColumnName("taxonomy_node_id"); - - b.Property("TaxonomyOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("taxonomy_owner_tenant_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_question_taxonomy_assignments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_taxonomy_assignments_tenant_id_id"); - - b.HasIndex("TaxonomyOwnerTenantId", "TaxonomyNodeId") - .HasDatabaseName("ix_question_taxonomy_assignments_taxonomy_owner_tenant_id_taxo~"); - - b.HasIndex("TenantId", "QuestionId", "TaxonomyOwnerTenantId", "TaxonomyNodeId") - .IsUnique() - .HasDatabaseName("ix_question_taxonomy_assignments_tenant_id_question_id_taxonom~"); - - b.ToTable("question_taxonomy_assignments", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Region", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("code"); - - b.Property("Config") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FullName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("full_name"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsHot") - .HasColumnType("boolean") - .HasColumnName("is_hot"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Pinyin") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("pinyin"); - - b.Property("ShortName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("short_name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_regions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_regions_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_regions_tenant_id_legacy_id"); - - b.ToTable("regions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.RegionModule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsPrimarySchoolModule") - .HasColumnType("boolean") - .HasColumnName("is_primary_school_module"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Route") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("route"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TextColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("text_color"); - - b.Property("Type") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_region_modules"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_region_modules_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_region_modules_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_region_modules_tenant_id_region_id"); - - b.ToTable("region_modules", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.School", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("ProfessionalExamDate") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("professional_exam_date"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_schools"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_schools_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_schools_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_schools_tenant_id_module_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_schools_tenant_id_region_id"); - - b.ToTable("schools", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineField", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("FieldKey") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("field_key"); - - b.Property("FieldName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("field_name"); - - b.Property("FieldType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("field_type"); - - b.Property("IsFilter") - .HasColumnType("boolean") - .HasColumnName("is_filter"); - - b.Property("IsRequired") - .HasColumnType("boolean") - .HasColumnName("is_required"); - - b.Property("IsTrend") - .HasColumnType("boolean") - .HasColumnName("is_trend"); - - b.Property("IsVisible") - .HasColumnType("boolean") - .HasColumnName("is_visible"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Options") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Placeholder") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("placeholder"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Unit") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("unit"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_scoreline_fields"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_scoreline_fields_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_scoreline_fields_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "FieldKey") - .IsUnique() - .HasDatabaseName("ix_scoreline_fields_tenant_id_region_id_field_key"); - - b.HasIndex("TenantId", "RegionId", "IsFilter", "SortOrder") - .HasDatabaseName("ix_scoreline_fields_tenant_id_region_id_is_filter_sort_order"); - - b.ToTable("scoreline_fields", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FieldValues") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("field_values") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("major_name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SchoolName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("school_name"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Year") - .HasColumnType("integer") - .HasColumnName("year"); - - b.HasKey("Id") - .HasName("pk_scoreline_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_scoreline_records_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_scoreline_records_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_scoreline_records_tenant_id_major_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_scoreline_records_tenant_id_school_id"); - - b.HasIndex("TenantId", "Year") - .HasDatabaseName("ix_scoreline_records_tenant_id_year"); - - b.HasIndex("TenantId", "RegionId", "SchoolId", "MajorId", "Year") - .HasDatabaseName("ix_scoreline_records_tenant_id_region_id_school_id_major_id_ye~"); - - b.ToTable("scoreline_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Subject", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorLegacyIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("major_legacy_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_subjects"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_subjects_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_subjects_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_subjects_tenant_id_major_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_subjects_tenant_id_module_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_subjects_tenant_id_node_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_subjects_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_subjects_tenant_id_school_id"); - - b.ToTable("subjects", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.TaxonomyNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Depth") - .HasColumnType("integer") - .HasColumnName("depth"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("node_type"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("ParentOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("parent_owner_tenant_id"); - - b.Property("Path") - .HasColumnType("ltree") - .HasColumnName("path"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_taxonomy_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_taxonomy_nodes_tenant_id_id"); - - b.HasIndex("ParentOwnerTenantId", "ParentId") - .HasDatabaseName("ix_taxonomy_nodes_parent_owner_tenant_id_parent_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_taxonomy_nodes_tenant_id_code"); - - b.ToTable("taxonomy_nodes", null, t => - { - t.HasCheckConstraint("ck_taxonomy_nodes_parent_pair", "(parent_owner_tenant_id is null) = (parent_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.ActivationCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AgentUserId") - .HasColumnType("uuid") - .HasColumnName("agent_user_id"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CouponCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("coupon_code"); - - b.Property("CouponRedemptionId") - .HasColumnType("uuid") - .HasColumnName("coupon_redemption_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("IsUsed") - .HasColumnType("boolean") - .HasColumnName("is_used"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("SoldTo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("sold_to"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitPriceCents") - .HasColumnType("integer") - .HasColumnName("unit_price_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("used_at"); - - b.Property("UsedBy") - .HasColumnType("uuid") - .HasColumnName("used_by"); - - b.Property("UsedRegionId") - .HasColumnType("uuid") - .HasColumnName("used_region_id"); - - b.HasKey("Id") - .HasName("pk_activation_codes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_activation_codes_tenant_id_id"); - - b.HasIndex("AgentUserId") - .HasDatabaseName("ix_activation_codes_agent_user_id"); - - b.HasIndex("UsedBy") - .HasDatabaseName("ix_activation_codes_used_by"); - - b.HasIndex("TenantId", "BatchId") - .HasDatabaseName("ix_activation_codes_tenant_id_batch_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_activation_codes_tenant_id_code"); - - b.HasIndex("TenantId", "CouponRedemptionId") - .HasDatabaseName("ix_activation_codes_tenant_id_coupon_redemption_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_activation_codes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UsedRegionId") - .HasDatabaseName("ix_activation_codes_tenant_id_used_region_id"); - - b.ToTable("activation_codes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CodeBatch", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CampaignName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("campaign_name"); - - b.Property("Channel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel"); - - b.Property("CommissionRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("commission_rate"); - - b.Property("CostPriceCents") - .HasColumnType("integer") - .HasColumnName("cost_price_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("DefaultUnitPriceCents") - .HasColumnType("integer") - .HasColumnName("default_unit_price_cents"); - - b.Property("IssuedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("issued_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_code_batches"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_code_batches_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_code_batches_tenant_id_legacy_id"); - - b.ToTable("code_batches", null, t => - { - t.HasCheckConstraint("ck_code_batches_amounts", "default_unit_price_cents >= 0 and cost_price_cents >= 0"); - - t.HasCheckConstraint("ck_code_batches_counts", "total_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationBatch", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BillDate") - .HasColumnType("date") - .HasColumnName("bill_date"); - - b.Property("BillType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("bill_type"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DuplicateCount") - .HasColumnType("integer") - .HasColumnName("duplicate_count"); - - b.Property("Error") - .HasColumnType("text") - .HasColumnName("error"); - - b.Property("FeeCents") - .HasColumnType("integer") - .HasColumnName("fee_cents"); - - b.Property("IgnoredCount") - .HasColumnType("integer") - .HasColumnName("ignored_count"); - - b.Property("MatchedCount") - .HasColumnType("integer") - .HasColumnName("matched_count"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MismatchCount") - .HasColumnType("integer") - .HasColumnName("mismatch_count"); - - b.Property("MissingLocalCount") - .HasColumnType("integer") - .HasColumnName("missing_local_count"); - - b.Property("MissingProviderCount") - .HasColumnType("integer") - .HasColumnName("missing_provider_count"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source"); - - b.Property("SourceHash") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("source_hash"); - - b.Property("SourceName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("source_name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_batches"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_batches_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_reconciliation_batches_created_by"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_batches_tenant_id_status_created_at"); - - b.HasIndex("TenantId", "Provider", "BillDate", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_batches_tenant_id_provider_bill_dat~"); - - b.ToTable("commerce_reconciliation_batches", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_batches_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commerce_reconciliation_batches_counts", "total_count >= 0 and matched_count >= 0 and mismatch_count >= 0 and missing_local_count >= 0 and missing_provider_count >= 0 and duplicate_count >= 0 and ignored_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("AssignedTo") - .HasColumnType("uuid") - .HasColumnName("assigned_to"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DueAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("due_at"); - - b.Property("IssueCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("IssueNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_no"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("MatchStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("match_status"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("OrderNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("RefundNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("ResolutionNote") - .HasColumnType("text") - .HasColumnName("resolution_note"); - - b.Property("ResolutionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("resolution_type"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("ResolvedBy") - .HasColumnType("uuid") - .HasColumnName("resolved_by"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("summary"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TransactionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("transaction_type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_issues_tenant_id_id"); - - b.HasIndex("AssignedTo") - .HasDatabaseName("ix_commerce_reconciliation_issues_assigned_to"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_reconciliation_issues_created_by"); - - b.HasIndex("ResolvedBy") - .HasDatabaseName("ix_commerce_reconciliation_issues_resolved_by"); - - b.HasIndex("TenantId", "IssueNo") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_issue_no"); - - b.HasIndex("TenantId", "ItemId") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_item_id") - .HasFilter("item_id is not null and status in ('open', 'investigating', 'escalated')"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_order_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "OrderNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_order_no_created_at"); - - b.HasIndex("TenantId", "PaymentId", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_payment_id_created~"); - - b.HasIndex("TenantId", "AssignedTo", "Status", "DueAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_assigned_to_status~"); - - b.HasIndex("TenantId", "BatchId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_batch_id_status_cr~"); - - b.HasIndex("TenantId", "Status", "Severity", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_status_severity_cr~"); - - b.ToTable("commerce_reconciliation_issues", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_issues_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssueEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("IssueId") - .HasColumnType("uuid") - .HasColumnName("issue_id"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_issue_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_issue_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_reconciliation_issue_events_actor_user_id"); - - b.HasIndex("TenantId", "IssueId", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issue_events_tenant_id_issue_id_cre~"); - - b.ToTable("commerce_reconciliation_issue_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FeeCents") - .HasColumnType("integer") - .HasColumnName("fee_cents"); - - b.Property("IssueCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("LocalStatus") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("local_status"); - - b.Property("MatchStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("match_status"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("OrderNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("ProviderStatus") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider_status"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("RefundNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("RefundedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("refunded_at"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TransactionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("transaction_type"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_items_tenant_id_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_order_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "BatchId", "RowNo") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_batch_id_row_no"); - - b.HasIndex("TenantId", "OrderNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_order_no_created_at"); - - b.HasIndex("TenantId", "BatchId", "MatchStatus", "RowNo") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_batch_id_match_stat~"); - - b.HasIndex("TenantId", "Provider", "ProviderTradeNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_provider_provider_t~"); - - b.HasIndex("TenantId", "RefundNo", "ProviderRefundNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_refund_no_provider_~"); - - b.ToTable("commerce_reconciliation_items", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_items_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commerce_reconciliation_items_row", "row_no > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_commerce_refund_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_refund_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_refund_events_actor_user_id"); - - b.HasIndex("TenantId", "RefundRequestId", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_events_tenant_id_refund_request_id_created_~"); - - b.ToTable("commerce_refund_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundRequest", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CancelledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("cancelled_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementAction") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("entitlement_action"); - - b.Property("FailedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("failed_at"); - - b.Property("FailureReason") - .HasColumnType("text") - .HasColumnName("failure_reason"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProcessedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("processed_at"); - - b.Property("ProcessedBy") - .HasColumnType("uuid") - .HasColumnName("processed_by"); - - b.Property("Provider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("Reason") - .HasColumnType("text") - .HasColumnName("reason"); - - b.Property("RefundNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RequestedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("requested_at") - .HasDefaultValueSql("now()"); - - b.Property("RequestedBy") - .HasColumnType("uuid") - .HasColumnName("requested_by"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SucceededAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("succeeded_at"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_refund_requests"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_refund_requests_tenant_id_id"); - - b.HasIndex("ProcessedBy") - .HasDatabaseName("ix_commerce_refund_requests_processed_by"); - - b.HasIndex("RequestedBy") - .HasDatabaseName("ix_commerce_refund_requests_requested_by"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commerce_refund_requests_reviewed_by"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundNo") - .IsUnique() - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_refund_no"); - - b.HasIndex("TenantId", "OrderId", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_order_id_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_status_created_at"); - - b.ToTable("commerce_refund_requests", null, t => - { - t.HasCheckConstraint("ck_commerce_refund_requests_amount", "amount_cents > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Coupon", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DiscountType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("discount_type"); - - b.Property("DiscountValue") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("discount_value"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MaxUses") - .HasColumnType("integer") - .HasColumnName("max_uses"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("Source") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property("ValidFrom") - .HasColumnType("timestamp with time zone") - .HasColumnName("valid_from"); - - b.Property("ValidTo") - .HasColumnType("timestamp with time zone") - .HasColumnName("valid_to"); - - b.HasKey("Id") - .HasName("pk_coupons"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_coupons_tenant_id_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_coupons_tenant_id_code"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_coupons_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_coupons_tenant_id_plan_id"); - - b.ToTable("coupons", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CouponRedemption", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClaimedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("claimed_at"); - - b.Property("CouponCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("coupon_code"); - - b.Property("CouponId") - .HasColumnType("uuid") - .HasColumnName("coupon_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DiscountAppliedCents") - .HasColumnType("integer") - .HasColumnName("discount_applied_cents"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("Source") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("used_at"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_coupon_redemptions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_coupon_redemptions_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_coupon_redemptions_user_id"); - - b.HasIndex("TenantId", "CouponId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_coupon_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_coupon_redemptions_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_plan_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_region_id"); - - b.HasIndex("TenantId", "OrderId", "UserId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_order_id_user_id") - .HasFilter("order_id is not null"); - - b.HasIndex("TenantId", "UserId", "CouponId") - .IsUnique() - .HasDatabaseName("ix_coupon_redemptions_tenant_id_user_id_coupon_id") - .HasFilter("coupon_id is not null"); - - b.ToTable("coupon_redemptions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Entitlement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementType") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("entitlement_type"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("LegacySourceId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_source_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RevokedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("revoked_at"); - - b.Property("RevokedBy") - .HasColumnType("uuid") - .HasColumnName("revoked_by"); - - b.Property("RevokedReason") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("revoked_reason"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("StartsAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at") - .HasDefaultValueSql("now()"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_entitlements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_entitlements_tenant_id_id"); - - b.HasIndex("RevokedBy") - .HasDatabaseName("ix_entitlements_revoked_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_entitlements_user_id"); - - b.HasIndex("TenantId", "UserId", "Status", "ExpiresAt") - .HasDatabaseName("ix_entitlements_tenant_id_user_id_status_expires_at"); - - b.ToTable("entitlements", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyPlanId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_plan_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("LegacyUserId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_user_id"); - - b.Property("OrderNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PayMethod") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("pay_method"); - - b.Property("PayProvider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("pay_provider"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("ProductName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("product_name"); - - b.Property("ProductType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("product_type"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefundedAmountCents") - .HasColumnType("integer") - .HasColumnName("refunded_amount_cents"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("trade_no"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_orders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_orders_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_orders_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_orders_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderNo") - .IsUnique() - .HasDatabaseName("ix_orders_tenant_id_order_no"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_orders_tenant_id_plan_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_orders_tenant_id_region_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_orders_tenant_id_user_id_created_at"); - - b.ToTable("orders", null, t => - { - t.HasCheckConstraint("ck_orders_amount", "amount_cents >= 0"); - - t.HasCheckConstraint("ck_orders_refunded_amount", "refunded_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("item_type"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("Quantity") - .HasColumnType("integer") - .HasColumnName("quantity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalAmountCents") - .HasColumnType("integer") - .HasColumnName("total_amount_cents"); - - b.Property("UnitAmountCents") - .HasColumnType("integer") - .HasColumnName("unit_amount_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_order_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_order_items_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_order_items_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_order_items_tenant_id_order_id"); - - b.ToTable("order_items", null, t => - { - t.HasCheckConstraint("ck_order_items_amounts", "unit_amount_cents >= 0 and total_amount_cents >= 0"); - - t.HasCheckConstraint("ck_order_items_quantity", "quantity > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Payment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyOrderId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_order_id"); - - b.Property("Method") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("method"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefundedAmountCents") - .HasColumnType("integer") - .HasColumnName("refunded_amount_cents"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_payments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_payments_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_payments_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderId", "UpdatedAt") - .HasDatabaseName("ix_payments_tenant_id_order_id_updated_at"); - - b.HasIndex("TenantId", "Provider", "ProviderTradeNo") - .IsUnique() - .HasDatabaseName("ix_payments_tenant_id_provider_provider_trade_no") - .HasFilter("provider_trade_no is not null"); - - b.ToTable("payments", null, t => - { - t.HasCheckConstraint("ck_payments_amount", "amount_cents >= 0"); - - t.HasCheckConstraint("ck_payments_refunded_amount", "refunded_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PaymentEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Error") - .HasColumnType("text") - .HasColumnName("error"); - - b.Property("EventId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("event_id"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProcessedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("processed_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("SignatureValid") - .HasColumnType("boolean") - .HasColumnName("signature_valid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_payment_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_payment_events_tenant_id_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_payment_events_tenant_id_payment_id"); - - b.HasIndex("TenantId", "Provider", "EventId") - .IsUnique() - .HasDatabaseName("ix_payment_events_tenant_id_provider_event_id"); - - b.ToTable("payment_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClaimedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("claimed_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TaskId") - .HasColumnType("uuid") - .HasColumnName("task_id"); - - b.Property("TaskKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("task_key"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_point_activity_claims"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_activity_claims_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_point_activity_claims_user_id"); - - b.HasIndex("TenantId", "TaskId") - .HasDatabaseName("ix_point_activity_claims_tenant_id_task_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_point_activity_claims_tenant_id_user_id_created_at"); - - b.HasIndex("TenantId", "UserId", "TaskId", "SourceType", "SourceId") - .IsUnique() - .HasDatabaseName("ix_point_activity_claims_tenant_id_user_id_task_id_source_type~") - .HasFilter("source_type is not null and source_id is not null"); - - b.ToTable("point_activity_claims", null, t => - { - t.HasCheckConstraint("ck_point_activity_claims_points", "points > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityTask", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("EndsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ends_at"); - - b.Property("MaxClaimsPerUser") - .HasColumnType("integer") - .HasColumnName("max_claims_per_user"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("Rules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TaskKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("task_key"); - - b.Property("TaskType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("task_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_point_activity_tasks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_activity_tasks_tenant_id_id"); - - b.HasIndex("TenantId", "TaskKey") - .IsUnique() - .HasDatabaseName("ix_point_activity_tasks_tenant_id_task_key"); - - b.HasIndex("TenantId", "Status", "SortOrder") - .HasDatabaseName("ix_point_activity_tasks_tenant_id_status_sort_order"); - - b.ToTable("point_activity_tasks", null, t => - { - t.HasCheckConstraint("ck_point_activity_tasks_max_claims", "max_claims_per_user > 0"); - - t.HasCheckConstraint("ck_point_activity_tasks_points", "points > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("EndsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ends_at"); - - b.Property("FulfillmentPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("fulfillment_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ItemKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("item_key"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("PointsCost") - .HasColumnType("integer") - .HasColumnName("points_cost"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Stock") - .HasColumnType("integer") - .HasColumnName("stock"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_point_exchange_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_exchange_items_tenant_id_id"); - - b.HasIndex("TenantId", "ItemKey") - .IsUnique() - .HasDatabaseName("ix_point_exchange_items_tenant_id_item_key"); - - b.HasIndex("TenantId", "RegionId", "Status", "SortOrder") - .HasDatabaseName("ix_point_exchange_items_tenant_id_region_id_status_sort_order"); - - b.ToTable("point_exchange_items", null, t => - { - t.HasCheckConstraint("ck_point_exchange_items_days", "days is null or days >= 0"); - - t.HasCheckConstraint("ck_point_exchange_items_points_cost", "points_cost > 0"); - - t.HasCheckConstraint("ck_point_exchange_items_stock", "stock is null or stock >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeOrder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CancelledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("cancelled_at"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FulfillmentSnapshot") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("fulfillment_snapshot") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("ItemName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("item_name"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("OrderedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("ordered_at") - .HasDefaultValueSql("now()"); - - b.Property("PointsCost") - .HasColumnType("integer") - .HasColumnName("points_cost"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_point_exchange_orders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_exchange_orders_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_point_exchange_orders_user_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_point_exchange_orders_tenant_id_item_id"); - - b.HasIndex("TenantId", "OrderNo") - .IsUnique() - .HasDatabaseName("ix_point_exchange_orders_tenant_id_order_no"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_point_exchange_orders_tenant_id_user_id_created_at"); - - b.ToTable("point_exchange_orders", null, t => - { - t.HasCheckConstraint("ck_point_exchange_orders_points_cost", "points_cost > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Cover") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cover"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DetailImages") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("detail_images") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true) - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyPriceText") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_price_text"); - - b.Property("Link") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("link"); - - b.Property("PreviewIframe") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_iframe"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_products"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_products_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_products_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "Type", "IsActive", "SortOrder") - .HasDatabaseName("ix_products_tenant_id_region_id_type_is_active_sort_order"); - - b.ToTable("products", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.SvipPlan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Badge") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("badge"); - - b.Property("CouponOnly") - .HasColumnType("boolean") - .HasColumnName("coupon_only"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OriginalPriceCents") - .HasColumnType("integer") - .HasColumnName("original_price_cents"); - - b.Property("PerDayLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("per_day_label"); - - b.Property("PriceCents") - .HasColumnType("integer") - .HasColumnName("price_cents"); - - b.Property("Recommended") - .HasColumnType("boolean") - .HasColumnName("recommended"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VpEnabled") - .HasColumnType("boolean") - .HasColumnName("vp_enabled"); - - b.Property("VpProductId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("vp_product_id"); - - b.HasKey("Id") - .HasName("pk_svip_plans"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_svip_plans_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_svip_plans_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_svip_plans_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("svip_plans", null, t => - { - t.HasCheckConstraint("ck_svip_plans_days", "days >= 0"); - - t.HasCheckConstraint("ck_svip_plans_price", "price_cents >= 0 and (original_price_cents is null or original_price_cents >= 0)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BillingCycle") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("billing_cycle"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PlanCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("plan_code"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_subscriptions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_subscriptions_tenant_id_id"); - - b.HasIndex("TenantId", "Status", "ExpiresAt") - .HasDatabaseName("ix_tenant_subscriptions_tenant_id_status_expires_at"); - - b.ToTable("tenant_subscriptions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MetricKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("metric_key"); - - b.Property("MetricValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)") - .HasColumnName("metric_value"); - - b.Property("PeriodEnd") - .HasColumnType("date") - .HasColumnName("period_end"); - - b.Property("PeriodStart") - .HasColumnType("date") - .HasColumnName("period_start"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_usage_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_usage_records_tenant_id_id"); - - b.HasIndex("TenantId", "MetricKey", "PeriodStart", "PeriodEnd") - .HasDatabaseName("ix_tenant_usage_records_tenant_id_metric_key_period_start_peri~"); - - b.ToTable("tenant_usage_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContextPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("context_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("GeneratedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("generated_at"); - - b.Property("InputPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("input_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Model") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("model"); - - b.Property("PromptVersion") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("prompt_version"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("ResultPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("result_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_ai_recommendation_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_ai_recommendation_reports_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_ai_recommendation_reports_user_id"); - - b.HasIndex("TenantId", "RegionId", "CreatedAt") - .HasDatabaseName("ix_ai_recommendation_reports_tenant_id_region_id_created_at"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_ai_recommendation_reports_tenant_id_user_id_created_at"); - - b.ToTable("ai_recommendation_reports", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AppAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssetKey") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("asset_key"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_app_assets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_app_assets_tenant_id_id"); - - b.HasIndex("TenantId", "AssetKey") - .IsUnique() - .HasDatabaseName("ix_app_assets_tenant_id_asset_key"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_app_assets_tenant_id_legacy_id"); - - b.ToTable("app_assets", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssetKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("asset_key"); - - b.Property("AssetType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("asset_type"); - - b.Property("Bucket") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("bucket"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("CdnUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cdn_url"); - - b.Property("ChecksumSha256") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("checksum_sha256"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("DownloadCount") - .HasColumnType("integer") - .HasColumnName("download_count"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("FileSizeBytes") - .HasColumnType("bigint") - .HasColumnName("file_size_bytes"); - - b.Property("IsPublic") - .HasColumnType("boolean") - .HasColumnName("is_public"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MimeType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("mime_type"); - - b.Property("ObjectKey") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("object_key"); - - b.Property("PreviewObjectKey") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("preview_object_key"); - - b.Property("PreviewStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("preview_status"); - - b.Property("PreviewUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_url"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SecurityFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("security_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SecurityScanProvider") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("security_scan_provider"); - - b.Property("SecurityScanStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("not_required") - .HasColumnName("security_scan_status"); - - b.Property("SecurityScanSummary") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("security_scan_summary") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SecurityScannedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("security_scanned_at"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StorageProvider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_provider"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("UploadStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("upload_status"); - - b.Property("VerificationDetails") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("verification_details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.Property("VerifiedBy") - .HasColumnType("uuid") - .HasColumnName("verified_by"); - - b.Property("VerifiedChecksumSha256") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("verified_checksum_sha256"); - - b.Property("VerifiedSizeBytes") - .HasColumnType("bigint") - .HasColumnName("verified_size_bytes"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_assets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_assets_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_assets_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_assets_updated_by"); - - b.HasIndex("VerifiedBy") - .HasDatabaseName("ix_content_assets_verified_by"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_content_assets_tenant_id_category_id"); - - b.HasIndex("TenantId", "ChecksumSha256") - .HasDatabaseName("ix_content_assets_tenant_id_checksum_sha256") - .HasFilter("checksum_sha256 is not null"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_content_assets_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_assets_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PreviewStatus") - .HasDatabaseName("ix_content_assets_tenant_id_preview_status") - .HasFilter("preview_status <> 'none'"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_content_assets_tenant_id_region_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_content_assets_tenant_id_subject_id"); - - b.HasIndex("TenantId", "StorageProvider", "Bucket", "ObjectKey") - .HasDatabaseName("ix_content_assets_tenant_id_storage_provider_bucket_object_key"); - - b.HasIndex("TenantId", "UploadStatus", "Status", "UpdatedAt") - .HasDatabaseName("ix_content_assets_tenant_id_upload_status_status_updated_at"); - - b.HasIndex("TenantId", "SecurityScanStatus", "UploadStatus", "Status", "UpdatedAt") - .HasDatabaseName("ix_content_assets_tenant_id_security_scan_status_upload_status~"); - - b.HasIndex("TenantId", "Status", "Visibility", "AssetType", "RegionId", "SubjectId", "CategoryId", "SortOrder") - .HasDatabaseName("ix_content_assets_tenant_id_status_visibility_asset_type_regio~"); - - b.ToTable("content_assets", null, t => - { - t.HasCheckConstraint("ck_content_assets_download_count", "download_count >= 0"); - - t.HasCheckConstraint("ck_content_assets_file_size", "file_size_bytes is null or file_size_bytes >= 0"); - - t.HasCheckConstraint("ck_content_assets_verified_checksum", "verified_checksum_sha256 is null or verified_checksum_sha256 ~ '^[a-f0-9]{64}$'"); - - t.HasCheckConstraint("ck_content_assets_verified_size", "verified_size_bytes is null or verified_size_bytes >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetAccessEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("access_type"); - - b.Property("ActorRole") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("actor_role"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("AssetType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("asset_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DenyCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("deny_code"); - - b.Property("Disposition") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("disposition"); - - b.Property("ExpiresInSeconds") - .HasColumnType("integer") - .HasColumnName("expires_in_seconds"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Result") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("result"); - - b.Property("SignatureMode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("signature_mode"); - - b.Property("StorageProvider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("storage_provider"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("Visibility") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_asset_access_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_asset_access_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_content_asset_access_events_user_id"); - - b.HasIndex("TenantId", "AssetId", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_asset_id_created_at"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_user_id_created_at") - .HasFilter("user_id is not null"); - - b.HasIndex("TenantId", "AccessType", "Result", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_access_type_result_cr~"); - - b.ToTable("content_asset_access_events", null, t => - { - t.HasCheckConstraint("ck_content_asset_access_events_expiry", "expires_in_seconds is null or expires_in_seconds > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetSecurityScanEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.PrimitiveCollection("IssueCodes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("issue_codes") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RiskLevel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("risk_level"); - - b.Property("ScanStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scan_status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_content_asset_security_scan_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_asset_security_scan_events_tenant_id_id"); - - b.HasIndex("TenantId", "AssetId", "CreatedAt") - .HasDatabaseName("ix_content_asset_security_scan_events_tenant_id_asset_id_creat~"); - - b.HasIndex("TenantId", "ScanStatus", "RiskLevel", "CreatedAt") - .HasDatabaseName("ix_content_asset_security_scan_events_tenant_id_scan_status_ri~"); - - b.ToTable("content_asset_security_scan_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("description"); - - b.Property("EntryKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("entry_key"); - - b.Property("EntryType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("entry_type"); - - b.Property("Icon") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LayoutConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("layout_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Route") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("route"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_entries"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_entries_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_entries_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_entries_updated_by"); - - b.HasIndex("TenantId", "EntryKey") - .IsUnique() - .HasDatabaseName("ix_content_entries_tenant_id_entry_key"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_entries_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "EntryType", "IsActive", "SortOrder") - .HasDatabaseName("ix_content_entries_tenant_id_region_id_entry_type_is_active_so~"); - - b.ToTable("content_entries", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FieldPath") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("field_path"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("JobId") - .HasColumnType("uuid") - .HasColumnName("job_id"); - - b.Property("Message") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("message"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_content_import_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_issues_tenant_id_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_content_import_issues_tenant_id_item_id"); - - b.HasIndex("TenantId", "JobId", "Severity", "RowNo") - .HasDatabaseName("ix_content_import_issues_tenant_id_job_id_severity_row_no"); - - b.ToTable("content_import_issues", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("content_hash"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExternalId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("external_id"); - - b.Property("IssuesCount") - .HasColumnType("integer") - .HasColumnName("issues_count"); - - b.Property("JobId") - .HasColumnType("uuid") - .HasColumnName("job_id"); - - b.Property("NormalizedPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("normalized_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("SourcePayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("source_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TargetId") - .HasColumnType("uuid") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_content_import_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_items_tenant_id_id"); - - b.HasIndex("TenantId", "JobId", "RowNo") - .IsUnique() - .HasDatabaseName("ix_content_import_items_tenant_id_job_id_row_no"); - - b.HasIndex("TenantId", "JobId", "Status", "RowNo") - .HasDatabaseName("ix_content_import_items_tenant_id_job_id_status_row_no"); - - b.ToTable("content_import_items", null, t => - { - t.HasCheckConstraint("ck_content_import_items_issues_count", "issues_count >= 0"); - - t.HasCheckConstraint("ck_content_import_items_row_no", "row_no >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DryRun") - .HasColumnType("boolean") - .HasColumnName("dry_run"); - - b.Property("ErrorCount") - .HasColumnType("integer") - .HasColumnName("error_count"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("ImportType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("import_type"); - - b.Property("InsertedCount") - .HasColumnType("integer") - .HasColumnName("inserted_count"); - - b.Property("NormalizedPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("normalized_payload") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SkippedCount") - .HasColumnType("integer") - .HasColumnName("skipped_count"); - - b.Property("SourceFormat") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_format"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SourceName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("source_name"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("summary") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TargetCategoryId") - .HasColumnType("uuid") - .HasColumnName("target_category_id"); - - b.Property("TargetContentNodeId") - .HasColumnType("uuid") - .HasColumnName("target_content_node_id"); - - b.Property("TargetQuestionBankId") - .HasColumnType("uuid") - .HasColumnName("target_question_bank_id"); - - b.Property("TargetRegionId") - .HasColumnType("uuid") - .HasColumnName("target_region_id"); - - b.Property("TargetSubjectId") - .HasColumnType("uuid") - .HasColumnName("target_subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedCount") - .HasColumnType("integer") - .HasColumnName("updated_count"); - - b.Property("ValidCount") - .HasColumnType("integer") - .HasColumnName("valid_count"); - - b.Property("WarningCount") - .HasColumnType("integer") - .HasColumnName("warning_count"); - - b.HasKey("Id") - .HasName("pk_content_import_jobs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_jobs_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_import_jobs_created_by"); - - b.HasIndex("TenantId", "TargetCategoryId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_category_id"); - - b.HasIndex("TenantId", "TargetContentNodeId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_content_node_id"); - - b.HasIndex("TenantId", "TargetQuestionBankId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_question_bank_id"); - - b.HasIndex("TenantId", "TargetRegionId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_region_id"); - - b.HasIndex("TenantId", "TargetSubjectId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_subject_id"); - - b.HasIndex("TenantId", "ImportType", "Status", "CreatedAt") - .HasDatabaseName("ix_content_import_jobs_tenant_id_import_type_status_created_at"); - - b.ToTable("content_import_jobs", null, t => - { - t.HasCheckConstraint("ck_content_import_jobs_counts", "total_count >= 0 and valid_count >= 0 and error_count >= 0 and warning_count >= 0 and inserted_count >= 0 and updated_count >= 0 and skipped_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Depth") - .HasColumnType("integer") - .HasColumnName("depth"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsLeaf") - .HasColumnType("boolean") - .HasColumnName("is_leaf"); - - b.Property("IsSelectable") - .HasColumnType("boolean") - .HasColumnName("is_selectable"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MarkerConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("marker_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MarkerType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("marker_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("node_key"); - - b.Property("NodeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("node_type"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("Path") - .HasColumnType("ltree") - .HasColumnName("path"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_content_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_nodes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_nodes_created_by"); - - b.HasIndex("Path") - .HasDatabaseName("ix_content_nodes_path"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Path"), "gist"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_nodes_updated_by"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_nodes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MarkerType") - .HasDatabaseName("ix_content_nodes_tenant_id_marker_type") - .HasFilter("marker_type is not null"); - - b.HasIndex("TenantId", "ParentId") - .HasDatabaseName("ix_content_nodes_tenant_id_parent_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_content_nodes_tenant_id_region_id"); - - b.HasIndex("TenantId", "EntryId", "NodeKey") - .IsUnique() - .HasDatabaseName("ix_content_nodes_tenant_id_entry_id_node_key"); - - b.HasIndex("TenantId", "EntryId", "ParentId", "SortOrder") - .HasDatabaseName("ix_content_nodes_tenant_id_entry_id_parent_id_sort_order"); - - b.ToTable("content_nodes", null, t => - { - t.HasCheckConstraint("ck_content_nodes_depth", "depth >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookChapter", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_chapters"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_chapters_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_chapters_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_chapters_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_handbook_chapters_tenant_id_subject_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_chapters_tenant_id_entry_id_content_node_id_subjec~"); - - b.ToTable("handbook_chapters", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ChapterId") - .HasColumnType("uuid") - .HasColumnName("chapter_id"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("Summary") - .HasColumnType("text") - .HasColumnName("summary"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_entries"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_entries_tenant_id_id"); - - b.HasIndex("TenantId", "ChapterId") - .HasDatabaseName("ix_handbook_entries_tenant_id_chapter_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_entries_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_entries_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "ChapterId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_entries_tenant_id_entry_id_content_node_id_chapter~"); - - b.ToTable("handbook_entries", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookSubject", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorLegacyIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("major_legacy_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_subjects"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_subjects_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_subjects_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_major_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_school_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_subjects_tenant_id_entry_id_content_node_id_region~"); - - b.ToTable("handbook_subjects", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ImageAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("CdnUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cdn_url"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("IsPublic") - .HasColumnType("boolean") - .HasColumnName("is_public"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_images"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_images_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_images_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "Category", "IsPublic") - .HasDatabaseName("ix_images_tenant_id_category_is_public"); - - b.ToTable("images", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.PracticeBlueprint", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssemblyType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assembly_type"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("mode"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("PassScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("pass_score"); - - b.Property("QuestionLimit") - .HasColumnType("integer") - .HasColumnName("question_limit"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Rules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Sections") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("sections") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_practice_blueprints"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_blueprints_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_practice_blueprints_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_practice_blueprints_updated_by"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_collection_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_practice_blueprints_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_region_id"); - - b.HasIndex("TenantId", "NodeId", "CollectionId", "Mode", "Status", "SortOrder") - .HasDatabaseName("ix_practice_blueprints_tenant_id_node_id_collection_id_mode_st~"); - - b.ToTable("practice_blueprints", null, t => - { - t.HasCheckConstraint("ck_practice_blueprints_duration", "duration_minutes is null or duration_minutes > 0"); - - t.HasCheckConstraint("ck_practice_blueprints_question_limit", "question_limit is null or question_limit > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("CollectionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("collection_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Filters") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("filters") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_question_collections"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_collections_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_question_collections_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_question_collections_updated_by"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_question_collections_tenant_id_category_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_question_collections_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_collections_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "QuestionBankId") - .HasDatabaseName("ix_question_collections_tenant_id_question_bank_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_question_collections_tenant_id_region_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_question_collections_tenant_id_subject_id"); - - b.HasIndex("TenantId", "NodeId", "Status", "SortOrder") - .HasDatabaseName("ix_question_collections_tenant_id_node_id_status_sort_order"); - - b.ToTable("question_collections", null, t => - { - t.HasCheckConstraint("ck_question_collections_duration", "duration_minutes is null or duration_minutes > 0"); - - t.HasCheckConstraint("ck_question_collections_question_count", "question_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollectionItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("Required") - .HasColumnType("boolean") - .HasColumnName("required"); - - b.Property("Score") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("score"); - - b.Property("SectionKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("section_key"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_collection_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_collection_items_tenant_id_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_question_collection_items_question_owner_tenant_id_question~"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_question_collection_items_tenant_id_question_reference_id"); - - b.HasIndex("TenantId", "CollectionId", "QuestionReferenceId") - .IsUnique() - .HasDatabaseName("ix_question_collection_items_tenant_id_collection_id_question_~"); - - b.HasIndex("TenantId", "CollectionId", "SectionKey", "SortOrder") - .HasDatabaseName("ix_question_collection_items_tenant_id_collection_id_section_k~"); - - b.ToTable("question_collection_items", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionTypeGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("display_name"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Types") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("types") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_type_groups"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_type_groups_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_type_groups_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_question_type_groups_tenant_id_subject_id_is_active_sort_or~"); - - b.ToTable("question_type_groups", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionVideo", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyQuestionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_question_id"); - - b.Property("LegacyVideoId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_video_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VideoId") - .HasColumnType("uuid") - .HasColumnName("video_id"); - - b.Property("VideoType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("video_type"); - - b.HasKey("Id") - .HasName("pk_question_videos"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_videos_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_videos_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "VideoId") - .HasDatabaseName("ix_question_videos_tenant_id_video_id"); - - b.HasIndex("TenantId", "QuestionId", "VideoId", "VideoType") - .IsUnique() - .HasDatabaseName("ix_question_videos_tenant_id_question_id_video_id_video_type"); - - b.ToTable("question_videos", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.SubjectShare", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("SourceSubjectId") - .HasColumnType("uuid") - .HasColumnName("source_subject_id"); - - b.Property("TargetSubjectId") - .HasColumnType("uuid") - .HasColumnName("target_subject_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Id") - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacySourceSubjectId") - .HasColumnType("text") - .HasColumnName("legacy_source_subject_id"); - - b.Property("LegacyTargetSubjectId") - .HasColumnType("text") - .HasColumnName("legacy_target_subject_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("TenantId", "SourceSubjectId", "TargetSubjectId") - .HasName("pk_subject_shares"); - - b.HasIndex("TenantId", "TargetSubjectId") - .HasDatabaseName("ix_subject_shares_tenant_id_target_subject_id"); - - b.ToTable("subject_shares", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionBankPreference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Alias") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("alias"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("IsVisible") - .HasColumnType("boolean") - .HasColumnName("is_visible"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NavigationLocation") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("navigation_location"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("QuestionBankOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_bank_owner_tenant_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_question_bank_preferences"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_question_bank_preferences_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_question_bank_preferences_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_question_bank_preferences_updated_by"); - - b.HasIndex("QuestionBankOwnerTenantId", "QuestionBankId") - .HasDatabaseName("ix_tenant_question_bank_preferences_question_bank_owner_tenant~"); - - b.HasIndex("TenantId", "QuestionBankOwnerTenantId", "QuestionBankId") - .IsUnique() - .HasDatabaseName("ix_tenant_question_bank_preferences_tenant_id_question_bank_ow~"); - - b.ToTable("tenant_question_bank_preferences", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionReference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_question_references"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_question_references_tenant_id_id"); - - b.HasAlternateKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasName("ak_tenant_question_references_tenant_id_question_owner_tenant_~"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_question_references_created_by"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_tenant_question_references_question_owner_tenant_id_questio~"); - - b.ToTable("tenant_question_references", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordFavorite", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FavoritedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("favorited_at"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacyUserId") - .HasColumnType("text") - .HasColumnName("legacy_user_id"); - - b.Property("LegacyWordId") - .HasColumnType("text") - .HasColumnName("legacy_word_id"); - - b.Property("Note") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WordId") - .HasColumnType("uuid") - .HasColumnName("word_id"); - - b.HasKey("Id") - .HasName("pk_user_word_favorites"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_word_favorites_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_word_favorites_user_id"); - - b.HasIndex("TenantId", "WordId") - .HasDatabaseName("ix_user_word_favorites_tenant_id_word_id"); - - b.HasIndex("TenantId", "UserId", "WordId") - .IsUnique() - .HasDatabaseName("ix_user_word_favorites_tenant_id_user_id_word_id"); - - b.ToTable("user_word_favorites", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordProgress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CorrectStreak") - .HasColumnType("integer") - .HasColumnName("correct_streak"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DueLevel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("due_level"); - - b.Property("EaseFactor") - .ValueGeneratedOnAdd() - .HasPrecision(4, 2) - .HasColumnType("numeric(4,2)") - .HasDefaultValue(2.50m) - .HasColumnName("ease_factor"); - - b.Property("LastResult") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("last_result"); - - b.Property("LastReviewAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_review_at"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacyUserId") - .HasColumnType("text") - .HasColumnName("legacy_user_id"); - - b.Property("LegacyWordId") - .HasColumnType("text") - .HasColumnName("legacy_word_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NextReviewAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_review_at"); - - b.Property("ReviewCount") - .HasColumnType("integer") - .HasColumnName("review_count"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WordId") - .HasColumnType("uuid") - .HasColumnName("word_id"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.HasKey("Id") - .HasName("pk_user_word_progress"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_word_progress_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_word_progress_user_id"); - - b.HasIndex("TenantId", "WordId") - .HasDatabaseName("ix_user_word_progress_tenant_id_word_id"); - - b.HasIndex("TenantId", "UserId", "WordId") - .IsUnique() - .HasDatabaseName("ix_user_word_progress_tenant_id_user_id_word_id"); - - b.HasIndex("TenantId", "UserId", "NextReviewAt", "Status", "DueLevel") - .HasDatabaseName("ix_user_word_progress_tenant_id_user_id_next_review_at_status_~"); - - b.ToTable("user_word_progress", null, t => - { - t.HasCheckConstraint("ck_user_word_progress_correct_streak", "correct_streak >= 0"); - - t.HasCheckConstraint("ck_user_word_progress_ease_factor", "ease_factor >= 1.30 and ease_factor <= 3.00"); - - t.HasCheckConstraint("ck_user_word_progress_review_count", "review_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoExplanation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("DurationSeconds") - .HasColumnType("integer") - .HasColumnName("duration_seconds"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsGeneral") - .HasColumnType("boolean") - .HasColumnName("is_general"); - - b.Property("KnowledgeTags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("knowledge_tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacySubjectId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_subject_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ThumbnailUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("thumbnail_url"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VideoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("video_url"); - - b.HasKey("Id") - .HasName("pk_video_explanations"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_video_explanations_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_video_explanations_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SourceHash") - .HasDatabaseName("ix_video_explanations_tenant_id_source_hash") - .HasFilter("source_hash is not null"); - - b.HasIndex("TenantId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_video_explanations_tenant_id_subject_id_is_active_sort_order"); - - b.ToTable("video_explanations", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyUnit", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WordCount") - .HasColumnType("integer") - .HasColumnName("word_count"); - - b.HasKey("Id") - .HasName("pk_vocabulary_units"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_vocabulary_units_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_vocabulary_units_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_vocabulary_units_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_vocabulary_units_tenant_id_region_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_vocabulary_units_tenant_id_entry_id_content_node_id_region_~"); - - b.ToTable("vocabulary_units", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyWord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Example") - .HasColumnType("text") - .HasColumnName("example"); - - b.Property("ExampleTranslation") - .HasColumnType("text") - .HasColumnName("example_translation"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Meaning") - .HasColumnType("text") - .HasColumnName("meaning"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Phonetic") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("phonetic"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitId") - .HasColumnType("uuid") - .HasColumnName("unit_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Word") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("word"); - - b.HasKey("Id") - .HasName("pk_vocabulary_words"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_vocabulary_words_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_vocabulary_words_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_vocabulary_words_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UnitId", "IsActive", "SortOrder", "CreatedAt") - .HasDatabaseName("ix_vocabulary_words_tenant_id_unit_id_is_active_sort_order_cre~"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "UnitId", "IsActive", "SortOrder") - .HasDatabaseName("ix_vocabulary_words_tenant_id_entry_id_content_node_id_unit_id~"); - - b.ToTable("vocabulary_words", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CommissionAmountCents") - .HasColumnType("integer") - .HasColumnName("commission_amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DefaultRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("default_rate"); - - b.Property("EffectiveRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("effective_rate"); - - b.Property("GeneratedBy") - .HasColumnType("uuid") - .HasColumnName("generated_by"); - - b.Property("GrossAmountCents") - .HasColumnType("integer") - .HasColumnName("gross_amount_cents"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaidBy") - .HasColumnType("uuid") - .HasColumnName("paid_by"); - - b.Property("PaidUserCount") - .HasColumnType("integer") - .HasColumnName("paid_user_count"); - - b.Property("PaymentAccount") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("payment_account"); - - b.Property("PaymentMethod") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_method"); - - b.Property("PeriodEnd") - .HasColumnType("date") - .HasColumnName("period_end"); - - b.Property("PeriodStart") - .HasColumnType("date") - .HasColumnName("period_start"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("SettlementNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("settlement_no"); - - b.Property("SourceCount") - .HasColumnType("integer") - .HasColumnName("source_count"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlements_tenant_id_id"); - - b.HasIndex("GeneratedBy") - .HasDatabaseName("ix_commission_settlements_generated_by"); - - b.HasIndex("PaidBy") - .HasDatabaseName("ix_commission_settlements_paid_by"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_commission_settlements_referrer_user_id"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commission_settlements_reviewed_by"); - - b.HasIndex("TenantId", "SettlementNo") - .IsUnique() - .HasDatabaseName("ix_commission_settlements_tenant_id_settlement_no"); - - b.HasIndex("TenantId", "ReferrerUserId", "PeriodStart") - .HasDatabaseName("ix_commission_settlements_referrer_start"); - - b.HasIndex("TenantId", "ReferrerUserId", "PeriodStart", "PeriodEnd") - .IsUnique() - .HasDatabaseName("ux_commission_settlements_referrer_period"); - - b.ToTable("commission_settlements", null, t => - { - t.HasCheckConstraint("ck_commission_settlements_amounts", "gross_amount_cents >= 0 and commission_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commission_settlements_counts", "source_count >= 0 and paid_user_count >= 0"); - - t.HasCheckConstraint("ck_commission_settlements_period", "period_end >= period_start"); - - t.HasCheckConstraint("ck_commission_settlements_rates", "default_rate >= 0 and default_rate <= 1 and (effective_rate is null or (effective_rate >= 0 and effective_rate <= 1))"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementExportEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentSha256") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("content_sha256"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExportFormat") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasColumnName("export_format"); - - b.Property("ExportedBy") - .HasColumnType("uuid") - .HasColumnName("exported_by"); - - b.Property("Filename") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("filename"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RowCount") - .HasColumnType("integer") - .HasColumnName("row_count"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_export_events"); - - b.HasIndex("ExportedBy") - .HasDatabaseName("ix_commission_settlement_export_events_exported_by"); - - b.HasIndex("TenantId", "SettlementId", "CreatedAt") - .HasDatabaseName("ix_commission_settlement_export_events_tenant_id_settlement_id~"); - - b.ToTable("commission_settlement_export_events", null, t => - { - t.HasCheckConstraint("ck_commission_export_events_rows", "row_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AttributionType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("attribution_type"); - - b.Property("CommissionAmountCents") - .HasColumnType("integer") - .HasColumnName("commission_amount_cents"); - - b.Property("CommissionRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("commission_rate"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("GrossAmountCents") - .HasColumnType("integer") - .HasColumnName("gross_amount_cents"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RateSource") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("rate_source"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_no"); - - b.Property("SourcePaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("source_paid_at"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_type"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlement_items_tenant_id_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_commission_settlement_items_referrer_user_id"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_commission_settlement_items_student_user_id"); - - b.HasIndex("TenantId", "SettlementId") - .HasDatabaseName("ix_commission_settlement_items_tenant_id_settlement_id"); - - b.HasIndex("TenantId", "ReferrerUserId", "SourcePaidAt") - .HasDatabaseName("ix_commission_settlement_items_tenant_id_referrer_user_id_sour~"); - - b.HasIndex("TenantId", "SourceType", "SourceId") - .IsUnique() - .HasDatabaseName("ix_commission_settlement_items_tenant_id_source_type_source_id"); - - b.ToTable("commission_settlement_items", null, t => - { - t.HasCheckConstraint("ck_commission_settlement_items_amounts", "gross_amount_cents >= 0 and commission_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commission_settlement_items_rate", "commission_rate >= 0 and commission_rate <= 1"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementProof", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("ExternalUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("external_url"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentAccount") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("payment_account"); - - b.Property("PaymentMethod") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_method"); - - b.Property("ProofType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("proof_type"); - - b.Property("ReviewNote") - .HasColumnType("text") - .HasColumnName("review_note"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubmittedBy") - .HasColumnType("uuid") - .HasColumnName("submitted_by"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_proofs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlement_proofs_tenant_id_id"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commission_settlement_proofs_reviewed_by"); - - b.HasIndex("SubmittedBy") - .HasDatabaseName("ix_commission_settlement_proofs_submitted_by"); - - b.HasIndex("TenantId", "AssetId") - .HasDatabaseName("ix_commission_settlement_proofs_tenant_id_asset_id"); - - b.HasIndex("TenantId", "SettlementId", "Status", "CreatedAt") - .HasDatabaseName("ix_commission_settlement_proofs_tenant_id_settlement_id_status~"); - - b.ToTable("commission_settlement_proofs", null, t => - { - t.HasCheckConstraint("ck_commission_settlement_proofs_amount", "amount_cents is null or amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignmentConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("assignment_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssignmentCursor") - .HasColumnType("integer") - .HasColumnName("assignment_cursor"); - - b.Property("AssignmentMode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assignment_mode"); - - b.Property("AssignmentPool") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("assignment_pool") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DelaySeconds") - .HasColumnType("integer") - .HasColumnName("delay_seconds"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("ExamType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("exam_type"); - - b.Property("FormName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("form_name"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TimeoutSeconds") - .HasColumnType("integer") - .HasColumnName("timeout_seconds"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Url") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_crm_config"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_config_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_crm_config_tenant_id"); - - b.HasIndex("TenantId", "AssignmentMode") - .HasDatabaseName("ix_crm_config_tenant_id_assignment_mode"); - - b.ToTable("crm_config", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempt") - .HasColumnType("integer") - .HasColumnName("attempt"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("HttpCode") - .HasColumnType("integer") - .HasColumnName("http_code"); - - b.Property("LeadId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Outcome") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("outcome"); - - b.Property("RecordId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("record_id"); - - b.Property("RequestBody") - .HasColumnType("text") - .HasColumnName("request_body"); - - b.Property("RequestPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("request_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ResponseSummary") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("response_summary"); - - b.Property("SignedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("signed_at"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_crm_webhook_log"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_webhook_log_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_log_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RecordId", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_log_tenant_id_record_id_created_at"); - - b.ToTable("crm_webhook_log", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookQueueItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IdempotencyKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("idempotency_key"); - - b.Property("LastAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_attempt_at"); - - b.Property("LastError") - .HasColumnType("text") - .HasColumnName("last_error"); - - b.Property("LastHttpCode") - .HasColumnType("integer") - .HasColumnName("last_http_code"); - - b.Property("LastResponseSummary") - .HasColumnType("text") - .HasColumnName("last_response_summary"); - - b.Property("LeadId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("NextAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_attempt_at"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Provider") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RecordId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("record_id"); - - b.Property("ScheduledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("scheduled_at"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Source") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TargetUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("target_url"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_crm_webhook_queue"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_webhook_queue_tenant_id_id"); - - b.HasIndex("TenantId", "IdempotencyKey") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_idempotency_key") - .HasFilter("idempotency_key is not null"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RecordId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_record_id") - .HasFilter("record_id is not null"); - - b.HasIndex("TenantId", "RecordId", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_record_id_created_at") - .HasFilter("record_id is not null"); - - b.HasIndex("TenantId", "Source", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_source_created_at") - .HasFilter("source = 'tenant.student.crm_push'"); - - b.HasIndex("TenantId", "Status", "ScheduledAt", "NextAttemptAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_status_scheduled_at_next_attemp~"); - - b.ToTable("crm_webhook_queue", null, t => - { - t.HasCheckConstraint("ck_crm_webhook_queue_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Channel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LandingPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("landing_path"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_referral_codes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_codes_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_referral_codes_user_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_referral_codes_tenant_id_code"); - - b.HasIndex("TenantId", "UserId") - .IsUnique() - .HasDatabaseName("ix_referral_codes_tenant_id_user_id"); - - b.HasIndex("TenantId", "Code", "Status") - .HasDatabaseName("ix_referral_codes_tenant_id_code_status"); - - b.ToTable("referral_codes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralLead", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("assigned_at"); - - b.Property("AssignedBy") - .HasColumnType("uuid") - .HasColumnName("assigned_by"); - - b.Property("AssignedToUserId") - .HasColumnType("uuid") - .HasColumnName("assigned_to_user_id"); - - b.Property("AssignmentMode") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assignment_mode"); - - b.Property("BindType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("bind_type"); - - b.Property("BoundAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("bound_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FirstTrackId") - .HasColumnType("uuid") - .HasColumnName("first_track_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ProtectedUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("protected_until"); - - b.Property("RefCode") - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Source") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_referral_leads"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_leads_tenant_id_id"); - - b.HasIndex("AssignedBy") - .HasDatabaseName("ix_referral_leads_assigned_by"); - - b.HasIndex("AssignedToUserId") - .HasDatabaseName("ix_referral_leads_assigned_to_user_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_referral_leads_referrer_user_id"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_referral_leads_student_user_id"); - - b.HasIndex("TenantId", "FirstTrackId") - .HasDatabaseName("ix_referral_leads_tenant_id_first_track_id"); - - b.HasIndex("TenantId", "StudentUserId") - .IsUnique() - .HasDatabaseName("ix_referral_leads_tenant_id_student_user_id"); - - b.HasIndex("TenantId", "ReferrerUserId", "BoundAt") - .HasDatabaseName("ix_referral_leads_tenant_id_referrer_user_id_bound_at"); - - b.HasIndex("TenantId", "AssignedToUserId", "Status", "BoundAt") - .HasDatabaseName("ix_referral_leads_tenant_id_assigned_to_user_id_status_bound_at"); - - b.ToTable("referral_leads", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralQrcode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Page") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("page"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("QrcodeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("qrcode_url"); - - b.Property("RefCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("Scene") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("scene"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_referral_qrcodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_qrcodes_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_referral_qrcodes_user_id"); - - b.HasIndex("TenantId", "RefCode", "Status") - .HasDatabaseName("ix_referral_qrcodes_tenant_id_ref_code_status"); - - b.HasIndex("TenantId", "Provider", "Scene", "Page") - .IsUnique() - .HasDatabaseName("ix_referral_qrcodes_tenant_id_provider_scene_page"); - - b.ToTable("referral_qrcodes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTeamEdge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LeaderUserId") - .HasColumnType("uuid") - .HasColumnName("leader_user_id"); - - b.Property("MemberUserId") - .HasColumnType("uuid") - .HasColumnName("member_user_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RelationType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("relation_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_referral_team_edges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_team_edges_tenant_id_id"); - - b.HasIndex("LeaderUserId") - .HasDatabaseName("ix_referral_team_edges_leader_user_id"); - - b.HasIndex("MemberUserId") - .HasDatabaseName("ix_referral_team_edges_member_user_id"); - - b.HasIndex("TenantId", "LeaderUserId", "Status") - .HasDatabaseName("ix_referral_team_edges_tenant_id_leader_user_id_status"); - - b.HasIndex("TenantId", "MemberUserId", "RelationType") - .IsUnique() - .HasDatabaseName("ix_referral_team_edges_tenant_id_member_user_id_relation_type"); - - b.ToTable("referral_team_edges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTrack", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("LeadId") - .HasColumnType("uuid") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefCode") - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Source") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source"); - - b.Property("TargetUserId") - .HasColumnType("uuid") - .HasColumnName("target_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_referral_tracks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_tracks_tenant_id_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_referral_tracks_referrer_user_id"); - - b.HasIndex("TargetUserId") - .HasDatabaseName("ix_referral_tracks_target_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_referral_tracks_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "LeadId", "CreatedAt") - .HasDatabaseName("ix_referral_tracks_tenant_id_lead_id_created_at"); - - b.HasIndex("TenantId", "RefCode", "EventType", "CreatedAt") - .HasDatabaseName("ix_referral_tracks_tenant_id_ref_code_event_type_created_at"); - - b.ToTable("referral_tracks", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.TenantCommissionSetting", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Config") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DefaultRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("default_rate"); - - b.Property("MinSettlementCents") - .HasColumnType("integer") - .HasColumnName("min_settlement_cents"); - - b.Property("SettlementCycle") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("settlement_cycle"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_commission_settings"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_commission_settings_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_commission_settings_tenant_id"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_commission_settings_updated_by"); - - b.ToTable("tenant_commission_settings", null, t => - { - t.HasCheckConstraint("ck_tenant_commission_settings_default_rate", "default_rate >= 0 and default_rate <= 1"); - - t.HasCheckConstraint("ck_tenant_commission_settings_min_settlement", "min_settlement_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.StudentProfile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AvatarPreset") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("male") - .HasColumnName("avatar_preset"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LastCheckInDate") - .HasColumnType("date") - .HasColumnName("last_check_in_date"); - - b.Property("LegacyUserId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_user_id"); - - b.Property("MasteredWordsCount") - .HasColumnType("integer") - .HasColumnName("mastered_words_count"); - - b.Property("ModuleSelections") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("module_selections") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Progress") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("progress") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionsAnsweredToday") - .HasColumnType("integer") - .HasColumnName("questions_answered_today"); - - b.Property("RecentActivities") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("recent_activities") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SelectedMajorId") - .HasColumnType("uuid") - .HasColumnName("selected_major_id"); - - b.Property("SelectedSchoolId") - .HasColumnType("uuid") - .HasColumnName("selected_school_id"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_student_profiles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_student_profiles_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_student_profiles_user_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_student_profiles_tenant_id_region_id"); - - b.HasIndex("TenantId", "SelectedMajorId") - .HasDatabaseName("ix_student_profiles_tenant_id_selected_major_id"); - - b.HasIndex("TenantId", "SelectedSchoolId") - .HasDatabaseName("ix_student_profiles_tenant_id_selected_school_id"); - - b.HasIndex("TenantId", "UserId") - .IsUnique() - .HasDatabaseName("ix_student_profiles_tenant_id_user_id"); - - b.ToTable("student_profiles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessFailedCount") - .HasColumnType("integer") - .HasColumnName("access_failed_count"); - - b.Property("AvatarUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("avatar_url"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("concurrency_stamp"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Email") - .HasMaxLength(320) - .HasColumnType("citext") - .HasColumnName("email"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean") - .HasColumnName("email_confirmed"); - - b.Property("ForcePasswordChange") - .HasColumnType("boolean") - .HasColumnName("force_password_change"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_seen_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean") - .HasColumnName("lockout_enabled"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone") - .HasColumnName("lockout_end"); - - b.Property("Name") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("NormalizedEmail") - .HasMaxLength(320) - .HasColumnType("character varying(320)") - .HasColumnName("normalized_email"); - - b.Property("NormalizedUserName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("normalized_user_name"); - - b.Property("PasswordHash") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("password_hash"); - - b.Property("Phone") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("PhoneNumber") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone_number"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean") - .HasColumnName("phone_number_confirmed"); - - b.Property("PrimaryRole") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("primary_role"); - - b.Property("RawProfile") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_profile") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Score") - .HasColumnType("integer") - .HasColumnName("score"); - - b.Property("SecurityStamp") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("security_stamp"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean") - .HasColumnName("two_factor_enabled"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("user_name"); - - b.HasKey("Id") - .HasName("pk_users"); - - b.HasIndex("LegacyId") - .IsUnique() - .HasDatabaseName("ix_users_legacy_id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("email_index"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("user_name_index"); - - b.HasIndex("Phone") - .IsUnique() - .HasDatabaseName("ix_users_phone"); - - b.ToTable("users", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.UserIdentity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Email") - .HasMaxLength(320) - .HasColumnType("citext") - .HasColumnName("email"); - - b.Property("OpenId") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("open_id"); - - b.Property("Phone") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderSubject") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("provider_subject"); - - b.Property("UnionId") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("union_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_identities"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_identities_user_id"); - - b.HasIndex("Provider", "ProviderSubject") - .IsUnique() - .HasDatabaseName("ix_user_identities_provider_provider_subject"); - - b.ToTable("user_identities", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CollectionName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("collection_name"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FieldPath") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("field_path"); - - b.Property("IssueCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("LegacyId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_id"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("RawValueSample") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("raw_value_sample"); - - b.Property("RunId") - .HasColumnType("uuid") - .HasColumnName("run_id"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_pb_import_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_pb_import_issues_tenant_id_id"); - - b.HasIndex("TenantId", "RunId") - .HasDatabaseName("ix_pb_import_issues_tenant_id_run_id"); - - b.HasIndex("RunId", "Severity", "CollectionName") - .HasDatabaseName("ix_pb_import_issues_run_id_severity_collection_name"); - - b.HasIndex("TenantId", "CollectionName", "LegacyId") - .HasDatabaseName("ix_pb_import_issues_tenant_id_collection_name_legacy_id"); - - b.ToTable("pb_import_issues", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportRun", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("SourceKind") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_kind"); - - b.Property("SourceName") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("source_name"); - - b.Property("StartedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at") - .HasDefaultValueSql("now()"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_pb_import_runs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_pb_import_runs_tenant_id_id"); - - b.HasIndex("TenantId", "Status", "StartedAt") - .HasDatabaseName("ix_pb_import_runs_tenant_id_status_started_at"); - - b.ToTable("pb_import_runs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseRawRecord", b => - { - b.Property("RunId") - .HasColumnType("uuid") - .HasColumnName("run_id"); - - b.Property("CollectionName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("collection_name"); - - b.Property("LegacyId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_id"); - - b.Property("Errors") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("errors") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("ImportedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("imported_at") - .HasDefaultValueSql("now()"); - - b.Property("Normalized") - .HasColumnType("boolean") - .HasColumnName("normalized"); - - b.Property("Record") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("record") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("RunId", "CollectionName", "LegacyId") - .HasName("pk_pb_raw_records"); - - b.HasIndex("TenantId", "RunId") - .HasDatabaseName("ix_pb_raw_records_tenant_id_run_id"); - - b.HasIndex("TenantId", "CollectionName", "LegacyId") - .HasDatabaseName("ix_pb_raw_records_tenant_id_collection_name_legacy_id"); - - b.ToTable("pb_raw_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.AnswerRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AnswerText") - .HasColumnType("text") - .HasColumnName("answer_text"); - - b.Property("AnsweredAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("answered_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsCorrect") - .HasColumnType("boolean") - .HasColumnName("is_correct"); - - b.Property("LegacyCategoryId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_category_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyQuestionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_question_id"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("SelectedOptions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("selected_options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SessionQuestionId") - .HasColumnType("uuid") - .HasColumnName("session_question_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_answer_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_answer_records_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_answer_records_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_answer_records_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PracticeSessionId", "SessionQuestionId") - .HasDatabaseName("ix_answer_records_tenant_id_practice_session_id_session_questi~"); - - b.HasIndex("TenantId", "UserId", "PracticeSessionId") - .HasDatabaseName("ix_answer_records_tenant_id_user_id_practice_session_id"); - - b.ToTable("answer_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.DashboardDailyStat", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActiveUsers") - .HasColumnType("integer") - .HasColumnName("active_users"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("NewOrders") - .HasColumnType("integer") - .HasColumnName("new_orders"); - - b.Property("NewQuestions") - .HasColumnType("integer") - .HasColumnName("new_questions"); - - b.Property("NewRevenueCents") - .HasColumnType("integer") - .HasColumnName("new_revenue_cents"); - - b.Property("NewUsers") - .HasColumnType("integer") - .HasColumnName("new_users"); - - b.Property("RebuiltAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rebuilt_at"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("StatDate") - .HasColumnType("date") - .HasColumnName("stat_date"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_dashboard_daily_stats"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_dashboard_daily_stats_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_region_id"); - - b.HasIndex("TenantId", "StatDate", "RegionId") - .IsUnique() - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_stat_date_region_id"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "StatDate", "RegionId"), false); - - b.ToTable("dashboard_daily_stats", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ExamDate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("ExamAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("exam_at"); - - b.Property("ExamName") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("exam_name"); - - b.Property("ExamType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("exam_type"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_exam_dates"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_exam_dates_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_exam_dates_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_exam_dates_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId", "ExamAt") - .HasDatabaseName("ix_exam_dates_tenant_id_school_id_exam_at"); - - b.ToTable("exam_dates", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.FavoriteQuestion", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.HasKey("TenantId", "UserId", "QuestionReferenceId") - .HasName("pk_favorite_questions"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_favorite_questions_user_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_favorite_questions_question_owner_tenant_id_question_id"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_favorite_questions_tenant_id_question_reference_id"); - - b.ToTable("favorite_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeAccessEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessMode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("access_mode"); - - b.Property("ConsumedFreeQuota") - .HasColumnType("integer") - .HasColumnName("consumed_free_quota"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementId") - .HasColumnType("uuid") - .HasColumnName("entitlement_id"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("event_type"); - - b.Property("GrantedCount") - .HasColumnType("integer") - .HasColumnName("granted_count"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("Reason") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("reason"); - - b.Property("RequestedCount") - .HasColumnType("integer") - .HasColumnName("requested_count"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_access_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_access_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_access_events_user_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .HasDatabaseName("ix_practice_access_events_tenant_id_practice_session_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_practice_access_events_tenant_id_user_id_created_at"); - - b.ToTable("practice_access_events", null, t => - { - t.HasCheckConstraint("ck_practice_access_events_consumed_free_quota", "consumed_free_quota >= 0"); - - t.HasCheckConstraint("ck_practice_access_events_granted_count", "granted_count >= 0"); - - t.HasCheckConstraint("ck_practice_access_events_requested_count", "requested_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeDailyUsage", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FreeLimit") - .HasColumnType("integer") - .HasColumnName("free_limit"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsageDate") - .ValueGeneratedOnAdd() - .HasColumnType("date") - .HasColumnName("usage_date") - .HasDefaultValueSql("current_date"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_daily_usage"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_daily_usage_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_daily_usage_user_id"); - - b.HasIndex("TenantId", "UserId", "UsageDate") - .HasDatabaseName("ix_practice_daily_usage_tenant_id_user_id_usage_date"); - - b.HasIndex("TenantId", "UserId", "UsageDate", "ScopeType", "ScopeId") - .IsUnique() - .HasDatabaseName("ix_practice_daily_usage_tenant_id_user_id_usage_date_scope_typ~"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "UserId", "UsageDate", "ScopeType", "ScopeId"), false); - - b.ToTable("practice_daily_usage", null, t => - { - t.HasCheckConstraint("ck_practice_daily_usage_free_limit", "free_limit >= 0"); - - t.HasCheckConstraint("ck_practice_daily_usage_used_count", "used_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessEntitlementId") - .HasColumnType("uuid") - .HasColumnName("access_entitlement_id"); - - b.Property("AccessMode") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("free") - .HasColumnName("access_mode"); - - b.Property("AccessSnapshot") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_snapshot") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("BlueprintId") - .HasColumnType("uuid") - .HasColumnName("blueprint_id"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("ConsumedFreeQuota") - .HasColumnType("integer") - .HasColumnName("consumed_free_quota"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("mode"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("StartedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at") - .HasDefaultValueSql("now()"); - - b.Property("TargetId") - .HasColumnType("uuid") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_sessions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_sessions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "UserId", "Id") - .HasName("ak_practice_sessions_tenant_id_user_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_sessions_user_id"); - - b.HasIndex("TenantId", "BlueprintId") - .HasDatabaseName("ix_practice_sessions_tenant_id_blueprint_id"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_sessions_tenant_id_collection_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_practice_sessions_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_practice_sessions_tenant_id_entry_id"); - - b.HasIndex("TenantId", "UserId", "StartedAt") - .HasDatabaseName("ix_practice_sessions_tenant_id_user_id_started_at"); - - b.ToTable("practice_sessions", null, t => - { - t.HasCheckConstraint("ck_practice_sessions_consumed_free_quota", "consumed_free_quota >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionQuestion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Position") - .HasColumnType("integer") - .HasColumnName("position"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("QuestionVersionId") - .HasColumnType("uuid") - .HasColumnName("question_version_id"); - - b.Property("Score") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("score"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_practice_session_questions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_questions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "PracticeSessionId", "Id") - .HasName("ak_practice_session_questions_tenant_id_practice_session_id_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId", "QuestionVersionId") - .HasDatabaseName("ix_practice_session_questions_question_owner_tenant_id_questio~"); - - b.HasIndex("TenantId", "PracticeSessionId", "Position") - .IsUnique() - .HasDatabaseName("ix_practice_session_questions_tenant_id_practice_session_id_po~"); - - b.HasIndex("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_practice_session_questions_tenant_id_question_owner_tenant_~"); - - b.ToTable("practice_session_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReport", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Accuracy") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("accuracy"); - - b.Property("AnsweredCount") - .HasColumnType("integer") - .HasColumnName("answered_count"); - - b.Property("BlueprintId") - .HasColumnType("uuid") - .HasColumnName("blueprint_id"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DurationSeconds") - .HasColumnType("integer") - .HasColumnName("duration_seconds"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("mode"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionResults") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("question_results") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Score") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("score"); - - b.Property("SectionStats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("section_stats") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("SubmittedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("submitted_at") - .HasDefaultValueSql("now()"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalQuestions") - .HasColumnType("integer") - .HasColumnName("total_questions"); - - b.Property("TotalScore") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("total_score"); - - b.Property("UnansweredCount") - .HasColumnType("integer") - .HasColumnName("unanswered_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.Property("WrongQuestionIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("wrong_question_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.HasKey("Id") - .HasName("pk_practice_session_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_reports_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_session_reports_user_id"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_session_reports_tenant_id_collection_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .IsUnique() - .HasDatabaseName("ix_practice_session_reports_tenant_id_practice_session_id"); - - b.HasIndex("TenantId", "BlueprintId", "SubmittedAt") - .HasDatabaseName("ix_practice_session_reports_tenant_id_blueprint_id_submitted_at") - .HasFilter("blueprint_id is not null"); - - b.HasIndex("TenantId", "UserId", "SubmittedAt") - .HasDatabaseName("ix_practice_session_reports_tenant_id_user_id_submitted_at"); - - b.ToTable("practice_session_reports", null, t => - { - t.HasCheckConstraint("ck_practice_session_reports_accuracy", "accuracy >= 0 and accuracy <= 1"); - - t.HasCheckConstraint("ck_practice_session_reports_counts", "total_questions >= 0 and answered_count >= 0 and correct_count >= 0 and wrong_count >= 0 and unanswered_count >= 0"); - - t.HasCheckConstraint("ck_practice_session_reports_duration", "duration_seconds >= 0"); - - t.HasCheckConstraint("ck_practice_session_reports_scores", "score >= 0 and total_score >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReportSection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Accuracy") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("accuracy"); - - b.Property("AnsweredCount") - .HasColumnType("integer") - .HasColumnName("answered_count"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("QuestionType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("question_type"); - - b.Property("ReportId") - .HasColumnType("uuid") - .HasColumnName("report_id"); - - b.Property("Score") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("score"); - - b.Property("SectionKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("section_key"); - - b.Property("SectionName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("section_name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("total_score"); - - b.Property("UnansweredCount") - .HasColumnType("integer") - .HasColumnName("unanswered_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.HasKey("Id") - .HasName("pk_practice_session_report_sections"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_report_sections_tenant_id_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_practice_session~"); - - b.HasIndex("TenantId", "ReportId", "SectionKey") - .IsUnique() - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_report_id_sectio~"); - - b.HasIndex("TenantId", "ReportId", "SortOrder") - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_report_id_sort_o~"); - - b.ToTable("practice_session_report_sections", null, t => - { - t.HasCheckConstraint("ck_practice_session_report_sections_accuracy", "accuracy >= 0 and accuracy <= 1"); - - t.HasCheckConstraint("ck_practice_session_report_sections_counts", "question_count >= 0 and answered_count >= 0 and correct_count >= 0 and wrong_count >= 0 and unanswered_count >= 0"); - - t.HasCheckConstraint("ck_practice_session_report_sections_scores", "score >= 0 and total_score >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RecentPractice", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LastAccessAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_access_at"); - - b.Property("LastPracticeAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_practice_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("practice_type"); - - b.Property("Progress") - .HasColumnType("integer") - .HasColumnName("progress"); - - b.Property("TargetLegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("target_legacy_id"); - - b.Property("TargetName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("target_name"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_recent_practices"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_recent_practices_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_recent_practices_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_recent_practices_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UserId", "LastAccessAt") - .HasDatabaseName("ix_recent_practices_tenant_id_user_id_last_access_at"); - - b.ToTable("recent_practices", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.Report", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attachments") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("attachments") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("Contact") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("contact"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("HandledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("handled_at"); - - b.Property("HandledBy") - .HasColumnType("uuid") - .HasColumnName("handled_by"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("priority"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("Resolution") - .HasColumnType("text") - .HasColumnName("resolution"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_reports_tenant_id_id"); - - b.HasIndex("HandledBy") - .HasDatabaseName("ix_reports_handled_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_reports_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_reports_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "QuestionId", "CreatedAt") - .HasDatabaseName("ix_reports_tenant_id_question_id_created_at") - .HasFilter("question_id is not null"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_reports_tenant_id_status_created_at"); - - b.ToTable("reports", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ReportStatusEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("ReportId") - .HasColumnType("uuid") - .HasColumnName("report_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_report_status_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_report_status_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_report_status_events_actor_user_id"); - - b.HasIndex("TenantId", "ReportId", "CreatedAt") - .HasDatabaseName("ix_report_status_events_tenant_id_report_id_created_at"); - - b.ToTable("report_status_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RevenueDailyStat", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CodeCount") - .HasColumnType("integer") - .HasColumnName("code_count"); - - b.Property("CodeEstimated") - .HasColumnType("integer") - .HasColumnName("code_estimated"); - - b.Property("CodeUsed") - .HasColumnType("integer") - .HasColumnName("code_used"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EstimatedRevenueCents") - .HasColumnType("integer") - .HasColumnName("estimated_revenue_cents"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("OrderCount") - .HasColumnType("integer") - .HasColumnName("order_count"); - - b.Property("RealRevenueCents") - .HasColumnType("integer") - .HasColumnName("real_revenue_cents"); - - b.Property("RebuiltAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rebuilt_at"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("StatDate") - .HasColumnType("date") - .HasColumnName("stat_date"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_revenue_daily_stats"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_revenue_daily_stats_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_region_id"); - - b.HasIndex("TenantId", "StatDate", "RegionId", "SaleType") - .IsUnique() - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_stat_date_region_id_sale_type"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "StatDate", "RegionId", "SaleType"), false); - - b.ToTable("revenue_daily_stats", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.UserScoreEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BalanceAfter") - .HasColumnType("integer") - .HasColumnName("balance_after"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("event_type"); - - b.Property("IdempotencyKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("idempotency_key"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_score_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_score_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_score_events_user_id"); - - b.HasIndex("TenantId", "IdempotencyKey") - .IsUnique() - .HasDatabaseName("ix_user_score_events_tenant_id_idempotency_key"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_user_score_events_tenant_id_user_id_created_at"); - - b.ToTable("user_score_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.WrongQuestion", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("LastWrongAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_wrong_at") - .HasDefaultValueSql("now()"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("WrongCount") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(1) - .HasColumnName("wrong_count"); - - b.HasKey("TenantId", "UserId", "QuestionReferenceId") - .HasName("pk_wrong_questions"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_wrong_questions_user_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_wrong_questions_question_owner_tenant_id_question_id"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_wrong_questions_tenant_id_question_reference_id"); - - b.ToTable("wrong_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Announcement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BackgroundColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("background_color"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Link") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("link"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_announcements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_announcements_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_announcements_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "IsActive", "SortOrder") - .HasDatabaseName("ix_announcements_tenant_id_is_active_sort_order"); - - b.ToTable("announcements", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.AuditLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Action") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("action"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("TargetId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_audit_logs"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_audit_logs_actor_user_id"); - - b.HasIndex("TenantId", "CreatedAt") - .HasDatabaseName("ix_audit_logs_tenant_id_created_at"); - - b.HasIndex("TenantId", "TargetType", "TargetId", "CreatedAt") - .HasDatabaseName("ix_audit_logs_tenant_id_target_type_target_id_created_at"); - - b.ToTable("audit_logs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Area") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("area"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Icon") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("ParentCode") - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("parent_code"); - - b.Property("Path") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("path"); - - b.Property("PermissionCode") - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_backend_menus"); - - b.HasAlternateKey("Code") - .HasName("ak_backend_menus_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_backend_menus_code"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_backend_menus_permission_code"); - - b.HasIndex("Area", "ParentCode", "SortOrder") - .HasDatabaseName("ix_backend_menus_area_parent_code_sort_order"); - - b.ToTable("backend_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendPermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Area") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("area"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Module") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("module"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_backend_permissions"); - - b.HasAlternateKey("Code") - .HasName("ak_backend_permissions_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_backend_permissions_code"); - - b.HasIndex("Area", "Module", "SortOrder") - .HasDatabaseName("ix_backend_permissions_area_module_sort_order"); - - b.ToTable("backend_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("JobType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("job_type"); - - b.Property("LastError") - .HasMaxLength(4000) - .HasColumnType("character varying(4000)") - .HasColumnName("last_error"); - - b.Property("LockExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("lock_expires_at"); - - b.Property("LockedBy") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("locked_by"); - - b.Property("MaxRetries") - .HasColumnType("integer") - .HasColumnName("max_retries"); - - b.Property("OutputAssetId") - .HasColumnType("uuid") - .HasColumnName("output_asset_id"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Result") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("result") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RetryCount") - .HasColumnType("integer") - .HasColumnName("retry_count"); - - b.Property("RunAfter") - .HasColumnType("timestamp with time zone") - .HasColumnName("run_after"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_background_jobs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_background_jobs_tenant_id_id"); - - b.HasIndex("TenantId", "OutputAssetId") - .HasDatabaseName("ix_background_jobs_tenant_id_output_asset_id"); - - b.HasIndex("TenantId", "JobType", "Status", "CreatedAt") - .HasDatabaseName("ix_background_jobs_tenant_id_job_type_status_created_at"); - - b.HasIndex("TenantId", "Status", "RunAfter", "CreatedAt") - .HasDatabaseName("ix_background_jobs_tenant_id_status_run_after_created_at"); - - b.ToTable("background_jobs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Badge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("ConditionExtra") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("condition_extra") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ConditionField") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("condition_field"); - - b.Property("ConditionOperator") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("condition_operator"); - - b.Property("ConditionValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)") - .HasColumnName("condition_value"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IconUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon_url"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Level") - .HasColumnType("integer") - .HasColumnName("level"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnlockType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("unlock_type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_badges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_badges_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_badges_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "Category", "IsActive", "SortOrder") - .HasDatabaseName("ix_badges_tenant_id_category_is_active_sort_order"); - - b.ToTable("badges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Banner", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BackgroundColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("background_color"); - - b.Property("BorderColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("border_color"); - - b.Property("ButtonLink") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("button_link"); - - b.Property("ButtonText") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("button_text"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Subtitle") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("subtitle"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_banners"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_banners_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_banners_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_banners_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("banners", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Faq", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Answer") - .HasColumnType("text") - .HasColumnName("answer"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Question") - .HasColumnType("text") - .HasColumnName("question"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_faqs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_faqs_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_faqs_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_faqs_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("faqs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_backend_roles"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_backend_roles_code"); - - b.HasIndex("Status", "Code") - .HasDatabaseName("ix_platform_backend_roles_status_code"); - - b.ToTable("platform_backend_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("MenuCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("menu_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_role_menus"); - - b.HasIndex("MenuCode") - .HasDatabaseName("ix_platform_backend_role_menus_menu_code"); - - b.HasIndex("RoleId", "MenuCode") - .IsUnique() - .HasDatabaseName("ix_platform_backend_role_menus_role_id_menu_code"); - - b.ToTable("platform_backend_role_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("PermissionCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_role_permissions"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_platform_backend_role_permissions_permission_code"); - - b.HasIndex("RoleId", "PermissionCode") - .IsUnique() - .HasDatabaseName("ix_platform_backend_role_permissions_role_id_permission_code"); - - b.ToTable("platform_backend_role_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_user_roles"); - - b.HasIndex("RoleId") - .HasDatabaseName("ix_platform_backend_user_roles_role_id"); - - b.HasIndex("UserId", "RoleId") - .IsUnique() - .HasDatabaseName("ix_platform_backend_user_roles_user_id_role_id"); - - b.ToTable("platform_backend_user_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DataScope") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("data_scope") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_roles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_roles_tenant_id_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_roles_tenant_id_code"); - - b.HasIndex("TenantId", "Status", "Code") - .HasDatabaseName("ix_tenant_backend_roles_tenant_id_status_code"); - - b.ToTable("tenant_backend_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("MenuCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("menu_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_role_menus"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_role_menus_tenant_id_id"); - - b.HasIndex("MenuCode") - .HasDatabaseName("ix_tenant_backend_role_menus_menu_code"); - - b.HasIndex("TenantId", "RoleId", "MenuCode") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_role_menus_tenant_id_role_id_menu_code"); - - b.ToTable("tenant_backend_role_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("PermissionCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_role_permissions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_role_permissions_tenant_id_id"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_tenant_backend_role_permissions_permission_code"); - - b.HasIndex("TenantId", "RoleId", "PermissionCode") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_role_permissions_tenant_id_role_id_permissio~"); - - b.ToTable("tenant_backend_role_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_user_roles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_user_roles_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_backend_user_roles_user_id"); - - b.HasIndex("TenantId", "RoleId") - .HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_role_id"); - - b.HasIndex("TenantId", "UserId", "RoleId") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_user_id_role_id"); - - b.ToTable("tenant_backend_user_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActionLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("action_label"); - - b.Property("ActionPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("action_path"); - - b.Property("AdoptionId") - .HasColumnType("uuid") - .HasColumnName("adoption_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DedupeKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("dedupe_key"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NotificationType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("notification_type"); - - b.Property("ReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("read_at"); - - b.Property("ReadBy") - .HasColumnType("uuid") - .HasColumnName("read_by"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("SourceQuestionBankId") - .HasColumnType("uuid") - .HasColumnName("source_question_bank_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_content_notifications"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_content_notifications_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_content_notifications_created_by"); - - b.HasIndex("ReadBy") - .HasDatabaseName("ix_tenant_content_notifications_read_by"); - - b.HasIndex("TenantId", "SourceQuestionBankId") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_source_question_bank~"); - - b.HasIndex("TenantId", "NotificationType", "DedupeKey") - .IsUnique() - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_notification_type_de~") - .HasFilter("dedupe_key is not null"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_status_created_at"); - - b.HasIndex("TenantId", "AdoptionId", "NotificationType", "Status") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_adoption_id_notifica~"); - - b.ToTable("tenant_content_notifications", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActivePublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("active_public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ActiveTemplateCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("active_template_code"); - - b.Property("ActiveTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("active_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DraftPublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftTemplateCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("draft_template_code"); - - b.Property("DraftTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftUpdatedBy") - .HasColumnType("uuid") - .HasColumnName("draft_updated_by"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublishedBy") - .HasColumnType("uuid") - .HasColumnName("published_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_theme_configs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_theme_configs_tenant_id_id"); - - b.HasIndex("ActiveTemplateCode") - .HasDatabaseName("ix_tenant_theme_configs_active_template_code"); - - b.HasIndex("DraftTemplateCode") - .HasDatabaseName("ix_tenant_theme_configs_draft_template_code"); - - b.HasIndex("DraftUpdatedBy") - .HasDatabaseName("ix_tenant_theme_configs_draft_updated_by"); - - b.HasIndex("PublishedBy") - .HasDatabaseName("ix_tenant_theme_configs_published_by"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_theme_configs_tenant_id"); - - b.ToTable("tenant_theme_configs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("PreviewImageUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_image_url"); - - b.Property("PublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Theme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_theme_templates"); - - b.HasAlternateKey("Code") - .HasName("ak_tenant_theme_templates_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_tenant_theme_templates_code"); - - b.HasIndex("Status", "SortOrder", "Code") - .HasDatabaseName("ix_tenant_theme_templates_status_sort_order_code"); - - b.ToTable("tenant_theme_templates", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserBadge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BadgeId") - .HasColumnType("uuid") - .HasColumnName("badge_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("GrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("granted_at"); - - b.Property("GrantedBy") - .HasColumnType("uuid") - .HasColumnName("granted_by"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_badges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_badges_tenant_id_id"); - - b.HasIndex("GrantedBy") - .HasDatabaseName("ix_user_badges_granted_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_badges_user_id"); - - b.HasIndex("TenantId", "BadgeId") - .HasDatabaseName("ix_user_badges_tenant_id_badge_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_user_badges_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UserId", "BadgeId") - .IsUnique() - .HasDatabaseName("ix_user_badges_tenant_id_user_id_badge_id"); - - b.ToTable("user_badges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserNotification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActionLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("action_label"); - - b.Property("ActionPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("action_path"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DedupeKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("dedupe_key"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NotificationType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("notification_type"); - - b.Property("ReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("read_at"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_notifications"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_notifications_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_user_notifications_created_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_notifications_user_id"); - - b.HasIndex("TenantId", "SourceType", "SourceId") - .HasDatabaseName("ix_user_notifications_tenant_id_source_type_source_id"); - - b.HasIndex("TenantId", "UserId", "NotificationType", "DedupeKey") - .IsUnique() - .HasDatabaseName("ix_user_notifications_tenant_id_user_id_notification_type_dedu~") - .HasFilter("dedupe_key is not null"); - - b.HasIndex("TenantId", "UserId", "Status", "CreatedAt") - .HasDatabaseName("ix_user_notifications_tenant_id_user_id_status_created_at"); - - b.ToTable("user_notifications", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AcknowledgedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("acknowledged_at"); - - b.Property("AcknowledgedBy") - .HasColumnType("uuid") - .HasColumnName("acknowledged_by"); - - b.Property("Action") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("action"); - - b.Property("AuditLogId") - .HasColumnType("uuid") - .HasColumnName("audit_log_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FirstSeenAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("first_seen_at") - .HasDefaultValueSql("now()"); - - b.Property("LastSeenAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_seen_at") - .HasDefaultValueSql("now()"); - - b.Property("ResolutionNote") - .HasColumnType("text") - .HasColumnName("resolution_note"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("ResolvedBy") - .HasColumnType("uuid") - .HasColumnName("resolved_by"); - - b.Property("RuleId") - .HasColumnType("uuid") - .HasColumnName("rule_id"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .HasColumnType("text") - .HasColumnName("summary"); - - b.Property("TargetId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_audit_alerts"); - - b.HasIndex("AcknowledgedBy") - .HasDatabaseName("ix_platform_audit_alerts_acknowledged_by"); - - b.HasIndex("AuditLogId") - .HasDatabaseName("ix_platform_audit_alerts_audit_log_id"); - - b.HasIndex("ResolvedBy") - .HasDatabaseName("ix_platform_audit_alerts_resolved_by"); - - b.HasIndex("RuleId", "AuditLogId") - .IsUnique() - .HasDatabaseName("ix_platform_audit_alerts_rule_id_audit_log_id"); - - b.HasIndex("Status", "Severity", "CreatedAt") - .HasDatabaseName("ix_platform_audit_alerts_status_severity_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_audit_alerts_tenant_id_status_created_at"); - - b.ToTable("platform_audit_alerts", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlertRule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.PrimitiveCollection("ActionPatterns") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("action_patterns") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("Conditions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("conditions") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.PrimitiveCollection("TargetTypes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("target_types") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_audit_alert_rules"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_audit_alert_rules_code"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_platform_audit_alert_rules_tenant_id"); - - b.HasIndex("Enabled", "Severity", "Code") - .HasDatabaseName("ix_platform_audit_alert_rules_enabled_severity_code"); - - b.ToTable("platform_audit_alert_rules", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationChannel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ChannelCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel_code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MinReminderLevel") - .HasColumnType("integer") - .HasColumnName("min_reminder_level"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("provider"); - - b.PrimitiveCollection("ReminderChannels") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("reminder_channels") - .HasDefaultValueSql("array['internal']::text[]"); - - b.PrimitiveCollection("ReminderTypes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("reminder_types") - .HasDefaultValueSql("array['overdue', 'final_notice']::text[]"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.PrimitiveCollection("TenantIds") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("uuid[]") - .HasColumnName("tenant_ids") - .HasDefaultValueSql("'{}'::uuid[]"); - - b.Property("TimeoutSeconds") - .HasColumnType("integer") - .HasColumnName("timeout_seconds"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WebhookUrl") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("webhook_url"); - - b.HasKey("Id") - .HasName("pk_platform_dunning_notification_channels"); - - b.HasIndex("ChannelCode") - .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_channels_channel_code"); - - b.HasIndex("Enabled", "MinReminderLevel", "ChannelCode") - .HasDatabaseName("ix_platform_dunning_notification_channels_enabled_min_reminder~"); - - b.ToTable("platform_dunning_notification_channels", null, t => - { - t.HasCheckConstraint("ck_platform_dunning_channels_level", "min_reminder_level between 1 and 20"); - - t.HasCheckConstraint("ck_platform_dunning_channels_timeout", "timeout_seconds between 1 and 60"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("ChannelId") - .HasColumnType("uuid") - .HasColumnName("channel_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("LastAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_attempt_at"); - - b.Property("LastError") - .HasColumnType("text") - .HasColumnName("last_error"); - - b.Property("LastHttpCode") - .HasColumnType("integer") - .HasColumnName("last_http_code"); - - b.Property("LastResponseSummary") - .HasColumnType("text") - .HasColumnName("last_response_summary"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NextAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_attempt_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("provider"); - - b.Property("ReminderId") - .HasColumnType("uuid") - .HasColumnName("reminder_id"); - - b.Property("RequestPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("request_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ScheduledAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("scheduled_at") - .HasDefaultValueSql("now()"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_dunning_notification_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_platform_dunning_notification_events_tenant_id_id"); - - b.HasIndex("ChannelId") - .HasDatabaseName("ix_platform_dunning_notification_events_channel_id"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_invoice_id"); - - b.HasIndex("TenantId", "ReminderId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_reminder_id"); - - b.HasIndex("InvoiceId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_invoice_id_status_crea~"); - - b.HasIndex("ReminderId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_reminder_id_status_cre~"); - - b.HasIndex("TenantId", "ChannelId", "ReminderId") - .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_channel_id_r~"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_status_creat~"); - - b.HasIndex("Status", "NextAttemptAt", "ScheduledAt", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_status_next_attempt_at~"); - - b.ToTable("platform_dunning_notification_events", null, t => - { - t.HasCheckConstraint("ck_platform_dunning_events_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformSaasPlan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BaseAmountCents") - .HasColumnType("integer") - .HasColumnName("base_amount_cents"); - - b.Property("BillingCycle") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("billing_cycle"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("FeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("IncludedQuotas") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("included_quotas") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OveragePrices") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("overage_prices") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_saas_plans"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_saas_plans_code"); - - b.HasIndex("Status", "SortOrder") - .HasDatabaseName("ix_platform_saas_plans_status_sort_order"); - - b.ToTable("platform_saas_plans", null, t => - { - t.HasCheckConstraint("ck_platform_saas_plans_amount", "base_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("BankAccountMasked") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("bank_account_masked"); - - b.Property("BankName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("bank_name"); - - b.Property("BillingAddress") - .HasColumnType("text") - .HasColumnName("billing_address"); - - b.Property("BillingName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("billing_name"); - - b.Property("ContactEmail") - .HasMaxLength(300) - .HasColumnType("citext") - .HasColumnName("contact_email"); - - b.Property("ContactName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("contact_name"); - - b.Property("ContactPhone") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("contact_phone"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceTitle") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("invoice_title"); - - b.Property("InvoiceType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TaxId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("tax_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_billing_profiles"); - - b.ToTable("tenant_billing_profiles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BalanceCents") - .HasColumnType("integer") - .HasColumnName("balance_cents"); - - b.Property("BillingPeriodEnd") - .HasColumnType("date") - .HasColumnName("billing_period_end"); - - b.Property("BillingPeriodStart") - .HasColumnType("date") - .HasColumnName("billing_period_start"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("DiscountCents") - .HasColumnType("integer") - .HasColumnName("discount_cents"); - - b.Property("DueDate") - .HasColumnType("date") - .HasColumnName("due_date"); - - b.Property("InvoiceNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("invoice_no"); - - b.Property("InvoiceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - - b.Property("IssuedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("issued_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaidCents") - .HasColumnType("integer") - .HasColumnName("paid_cents"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubtotalCents") - .HasColumnType("integer") - .HasColumnName("subtotal_cents"); - - b.Property("TaxCents") - .HasColumnType("integer") - .HasColumnName("tax_cents"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCents") - .HasColumnType("integer") - .HasColumnName("total_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoices"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoices_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoices_created_by"); - - b.HasIndex("TenantId", "InvoiceNo") - .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_invoice_no"); - - b.HasIndex("TenantId", "BillingPeriodStart", "BillingPeriodEnd") - .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_billing_period_start_billing_peri~") - .HasFilter("invoice_type = 'usage_overage' and status <> 'void' and metadata->>'source' = 'usage_overage_auto'"); - - b.HasIndex("TenantId", "Status", "DueDate") - .HasDatabaseName("ix_tenant_invoices_tenant_id_status_due_date"); - - b.ToTable("tenant_invoices", null, t => - { - t.HasCheckConstraint("ck_tenant_invoices_amounts", "subtotal_cents >= 0 and discount_cents >= 0 and tax_cents >= 0 and total_cents >= 0 and paid_cents >= 0 and balance_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoices_period", "billing_period_end is null or billing_period_start is null or billing_period_end >= billing_period_start"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("description"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("ItemRefId") - .HasColumnType("uuid") - .HasColumnName("item_ref_id"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Quantity") - .HasPrecision(12, 2) - .HasColumnType("numeric(12,2)") - .HasColumnName("quantity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitAmountCents") - .HasColumnType("integer") - .HasColumnName("unit_amount_cents"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_items_tenant_id_id"); - - b.HasIndex("InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_invoice_id"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_tenant_id_invoice_id"); - - b.ToTable("tenant_invoice_items", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_items_amounts", "unit_amount_cents >= 0 and amount_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoice_items_quantity", "quantity > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("Method") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("method"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_no"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ReceivedBy") - .HasColumnType("uuid") - .HasColumnName("received_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_payments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_payments_tenant_id_id"); - - b.HasIndex("ReceivedBy") - .HasDatabaseName("ix_tenant_invoice_payments_received_by"); - - b.HasIndex("InvoiceId", "Status") - .HasDatabaseName("ix_tenant_invoice_payments_invoice_id_status"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_invoice_id"); - - b.HasIndex("TenantId", "PaymentNo") - .IsUnique() - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_payment_no"); - - b.ToTable("tenant_invoice_payments", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_payments_amount", "amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AcknowledgedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("acknowledged_at"); - - b.Property("BalanceCentsSnapshot") - .HasColumnType("integer") - .HasColumnName("balance_cents_snapshot"); - - b.Property("Channel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("channel"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DueDate") - .HasColumnType("date") - .HasColumnName("due_date"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("Message") - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ReminderDate") - .HasColumnType("date") - .HasColumnName("reminder_date"); - - b.Property("ReminderLevel") - .HasColumnType("integer") - .HasColumnName("reminder_level"); - - b.Property("ReminderType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("reminder_type"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_reminders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_reminders_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoice_reminders_created_by"); - - b.HasIndex("InvoiceId", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_invoice_id_reminder_date"); - - b.HasIndex("TenantId", "Status", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_status_reminder_date"); - - b.HasIndex("TenantId", "InvoiceId", "ReminderType", "Channel", "ReminderDate") - .IsUnique() - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_invoice_id_reminder_type~"); - - b.ToTable("tenant_invoice_reminders", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_reminders_balance", "balance_cents_snapshot >= 0"); - - t.HasCheckConstraint("ck_tenant_invoice_reminders_level", "reminder_level between 1 and 20"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CurrentVersionId") - .HasColumnType("uuid") - .HasColumnName("current_version_id"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("ExamMarkers") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("exam_markers") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("HasVideoExplanation") - .HasColumnType("boolean") - .HasColumnName("has_video_explanation"); - - b.Property("LegacyCategoryId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_category_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyNodeId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_node_id"); - - b.Property("LegacySubjectId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_subject_id"); - - b.Property("MediaUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("media_url"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("PrimaryCollectionId") - .HasColumnType("uuid") - .HasColumnName("primary_collection_id"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("type"); - - b.Property("TypeLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("type_label"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_questions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_questions_tenant_id_id"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_questions_tenant_id_category_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_questions_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_questions_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_questions_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_questions_tenant_id_node_id"); - - b.HasIndex("TenantId", "PrimaryCollectionId") - .HasDatabaseName("ix_questions_tenant_id_primary_collection_id"); - - b.HasIndex("TenantId", "QuestionBankId") - .HasDatabaseName("ix_questions_tenant_id_question_bank_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_questions_tenant_id_subject_id"); - - b.HasIndex("TenantId", "Id", "CurrentVersionId") - .HasDatabaseName("ix_questions_tenant_id_id_current_version_id"); - - b.ToTable("questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionBank", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_banks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_banks_tenant_id_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_question_banks_tenant_id_region_id"); - - b.ToTable("question_banks", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AnswerText") - .HasColumnType("text") - .HasColumnName("answer_text"); - - b.Property("CodeLang") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("code_lang"); - - b.Property("CodeTemplate") - .HasColumnType("text") - .HasColumnName("code_template"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CorrectOptionIndex") - .HasColumnType("integer") - .HasColumnName("correct_option_index"); - - b.Property("CorrectOptionIndices") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("correct_option_indices") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Explanation") - .HasColumnType("text") - .HasColumnName("explanation"); - - b.Property("Options") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubQuestions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("sub_questions") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("VersionNo") - .HasColumnType("integer") - .HasColumnName("version_no"); - - b.HasKey("Id") - .HasName("pk_question_versions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_versions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "QuestionId", "Id") - .HasName("ak_question_versions_tenant_id_question_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_question_versions_created_by"); - - b.HasIndex("TenantId", "QuestionId", "VersionNo") - .IsUnique() - .HasDatabaseName("ix_question_versions_tenant_id_question_id_version_no"); - - b.ToTable("question_versions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ConsumedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("consumed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("purpose"); - - b.Property("Realm") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("realm"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("security_stamp"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("token_hash"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_challenges"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_auth_challenges_tenant_id"); - - b.HasIndex("TokenHash") - .IsUnique() - .HasDatabaseName("ix_auth_challenges_token_hash"); - - b.HasIndex("UserId", "Purpose", "ExpiresAt") - .HasDatabaseName("ix_auth_challenges_user_id_purpose_expires_at"); - - b.ToTable("auth_challenges", null, t => - { - t.HasCheckConstraint("ck_auth_challenges_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FailureCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("failure_code"); - - b.Property("Identifier") - .HasMaxLength(320) - .HasColumnType("character varying(320)") - .HasColumnName("identifier"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Result") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("result"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_login_events"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_auth_login_events_user_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_auth_login_events_tenant_id_user_id_created_at"); - - b.ToTable("auth_login_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MfaSatisfied") - .HasColumnType("boolean") - .HasColumnName("mfa_satisfied"); - - b.Property("ParentSessionId") - .HasColumnType("uuid") - .HasColumnName("parent_session_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Realm") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("realm"); - - b.Property("ReplacedBySessionId") - .HasColumnType("uuid") - .HasColumnName("replaced_by_session_id"); - - b.Property("RevokedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("revoked_at"); - - b.Property("RevokedReason") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("revoked_reason"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("security_stamp"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TokenFamilyId") - .HasColumnType("uuid") - .HasColumnName("token_family_id"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("token_hash"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_sessions"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_auth_sessions_tenant_id"); - - b.HasIndex("TokenHash") - .IsUnique() - .HasDatabaseName("ix_auth_sessions_token_hash") - .HasAnnotation("Tiku:GlobalUnique", true); - - b.HasIndex("UserId") - .HasDatabaseName("ix_auth_sessions_user_id"); - - b.HasIndex("TokenFamilyId", "RevokedAt") - .HasDatabaseName("ix_auth_sessions_token_family_id_revoked_at"); - - b.HasIndex("Realm", "TenantId", "UserId", "ExpiresAt") - .HasDatabaseName("ix_auth_sessions_realm_tenant_id_user_id_expires_at") - .HasFilter("revoked_at is null"); - - b.ToTable("auth_sessions", null, t => - { - t.HasCheckConstraint("ck_auth_sessions_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsSendRateLimit", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Dimension") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("dimension"); - - b.Property("ScopeHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("scope_hash"); - - b.Property("BucketStart") - .HasColumnType("timestamp with time zone") - .HasColumnName("bucket_start"); - - b.Property("RequestCount") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(0) - .HasColumnName("request_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId", "Dimension", "ScopeHash", "BucketStart") - .HasName("pk_sms_send_rate_limits"); - - b.HasIndex("UpdatedAt") - .HasDatabaseName("ix_sms_send_rate_limits_updated_at"); - - b.ToTable("sms_send_rate_limits", null, t => - { - t.HasCheckConstraint("ck_sms_send_rate_limits_request_count", "request_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsVerificationCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("CodeHash") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("code_hash"); - - b.Property("ConsumedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("consumed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Phone") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("purpose"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_sms_verification_codes"); - - b.HasIndex("TenantId", "Phone", "Purpose") - .IsUnique() - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose") - .HasFilter("consumed_at is null and status in ('pending', 'sent')"); - - b.HasIndex("TenantId", "Phone", "Purpose", "CreatedAt") - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose_created_at") - .HasFilter("consumed_at is null and status in ('pending', 'sent')"); - - b.HasIndex("TenantId", "Phone", "Purpose", "ExpiresAt") - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose_expires_at"); - - b.ToTable("sms_verification_codes", null, t => - { - t.HasCheckConstraint("ck_sms_verification_codes_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.Tenant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BillingStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("billing_status"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegalName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("legal_name"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("mode"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OwnerUserId") - .HasColumnType("uuid") - .HasColumnName("owner_user_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("slug"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenants"); - - b.HasIndex("LegacyId") - .IsUnique() - .HasDatabaseName("ix_tenants_legacy_id"); - - b.HasIndex("Mode") - .IsUnique() - .HasDatabaseName("ix_tenants_mode") - .HasFilter("mode = 'platform_owned'"); - - b.HasIndex("OwnerUserId") - .HasDatabaseName("ix_tenants_owner_user_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_tenants_slug"); - - b.ToTable("tenants", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantBranding", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("BrandName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("brand_name"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FaviconUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("favicon_url"); - - b.Property("LogoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("logo_url"); - - b.Property("OrganizationName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("organization_name"); - - b.Property("PublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ServiceAccountName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("service_account_name"); - - b.Property("ServiceWechat") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("service_wechat"); - - b.Property("ShortName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("short_name"); - - b.Property("Slogan") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("slogan"); - - b.Property("Theme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_branding"); - - b.ToTable("tenant_branding", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClass", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_classes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_classes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_classes_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_classes_updated_by"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_tenant_classes_tenant_id_code") - .HasFilter("code is not null and code <> ''"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_tenant_classes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_tenant_classes_tenant_id_region_id") - .HasFilter("region_id is not null"); - - b.HasIndex("TenantId", "Status", "SortOrder", "CreatedAt") - .HasDatabaseName("ix_tenant_classes_tenant_id_status_sort_order_created_at"); - - b.ToTable("tenant_classes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClassMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClassId") - .HasColumnType("uuid") - .HasColumnName("class_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("JoinedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at") - .HasDefaultValueSql("now()"); - - b.Property("LeftAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("left_at"); - - b.Property("MemberType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("member_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_class_members"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_class_members_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_class_members_user_id"); - - b.HasIndex("TenantId", "ClassId", "Status", "MemberType") - .HasDatabaseName("ix_tenant_class_members_tenant_id_class_id_status_member_type"); - - b.HasIndex("TenantId", "ClassId", "UserId", "MemberType") - .IsUnique() - .HasDatabaseName("ix_tenant_class_members_tenant_id_class_id_user_id_member_type"); - - b.HasIndex("TenantId", "UserId", "Status", "MemberType") - .HasDatabaseName("ix_tenant_class_members_tenant_id_user_id_status_member_type"); - - b.ToTable("tenant_class_members", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantDomain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DnsVerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("dns_verified_at"); - - b.Property("DomainType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("domain_type"); - - b.Property("Host") - .IsRequired() - .HasMaxLength(253) - .HasColumnType("citext") - .HasColumnName("host"); - - b.Property("IsPrimary") - .HasColumnType("boolean") - .HasColumnName("is_primary"); - - b.Property("LastCheckedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_checked_at"); - - b.Property("LastFailureReason") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("last_failure_reason"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TlsReadyAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("tls_ready_at"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VerificationToken") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("verification_token"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_tenant_domains"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_domains_tenant_id_id"); - - b.HasIndex("Host") - .IsUnique() - .HasDatabaseName("ix_tenant_domains_host") - .HasAnnotation("Tiku:GlobalUnique", true); - - b.HasIndex("TenantId", "IsPrimary") - .IsUnique() - .HasDatabaseName("ix_tenant_domains_tenant_id_is_primary") - .HasFilter("is_primary"); - - b.ToTable("tenant_domains", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantExternalProvider", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Capability") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("capability"); - - b.Property("ConfigPublic") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config_public") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("display_name"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_external_providers"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_external_providers_tenant_id_id"); - - b.HasIndex("TenantId", "Capability", "Provider") - .IsUnique() - .HasDatabaseName("ix_tenant_external_providers_tenant_id_capability_provider"); - - b.HasIndex("TenantId", "Capability", "Status", "Priority") - .HasDatabaseName("ix_tenant_external_providers_tenant_id_capability_status_prior~"); - - b.ToTable("tenant_external_providers", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantFrontendConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ConfigVersion") - .IsConcurrencyToken() - .HasColumnType("integer") - .HasColumnName("config_version"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DraftBranding") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_branding") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftFeatures") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_features") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftHomeModules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_home_modules") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("DraftNavigation") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_navigation") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("DraftTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublishedBranding") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_branding") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedFeatures") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_features") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedHomeModules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_home_modules") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("PublishedNavigation") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_navigation") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("PublishedTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SchemaVersion") - .HasColumnType("integer") - .HasColumnName("schema_version"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_frontend_configs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_frontend_configs_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_frontend_configs_tenant_id"); - - b.ToTable("tenant_frontend_configs", null, t => - { - t.HasCheckConstraint("ck_tenant_frontend_configs_config_version", "config_version > 0"); - - t.HasCheckConstraint("ck_tenant_frontend_configs_schema_version", "schema_version > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantMembership", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyRole") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("legacy_role"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("role"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_memberships"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_memberships_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_memberships_user_id"); - - b.HasIndex("TenantId", "UserId", "Role") - .IsUnique() - .HasDatabaseName("ix_tenant_memberships_tenant_id_user_id_role"); - - b.ToTable("tenant_memberships", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EncryptedPayload") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encrypted_payload"); - - b.Property("EncryptionKeyId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("encryption_key_id"); - - b.Property("EncryptionNonce") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encryption_nonce"); - - b.Property("EncryptionTag") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encryption_tag"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)") - .HasColumnName("purpose"); - - b.Property("RotatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rotated_at"); - - b.Property("SecretKey") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)") - .HasColumnName("secret_key"); - - b.Property("SecretRef") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_secrets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_secrets_tenant_id_id"); - - b.HasIndex("TenantId", "SecretRef") - .IsUnique() - .HasDatabaseName("ix_tenant_secrets_tenant_id_secret_ref"); - - b.HasIndex("TenantId", "Purpose", "Provider", "SecretKey") - .IsUnique() - .HasDatabaseName("ix_tenant_secrets_tenant_id_purpose_provider_secret_key"); - - b.HasIndex("TenantId", "Purpose", "Provider", "Status") - .HasDatabaseName("ix_tenant_secrets_tenant_id_purpose_provider_status"); - - b.ToTable("tenant_secrets", null, t => - { - t.HasCheckConstraint("ck_tenant_secrets_encryption_envelope", "octet_length(encrypted_payload) > 0 and octet_length(encryption_nonce) = 12 and octet_length(encryption_tag) = 16 and encryption_key_id <> ''"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSettings", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("AdminFeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("admin_feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublicConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_settings"); - - b.ToTable("tenant_settings", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentFollowup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignedToUserId") - .HasColumnType("uuid") - .HasColumnName("assigned_to_user_id"); - - b.Property("ClassId") - .HasColumnType("uuid") - .HasColumnName("class_id"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CompletedBy") - .HasColumnType("uuid") - .HasColumnName("completed_by"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("DueAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("due_at"); - - b.Property("FollowupType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("followup_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("priority"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_student_followups"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_student_followups_tenant_id_id"); - - b.HasIndex("AssignedToUserId") - .HasDatabaseName("ix_tenant_student_followups_assigned_to_user_id"); - - b.HasIndex("CompletedBy") - .HasDatabaseName("ix_tenant_student_followups_completed_by"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_tenant_student_followups_student_user_id"); - - b.HasIndex("TenantId", "AssignedToUserId", "Status", "DueAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_assigned_to_user_id_stat~") - .HasFilter("assigned_to_user_id is not null"); - - b.HasIndex("TenantId", "ClassId", "Status", "DueAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_class_id_status_due_at") - .HasFilter("class_id is not null"); - - b.HasIndex("TenantId", "StudentUserId", "Status", "DueAt", "CreatedAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_student_user_id_status_d~"); - - b.ToTable("tenant_student_followups", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentNote", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Content") - .IsRequired() - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("IsPinned") - .HasColumnType("boolean") - .HasColumnName("is_pinned"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NoteType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("note_type"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_tenant_student_notes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_student_notes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_student_notes_created_by"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_tenant_student_notes_student_user_id"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_student_notes_updated_by"); - - b.HasIndex("TenantId", "CreatedBy", "CreatedAt") - .HasDatabaseName("ix_tenant_student_notes_tenant_id_created_by_created_at") - .HasFilter("created_by is not null"); - - b.HasIndex("TenantId", "StudentUserId", "IsPinned", "CreatedAt") - .HasDatabaseName("ix_tenant_student_notes_tenant_id_student_user_id_is_pinned_cr~"); - - b.ToTable("tenant_student_notes", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_claims_users_user_id"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_logins_users_user_id"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_tokens_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Category", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_categories_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_categories_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_categories_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Major", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_majors_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_majors_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_majors_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ModuleNode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_module_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_module_nodes_tenant_id_parent_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.QuestionTaxonomyAssignment", b => - { - b.HasOne("Tiku.Domain.Catalog.TaxonomyNode", null) - .WithMany() - .HasForeignKey("TaxonomyOwnerTenantId", "TaxonomyNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_question_taxonomy_assignments_taxonomy_nodes_taxonomy_owner~"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_taxonomy_assignments_questions_tenant_id_question_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Region", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_regions_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.RegionModule", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_region_modules_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_region_modules_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.School", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_schools_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_schools_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_schools_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineField", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_scoreline_fields_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_fields_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_scoreline_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Subject", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subjects_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.TaxonomyNode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_taxonomy_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.TaxonomyNode", null) - .WithMany() - .HasForeignKey("ParentOwnerTenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_taxonomy_nodes_taxonomy_nodes_parent_owner_tenant_id_parent~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.ActivationCode", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AgentUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_users_agent_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activation_codes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UsedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_users_used_by"); - - b.HasOne("Tiku.Domain.Commerce.CodeBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_code_batches_tenant_id_batch_id"); - - b.HasOne("Tiku.Domain.Commerce.CouponRedemption", null) - .WithMany() - .HasForeignKey("TenantId", "CouponRedemptionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_coupon_redemptions_tenant_id_coupon_redemp~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "UsedRegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_regions_tenant_id_used_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CodeBatch", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_code_batches_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationBatch", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_batches_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_batches_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssue", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedTo") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_assigned_to"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ResolvedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_resolved_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_reconciliation_batc~"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_reconciliation_item~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_refund_requests_ten~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssueEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issue_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issue_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationIssue", null) - .WithMany() - .HasForeignKey("TenantId", "IssueId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issue_events_commerce_reconciliatio~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_items_commerce_reconciliation_batch~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_commerce_refund_requests_tena~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_events_commerce_refund_requests_tenant_id_r~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundRequest", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ProcessedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_processed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("RequestedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_requested_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_requests_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_requests_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_refund_requests_payments_tenant_id_payment_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Coupon", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_coupons_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_coupons_svip_plans_tenant_id_plan_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CouponRedemption", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_coupon_redemptions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.Coupon", null) - .WithMany() - .HasForeignKey("TenantId", "CouponId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_coupons_tenant_id_coupon_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_svip_plans_tenant_id_plan_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Entitlement", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("RevokedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_entitlements_users_revoked_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entitlements_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entitlements_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Order", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_orders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_orders_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_orders_svip_plans_tenant_id_plan_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_orders_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.OrderItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_order_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_order_items_orders_tenant_id_order_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Payment", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payments_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payments_orders_tenant_id_order_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PaymentEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_payment_events_payments_tenant_id_payment_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityClaim", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.PointActivityTask", null) - .WithMany() - .HasForeignKey("TenantId", "TaskId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_point_activity_tasks_tenant_id_task_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityTask", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_tasks_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_point_exchange_items_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeOrder", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.PointExchangeItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_point_exchange_items_tenant_id_item_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Product", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_products_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_products_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.SvipPlan", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_svip_plans_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_svip_plans_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_subscriptions_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_usage_records_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_ai_recommendation_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_ai_recommendation_reports_users_user_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_ai_recommendation_reports_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AppAsset", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_app_assets_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAsset", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_assets_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_updated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("VerifiedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_verified_by"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetAccessEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_asset_access_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_asset_access_events_users_user_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_asset_access_events_content_assets_tenant_id_asset_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetSecurityScanEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_asset_security_scan_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_asset_security_scan_events_content_assets_tenant_id~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentEntry", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_entries_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_entries_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_entries_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_entries_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportIssue", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_content_import_issues_content_import_items_tenant_id_item_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportJob", null) - .WithMany() - .HasForeignKey("TenantId", "JobId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_issues_content_import_jobs_tenant_id_job_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportJob", null) - .WithMany() - .HasForeignKey("TenantId", "JobId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_items_content_import_jobs_tenant_id_job_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportJob", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_import_jobs_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_jobs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "TargetCategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_categories_tenant_id_target_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "TargetContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_content_nodes_tenant_id_target_content_~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "TargetQuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_question_banks_tenant_id_target_questio~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "TargetRegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_regions_tenant_id_target_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "TargetSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_subjects_tenant_id_target_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentNode", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_nodes_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_nodes_users_updated_by"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_nodes_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_content_nodes_content_nodes_tenant_id_parent_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_nodes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookChapter", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_chapters_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_chapters_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_chapters_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.HandbookSubject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_handbook_chapters_handbook_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookEntry", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_entries_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.HandbookChapter", null) - .WithMany() - .HasForeignKey("TenantId", "ChapterId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_handbook_entries_handbook_chapters_tenant_id_chapter_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_entries_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_entries_content_entries_tenant_id_entry_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookSubject", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_subjects_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ImageAsset", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_images_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.PracticeBlueprint", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_blueprints_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_blueprints_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_blueprints_users_updated_by"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_question_collections_tenant_id_collecti~"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_content_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollection", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_collections_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collections_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_collections_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_content_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_question_banks_tenant_id_question_bank~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollectionItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_question_collection_items_questions_question_owner_tenant_i~"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_question_collections_tenant_id_co~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_tenant_question_references_tenant~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionTypeGroup", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_type_groups_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_type_groups_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionVideo", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_videos_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_videos_questions_tenant_id_question_id"); - - b.HasOne("Tiku.Domain.Content.VideoExplanation", null) - .WithMany() - .HasForeignKey("TenantId", "VideoId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_videos_video_explanations_tenant_id_video_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.SubjectShare", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SourceSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_subjects_tenant_id_source_subject_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "TargetSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_subjects_tenant_id_target_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionBankPreference", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_bank_preferences_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_question_bank_preferences_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_bank_preferences_users_updated_by"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("QuestionBankOwnerTenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_question_bank_preferences_question_banks_question_ba~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionReference", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_references_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_question_references_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_question_references_questions_question_owner_tenant_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordFavorite", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_users_user_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyWord", null) - .WithMany() - .HasForeignKey("TenantId", "WordId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_vocabulary_words_tenant_id_word_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordProgress", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_users_user_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyWord", null) - .WithMany() - .HasForeignKey("TenantId", "WordId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_vocabulary_words_tenant_id_word_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoExplanation", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_explanations_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_video_explanations_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyUnit", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_vocabulary_units_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyWord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_vocabulary_words_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyUnit", null) - .WithMany() - .HasForeignKey("TenantId", "UnitId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_vocabulary_units_tenant_id_unit_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlement", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("GeneratedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_generated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("PaidBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_paid_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlements_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlements_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementExportEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ExportedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_export_events_users_exported_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_export_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_export_events_commission_settlements_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementItem", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_items_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_items_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_items_commission_settlements_tenant_i~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementProof", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("SubmittedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_users_submitted_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_proofs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_content_assets_tenant_id_asset~"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_proofs_commission_settlements_tenant_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmConfig", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_config_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookLog", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_webhook_log_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookQueueItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_webhook_queue_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralCode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_codes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_codes_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralLead", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_assigned_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedToUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_assigned_to_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_leads_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_leads_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.ReferralTrack", null) - .WithMany() - .HasForeignKey("TenantId", "FirstTrackId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_referral_tracks_tenant_id_first_track_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralQrcode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_qrcodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_qrcodes_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTeamEdge", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("LeaderUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_team_edges_users_leader_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("MemberUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_team_edges_users_member_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_team_edges_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTrack", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("TargetUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_users_target_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_tracks_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.ReferralLead", null) - .WithMany() - .HasForeignKey("TenantId", "LeadId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_referral_leads_tenant_id_lead_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.TenantCommissionSetting", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_commission_settings_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_commission_settings_users_updated_by"); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.StudentProfile", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_student_profiles_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_student_profiles_users_user_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "SelectedMajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_majors_tenant_id_selected_major_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SelectedSchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_schools_tenant_id_selected_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.UserIdentity", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_identities_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportIssue", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_import_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Import.PocketBaseImportRun", null) - .WithMany() - .HasForeignKey("TenantId", "RunId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_pb_import_issues_pb_import_runs_tenant_id_run_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportRun", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_import_runs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseRawRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_raw_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Import.PocketBaseImportRun", null) - .WithMany() - .HasForeignKey("TenantId", "RunId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_raw_records_pb_import_runs_tenant_id_run_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.AnswerRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_answer_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_answer_records_users_user_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSessionQuestion", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId", "SessionQuestionId") - .HasPrincipalKey("TenantId", "PracticeSessionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_answer_records_practice_session_questions_tenant_id_practic~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "UserId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "UserId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_answer_records_practice_sessions_tenant_id_user_id_practice~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.DashboardDailyStat", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_dashboard_daily_stats_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_dashboard_daily_stats_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ExamDate", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_exam_dates_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_exam_dates_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_exam_dates_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.FavoriteQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_favorite_questions_questions_question_owner_tenant_id_quest~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_tenant_question_references_tenant_id_que~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeAccessEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_access_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_access_events_users_user_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_access_events_practice_sessions_tenant_id_practice~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeDailyUsage", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_daily_usage_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_daily_usage_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSession", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_sessions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_sessions_users_user_id"); - - b.HasOne("Tiku.Domain.Content.PracticeBlueprint", null) - .WithMany() - .HasForeignKey("TenantId", "BlueprintId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_practice_blueprints_tenant_id_blueprint_id"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_question_collections_tenant_id_collection~"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_content_entries_tenant_id_entry_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_practice_sessions_tenant_id_prac~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionVersion", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId", "QuestionVersionId") - .HasPrincipalKey("TenantId", "QuestionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_question_versions_question_owner~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_tenant_question_references_tenan~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReport", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_users_user_id"); - - b.HasOne("Tiku.Domain.Content.PracticeBlueprint", null) - .WithMany() - .HasForeignKey("TenantId", "BlueprintId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_session_reports_practice_blueprints_tenant_id_blue~"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_session_reports_question_collections_tenant_id_col~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_practice_sessions_tenant_id_practi~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReportSection", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_practice_sessions_tenant_i~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSessionReport", null) - .WithMany() - .HasForeignKey("TenantId", "ReportId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_practice_session_reports_t~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RecentPractice", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_recent_practices_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_recent_practices_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.Report", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("HandledBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_reports_users_handled_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_reports_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_reports_questions_tenant_id_question_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ReportStatusEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_report_status_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_report_status_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.Report", null) - .WithMany() - .HasForeignKey("TenantId", "ReportId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_report_status_events_reports_tenant_id_report_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RevenueDailyStat", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_revenue_daily_stats_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_revenue_daily_stats_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.UserScoreEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_score_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_score_events_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.WrongQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_wrong_questions_questions_question_owner_tenant_id_question~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_tenant_question_references_tenant_id_questi~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Announcement", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_announcements_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.AuditLog", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_audit_logs_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_audit_logs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_backend_menus_backend_permissions_permission_code"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_background_jobs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "OutputAssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_background_jobs_content_assets_tenant_id_output_asset_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Badge", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Banner", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_banners_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_banners_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Faq", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_faqs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_faqs_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendMenu", null) - .WithMany() - .HasForeignKey("MenuCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_menus_backend_menus_menu_code"); - - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_menus_platform_backend_roles_role_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_permissions_backend_permissions_permi~"); - - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_permissions_platform_backend_roles_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b => - { - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_user_roles_platform_backend_roles_role_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_user_roles_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_roles_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendMenu", null) - .WithMany() - .HasForeignKey("MenuCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_menus_backend_menus_menu_code"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_menus_tenant_backend_roles_tenant_id_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_permissions_backend_permissions_permiss~"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_permissions_tenant_backend_roles_tenant~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_user_roles_users_user_id"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_user_roles_tenant_backend_roles_tenant_id_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_content_notifications_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReadBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_content_notifications_users_read_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_content_notifications_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "SourceQuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_tenant_content_notifications_question_banks_tenant_id_sourc~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeConfig", b => - { - b.HasOne("Tiku.Domain.Operations.TenantThemeTemplate", null) - .WithMany() - .HasForeignKey("ActiveTemplateCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_tenant_theme_templates_active_template~"); - - b.HasOne("Tiku.Domain.Operations.TenantThemeTemplate", null) - .WithMany() - .HasForeignKey("DraftTemplateCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_tenant_theme_templates_draft_template_~"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("DraftUpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_users_draft_updated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("PublishedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_users_published_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_theme_configs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserBadge", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("GrantedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_user_badges_users_granted_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_badges_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_user_badges_users_user_id"); - - b.HasOne("Tiku.Domain.Operations.Badge", null) - .WithMany() - .HasForeignKey("TenantId", "BadgeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_user_badges_badges_tenant_id_badge_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserNotification", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_user_notifications_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_notifications_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_notifications_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AcknowledgedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_platform_audit_alerts_users_acknowledged_by"); - - b.HasOne("Tiku.Domain.Operations.AuditLog", null) - .WithMany() - .HasForeignKey("AuditLogId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_audit_alerts_audit_logs_audit_log_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ResolvedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_platform_audit_alerts_users_resolved_by"); - - b.HasOne("Tiku.Domain.Platform.PlatformAuditAlertRule", null) - .WithMany() - .HasForeignKey("RuleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_audit_alerts_platform_audit_alert_rules_rule_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_platform_audit_alerts_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlertRule", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_platform_audit_alert_rules_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => - { - b.HasOne("Tiku.Domain.Platform.PlatformDunningNotificationChannel", null) - .WithMany() - .HasForeignKey("ChannelId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_platform_dunning_notif~"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoices_tenant~"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoiceReminder", null) - .WithMany() - .HasForeignKey("TenantId", "ReminderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoice_reminde~"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Platform.TenantBillingProfile", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_billing_profiles_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoices_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoices_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReceivedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_payments_users_received_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_reminders_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenant_invoices_tenant_id_invoice_~"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "PrimaryCollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_collections_tenant_id_primary_collection~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_banks_tenant_id_question_bank_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_subjects_tenant_id_subject_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionVersion", null) - .WithMany() - .HasForeignKey("TenantId", "Id", "CurrentVersionId") - .HasPrincipalKey("TenantId", "QuestionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_versions_tenant_id_id_current_version_id"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionBank", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_banks_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_banks_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionVersion", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_versions_users_created_by"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_versions_questions_tenant_id_question_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_auth_challenges_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.SetNull) - .IsRequired() - .HasConstraintName("fk_auth_login_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_auth_login_events_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthSession", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_auth_sessions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsSendRateLimit", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sms_send_rate_limits_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsVerificationCode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sms_verification_codes_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.Tenant", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("OwnerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenants_users_owner_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantBranding", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Tenancy.TenantBranding", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_branding_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClass", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_classes_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_classes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_classes_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_tenant_classes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClassMember", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_users_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.TenantClass", null) - .WithMany() - .HasForeignKey("TenantId", "ClassId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_tenant_classes_tenant_id_class_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantDomain", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_domains_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantExternalProvider", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_external_providers_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantFrontendConfig", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_frontend_configs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantMembership", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_memberships_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_memberships_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_secrets_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSettings", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Tenancy.TenantSettings", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_settings_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentFollowup", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedToUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_users_assigned_to_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CompletedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_users_completed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_followups_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_followups_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Tenancy.TenantClass", null) - .WithMany() - .HasForeignKey("TenantId", "ClassId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_tenant_classes_tenant_id_class_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentNote", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_notes_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_notes_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_notes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_notes_users_updated_by"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260728062148_AddCommerceAdjustmentVouchers.Designer.cs b/Tiku.Infrastructure/Persistence/Migrations/20260728062148_AddCommerceAdjustmentVouchers.Designer.cs deleted file mode 100644 index 3e6212e..0000000 --- a/Tiku.Infrastructure/Persistence/Migrations/20260728062148_AddCommerceAdjustmentVouchers.Designer.cs +++ /dev/null @@ -1,17959 +0,0 @@ -// -using System; -using System.Text.Json; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Tiku.Infrastructure.Persistence; - -#nullable disable - -namespace Tiku.Infrastructure.Persistence.Migrations -{ - [DbContext(typeof(TikuDbContext))] - [Migration("20260728062148_AddCommerceAdjustmentVouchers")] - partial class AddCommerceAdjustmentVouchers - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.10") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "citext"); - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "ltree"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FriendlyName") - .HasColumnType("text") - .HasColumnName("friendly_name"); - - b.Property("Xml") - .HasColumnType("text") - .HasColumnName("xml"); - - b.HasKey("Id") - .HasName("pk_data_protection_keys"); - - b.ToTable("data_protection_keys", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text") - .HasColumnName("claim_type"); - - b.Property("ClaimValue") - .HasColumnType("text") - .HasColumnName("claim_value"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_claims"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_claims_user_id"); - - b.ToTable("user_claims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text") - .HasColumnName("login_provider"); - - b.Property("ProviderKey") - .HasColumnType("text") - .HasColumnName("provider_key"); - - b.Property("ProviderDisplayName") - .HasColumnType("text") - .HasColumnName("provider_display_name"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("LoginProvider", "ProviderKey") - .HasName("pk_user_logins"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_logins_user_id"); - - b.ToTable("user_logins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("LoginProvider") - .HasColumnType("text") - .HasColumnName("login_provider"); - - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); - - b.Property("Value") - .HasColumnType("text") - .HasColumnName("value"); - - b.HasKey("UserId", "LoginProvider", "Name") - .HasName("pk_user_tokens"); - - b.ToTable("user_tokens", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CategoryType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("category_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("SvipQuestionLimit") - .HasColumnType("integer") - .HasColumnName("svip_question_limit"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_categories"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_categories_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_categories_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_categories_tenant_id_node_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_categories_tenant_id_subject_id"); - - b.ToTable("categories", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Major", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StudyTips") - .HasColumnType("text") - .HasColumnName("study_tips"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_majors"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_majors_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_majors_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_majors_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_majors_tenant_id_school_id"); - - b.ToTable("majors", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ModuleNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyModuleId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_module_id"); - - b.Property("LegacyParentId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_parent_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("Path") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("path"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_module_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_module_nodes_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_module_nodes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_module_nodes_tenant_id_module_id"); - - b.HasIndex("TenantId", "ParentId") - .HasDatabaseName("ix_module_nodes_tenant_id_parent_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_module_nodes_tenant_id_region_id"); - - b.ToTable("module_nodes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.QuestionTaxonomyAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsPrimary") - .HasColumnType("boolean") - .HasColumnName("is_primary"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("TaxonomyNodeId") - .HasColumnType("uuid") - .HasColumnName("taxonomy_node_id"); - - b.Property("TaxonomyOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("taxonomy_owner_tenant_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_question_taxonomy_assignments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_taxonomy_assignments_tenant_id_id"); - - b.HasIndex("TaxonomyOwnerTenantId", "TaxonomyNodeId") - .HasDatabaseName("ix_question_taxonomy_assignments_taxonomy_owner_tenant_id_taxo~"); - - b.HasIndex("TenantId", "QuestionId", "TaxonomyOwnerTenantId", "TaxonomyNodeId") - .IsUnique() - .HasDatabaseName("ix_question_taxonomy_assignments_tenant_id_question_id_taxonom~"); - - b.ToTable("question_taxonomy_assignments", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Region", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("code"); - - b.Property("Config") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FullName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("full_name"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsHot") - .HasColumnType("boolean") - .HasColumnName("is_hot"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Pinyin") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("pinyin"); - - b.Property("ShortName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("short_name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_regions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_regions_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_regions_tenant_id_legacy_id"); - - b.ToTable("regions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.RegionModule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsPrimarySchoolModule") - .HasColumnType("boolean") - .HasColumnName("is_primary_school_module"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Route") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("route"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TextColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("text_color"); - - b.Property("Type") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_region_modules"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_region_modules_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_region_modules_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_region_modules_tenant_id_region_id"); - - b.ToTable("region_modules", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.School", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("ProfessionalExamDate") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("professional_exam_date"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_schools"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_schools_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_schools_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_schools_tenant_id_module_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_schools_tenant_id_region_id"); - - b.ToTable("schools", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineField", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("FieldKey") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("field_key"); - - b.Property("FieldName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("field_name"); - - b.Property("FieldType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("field_type"); - - b.Property("IsFilter") - .HasColumnType("boolean") - .HasColumnName("is_filter"); - - b.Property("IsRequired") - .HasColumnType("boolean") - .HasColumnName("is_required"); - - b.Property("IsTrend") - .HasColumnType("boolean") - .HasColumnName("is_trend"); - - b.Property("IsVisible") - .HasColumnType("boolean") - .HasColumnName("is_visible"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Options") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Placeholder") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("placeholder"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Unit") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("unit"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_scoreline_fields"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_scoreline_fields_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_scoreline_fields_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "FieldKey") - .IsUnique() - .HasDatabaseName("ix_scoreline_fields_tenant_id_region_id_field_key"); - - b.HasIndex("TenantId", "RegionId", "IsFilter", "SortOrder") - .HasDatabaseName("ix_scoreline_fields_tenant_id_region_id_is_filter_sort_order"); - - b.ToTable("scoreline_fields", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FieldValues") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("field_values") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("major_name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SchoolName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("school_name"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Year") - .HasColumnType("integer") - .HasColumnName("year"); - - b.HasKey("Id") - .HasName("pk_scoreline_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_scoreline_records_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_scoreline_records_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_scoreline_records_tenant_id_major_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_scoreline_records_tenant_id_school_id"); - - b.HasIndex("TenantId", "Year") - .HasDatabaseName("ix_scoreline_records_tenant_id_year"); - - b.HasIndex("TenantId", "RegionId", "SchoolId", "MajorId", "Year") - .HasDatabaseName("ix_scoreline_records_tenant_id_region_id_school_id_major_id_ye~"); - - b.ToTable("scoreline_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Subject", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorLegacyIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("major_legacy_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_subjects"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_subjects_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_subjects_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_subjects_tenant_id_major_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_subjects_tenant_id_module_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_subjects_tenant_id_node_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_subjects_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_subjects_tenant_id_school_id"); - - b.ToTable("subjects", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.TaxonomyNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Depth") - .HasColumnType("integer") - .HasColumnName("depth"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("node_type"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("ParentOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("parent_owner_tenant_id"); - - b.Property("Path") - .HasColumnType("ltree") - .HasColumnName("path"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_taxonomy_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_taxonomy_nodes_tenant_id_id"); - - b.HasIndex("ParentOwnerTenantId", "ParentId") - .HasDatabaseName("ix_taxonomy_nodes_parent_owner_tenant_id_parent_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_taxonomy_nodes_tenant_id_code"); - - b.ToTable("taxonomy_nodes", null, t => - { - t.HasCheckConstraint("ck_taxonomy_nodes_parent_pair", "(parent_owner_tenant_id is null) = (parent_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.ActivationCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AgentUserId") - .HasColumnType("uuid") - .HasColumnName("agent_user_id"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CouponCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("coupon_code"); - - b.Property("CouponRedemptionId") - .HasColumnType("uuid") - .HasColumnName("coupon_redemption_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("IsUsed") - .HasColumnType("boolean") - .HasColumnName("is_used"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("SoldTo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("sold_to"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitPriceCents") - .HasColumnType("integer") - .HasColumnName("unit_price_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("used_at"); - - b.Property("UsedBy") - .HasColumnType("uuid") - .HasColumnName("used_by"); - - b.Property("UsedRegionId") - .HasColumnType("uuid") - .HasColumnName("used_region_id"); - - b.HasKey("Id") - .HasName("pk_activation_codes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_activation_codes_tenant_id_id"); - - b.HasIndex("AgentUserId") - .HasDatabaseName("ix_activation_codes_agent_user_id"); - - b.HasIndex("UsedBy") - .HasDatabaseName("ix_activation_codes_used_by"); - - b.HasIndex("TenantId", "BatchId") - .HasDatabaseName("ix_activation_codes_tenant_id_batch_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_activation_codes_tenant_id_code"); - - b.HasIndex("TenantId", "CouponRedemptionId") - .HasDatabaseName("ix_activation_codes_tenant_id_coupon_redemption_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_activation_codes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UsedRegionId") - .HasDatabaseName("ix_activation_codes_tenant_id_used_region_id"); - - b.ToTable("activation_codes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CodeBatch", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CampaignName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("campaign_name"); - - b.Property("Channel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel"); - - b.Property("CommissionRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("commission_rate"); - - b.Property("CostPriceCents") - .HasColumnType("integer") - .HasColumnName("cost_price_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("DefaultUnitPriceCents") - .HasColumnType("integer") - .HasColumnName("default_unit_price_cents"); - - b.Property("IssuedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("issued_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_code_batches"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_code_batches_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_code_batches_tenant_id_legacy_id"); - - b.ToTable("code_batches", null, t => - { - t.HasCheckConstraint("ck_code_batches_amounts", "default_unit_price_cents >= 0 and cost_price_cents >= 0"); - - t.HasCheckConstraint("ck_code_batches_counts", "total_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("ClosedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("closed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("Direction") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("direction"); - - b.Property("IssueId") - .HasColumnType("uuid") - .HasColumnName("issue_id"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProofAssetKey") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("proof_asset_key"); - - b.Property("Reason") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("reason"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VoucherNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("voucher_no"); - - b.HasKey("Id") - .HasName("pk_commerce_adjustment_vouchers"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_adjustment_vouchers_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_adjustment_vouchers_created_by"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commerce_adjustment_vouchers_reviewed_by"); - - b.HasIndex("TenantId", "BatchId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_batch_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_item_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_order_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "VoucherNo") - .IsUnique() - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_voucher_no"); - - b.HasIndex("TenantId", "IssueId", "CreatedAt") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_issue_id_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_status_created_at"); - - b.ToTable("commerce_adjustment_vouchers", null, t => - { - t.HasCheckConstraint("ck_commerce_adjustment_vouchers_amount", "amount_cents > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucherEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("Note") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.Property("VoucherId") - .HasColumnType("uuid") - .HasColumnName("voucher_id"); - - b.HasKey("Id") - .HasName("pk_commerce_adjustment_voucher_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_adjustment_voucher_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_adjustment_voucher_events_actor_user_id"); - - b.HasIndex("TenantId", "VoucherId", "CreatedAt") - .HasDatabaseName("ix_commerce_adjustment_voucher_events_tenant_id_voucher_id_cre~"); - - b.ToTable("commerce_adjustment_voucher_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationBatch", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BillDate") - .HasColumnType("date") - .HasColumnName("bill_date"); - - b.Property("BillType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("bill_type"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DuplicateCount") - .HasColumnType("integer") - .HasColumnName("duplicate_count"); - - b.Property("Error") - .HasColumnType("text") - .HasColumnName("error"); - - b.Property("FeeCents") - .HasColumnType("integer") - .HasColumnName("fee_cents"); - - b.Property("IgnoredCount") - .HasColumnType("integer") - .HasColumnName("ignored_count"); - - b.Property("MatchedCount") - .HasColumnType("integer") - .HasColumnName("matched_count"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MismatchCount") - .HasColumnType("integer") - .HasColumnName("mismatch_count"); - - b.Property("MissingLocalCount") - .HasColumnType("integer") - .HasColumnName("missing_local_count"); - - b.Property("MissingProviderCount") - .HasColumnType("integer") - .HasColumnName("missing_provider_count"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source"); - - b.Property("SourceHash") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("source_hash"); - - b.Property("SourceName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("source_name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_batches"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_batches_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_reconciliation_batches_created_by"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_batches_tenant_id_status_created_at"); - - b.HasIndex("TenantId", "Provider", "BillDate", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_batches_tenant_id_provider_bill_dat~"); - - b.ToTable("commerce_reconciliation_batches", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_batches_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commerce_reconciliation_batches_counts", "total_count >= 0 and matched_count >= 0 and mismatch_count >= 0 and missing_local_count >= 0 and missing_provider_count >= 0 and duplicate_count >= 0 and ignored_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("AssignedTo") - .HasColumnType("uuid") - .HasColumnName("assigned_to"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DueAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("due_at"); - - b.Property("IssueCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("IssueNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_no"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("MatchStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("match_status"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("OrderNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("RefundNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("ResolutionNote") - .HasColumnType("text") - .HasColumnName("resolution_note"); - - b.Property("ResolutionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("resolution_type"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("ResolvedBy") - .HasColumnType("uuid") - .HasColumnName("resolved_by"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("summary"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TransactionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("transaction_type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_issues_tenant_id_id"); - - b.HasIndex("AssignedTo") - .HasDatabaseName("ix_commerce_reconciliation_issues_assigned_to"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_reconciliation_issues_created_by"); - - b.HasIndex("ResolvedBy") - .HasDatabaseName("ix_commerce_reconciliation_issues_resolved_by"); - - b.HasIndex("TenantId", "IssueNo") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_issue_no"); - - b.HasIndex("TenantId", "ItemId") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_item_id") - .HasFilter("item_id is not null and status in ('open', 'investigating', 'escalated')"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_order_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "OrderNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_order_no_created_at"); - - b.HasIndex("TenantId", "PaymentId", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_payment_id_created~"); - - b.HasIndex("TenantId", "AssignedTo", "Status", "DueAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_assigned_to_status~"); - - b.HasIndex("TenantId", "BatchId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_batch_id_status_cr~"); - - b.HasIndex("TenantId", "Status", "Severity", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_status_severity_cr~"); - - b.ToTable("commerce_reconciliation_issues", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_issues_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssueEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("IssueId") - .HasColumnType("uuid") - .HasColumnName("issue_id"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_issue_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_issue_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_reconciliation_issue_events_actor_user_id"); - - b.HasIndex("TenantId", "IssueId", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issue_events_tenant_id_issue_id_cre~"); - - b.ToTable("commerce_reconciliation_issue_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FeeCents") - .HasColumnType("integer") - .HasColumnName("fee_cents"); - - b.Property("IssueCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("LocalStatus") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("local_status"); - - b.Property("MatchStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("match_status"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("OrderNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("ProviderStatus") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider_status"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("RefundNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("RefundedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("refunded_at"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TransactionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("transaction_type"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_items_tenant_id_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_order_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "BatchId", "RowNo") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_batch_id_row_no"); - - b.HasIndex("TenantId", "OrderNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_order_no_created_at"); - - b.HasIndex("TenantId", "BatchId", "MatchStatus", "RowNo") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_batch_id_match_stat~"); - - b.HasIndex("TenantId", "Provider", "ProviderTradeNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_provider_provider_t~"); - - b.HasIndex("TenantId", "RefundNo", "ProviderRefundNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_refund_no_provider_~"); - - b.ToTable("commerce_reconciliation_items", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_items_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commerce_reconciliation_items_row", "row_no > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_commerce_refund_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_refund_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_refund_events_actor_user_id"); - - b.HasIndex("TenantId", "RefundRequestId", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_events_tenant_id_refund_request_id_created_~"); - - b.ToTable("commerce_refund_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundRequest", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CancelledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("cancelled_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementAction") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("entitlement_action"); - - b.Property("FailedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("failed_at"); - - b.Property("FailureReason") - .HasColumnType("text") - .HasColumnName("failure_reason"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProcessedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("processed_at"); - - b.Property("ProcessedBy") - .HasColumnType("uuid") - .HasColumnName("processed_by"); - - b.Property("Provider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("Reason") - .HasColumnType("text") - .HasColumnName("reason"); - - b.Property("RefundNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RequestedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("requested_at") - .HasDefaultValueSql("now()"); - - b.Property("RequestedBy") - .HasColumnType("uuid") - .HasColumnName("requested_by"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SucceededAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("succeeded_at"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_refund_requests"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_refund_requests_tenant_id_id"); - - b.HasIndex("ProcessedBy") - .HasDatabaseName("ix_commerce_refund_requests_processed_by"); - - b.HasIndex("RequestedBy") - .HasDatabaseName("ix_commerce_refund_requests_requested_by"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commerce_refund_requests_reviewed_by"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundNo") - .IsUnique() - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_refund_no"); - - b.HasIndex("TenantId", "OrderId", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_order_id_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_status_created_at"); - - b.ToTable("commerce_refund_requests", null, t => - { - t.HasCheckConstraint("ck_commerce_refund_requests_amount", "amount_cents > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Coupon", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DiscountType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("discount_type"); - - b.Property("DiscountValue") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("discount_value"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MaxUses") - .HasColumnType("integer") - .HasColumnName("max_uses"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("Source") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property("ValidFrom") - .HasColumnType("timestamp with time zone") - .HasColumnName("valid_from"); - - b.Property("ValidTo") - .HasColumnType("timestamp with time zone") - .HasColumnName("valid_to"); - - b.HasKey("Id") - .HasName("pk_coupons"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_coupons_tenant_id_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_coupons_tenant_id_code"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_coupons_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_coupons_tenant_id_plan_id"); - - b.ToTable("coupons", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CouponRedemption", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClaimedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("claimed_at"); - - b.Property("CouponCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("coupon_code"); - - b.Property("CouponId") - .HasColumnType("uuid") - .HasColumnName("coupon_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DiscountAppliedCents") - .HasColumnType("integer") - .HasColumnName("discount_applied_cents"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("Source") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("used_at"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_coupon_redemptions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_coupon_redemptions_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_coupon_redemptions_user_id"); - - b.HasIndex("TenantId", "CouponId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_coupon_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_coupon_redemptions_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_plan_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_region_id"); - - b.HasIndex("TenantId", "OrderId", "UserId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_order_id_user_id") - .HasFilter("order_id is not null"); - - b.HasIndex("TenantId", "UserId", "CouponId") - .IsUnique() - .HasDatabaseName("ix_coupon_redemptions_tenant_id_user_id_coupon_id") - .HasFilter("coupon_id is not null"); - - b.ToTable("coupon_redemptions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Entitlement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementType") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("entitlement_type"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("LegacySourceId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_source_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RevokedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("revoked_at"); - - b.Property("RevokedBy") - .HasColumnType("uuid") - .HasColumnName("revoked_by"); - - b.Property("RevokedReason") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("revoked_reason"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("StartsAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at") - .HasDefaultValueSql("now()"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_entitlements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_entitlements_tenant_id_id"); - - b.HasIndex("RevokedBy") - .HasDatabaseName("ix_entitlements_revoked_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_entitlements_user_id"); - - b.HasIndex("TenantId", "UserId", "Status", "ExpiresAt") - .HasDatabaseName("ix_entitlements_tenant_id_user_id_status_expires_at"); - - b.ToTable("entitlements", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyPlanId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_plan_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("LegacyUserId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_user_id"); - - b.Property("OrderNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PayMethod") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("pay_method"); - - b.Property("PayProvider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("pay_provider"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("ProductName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("product_name"); - - b.Property("ProductType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("product_type"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefundedAmountCents") - .HasColumnType("integer") - .HasColumnName("refunded_amount_cents"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("trade_no"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_orders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_orders_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_orders_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_orders_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderNo") - .IsUnique() - .HasDatabaseName("ix_orders_tenant_id_order_no"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_orders_tenant_id_plan_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_orders_tenant_id_region_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_orders_tenant_id_user_id_created_at"); - - b.ToTable("orders", null, t => - { - t.HasCheckConstraint("ck_orders_amount", "amount_cents >= 0"); - - t.HasCheckConstraint("ck_orders_refunded_amount", "refunded_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("item_type"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("Quantity") - .HasColumnType("integer") - .HasColumnName("quantity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalAmountCents") - .HasColumnType("integer") - .HasColumnName("total_amount_cents"); - - b.Property("UnitAmountCents") - .HasColumnType("integer") - .HasColumnName("unit_amount_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_order_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_order_items_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_order_items_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_order_items_tenant_id_order_id"); - - b.ToTable("order_items", null, t => - { - t.HasCheckConstraint("ck_order_items_amounts", "unit_amount_cents >= 0 and total_amount_cents >= 0"); - - t.HasCheckConstraint("ck_order_items_quantity", "quantity > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Payment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyOrderId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_order_id"); - - b.Property("Method") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("method"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefundedAmountCents") - .HasColumnType("integer") - .HasColumnName("refunded_amount_cents"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_payments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_payments_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_payments_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderId", "UpdatedAt") - .HasDatabaseName("ix_payments_tenant_id_order_id_updated_at"); - - b.HasIndex("TenantId", "Provider", "ProviderTradeNo") - .IsUnique() - .HasDatabaseName("ix_payments_tenant_id_provider_provider_trade_no") - .HasFilter("provider_trade_no is not null"); - - b.ToTable("payments", null, t => - { - t.HasCheckConstraint("ck_payments_amount", "amount_cents >= 0"); - - t.HasCheckConstraint("ck_payments_refunded_amount", "refunded_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PaymentEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Error") - .HasColumnType("text") - .HasColumnName("error"); - - b.Property("EventId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("event_id"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProcessedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("processed_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("SignatureValid") - .HasColumnType("boolean") - .HasColumnName("signature_valid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_payment_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_payment_events_tenant_id_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_payment_events_tenant_id_payment_id"); - - b.HasIndex("TenantId", "Provider", "EventId") - .IsUnique() - .HasDatabaseName("ix_payment_events_tenant_id_provider_event_id"); - - b.ToTable("payment_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClaimedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("claimed_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TaskId") - .HasColumnType("uuid") - .HasColumnName("task_id"); - - b.Property("TaskKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("task_key"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_point_activity_claims"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_activity_claims_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_point_activity_claims_user_id"); - - b.HasIndex("TenantId", "TaskId") - .HasDatabaseName("ix_point_activity_claims_tenant_id_task_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_point_activity_claims_tenant_id_user_id_created_at"); - - b.HasIndex("TenantId", "UserId", "TaskId", "SourceType", "SourceId") - .IsUnique() - .HasDatabaseName("ix_point_activity_claims_tenant_id_user_id_task_id_source_type~") - .HasFilter("source_type is not null and source_id is not null"); - - b.ToTable("point_activity_claims", null, t => - { - t.HasCheckConstraint("ck_point_activity_claims_points", "points > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityTask", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("EndsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ends_at"); - - b.Property("MaxClaimsPerUser") - .HasColumnType("integer") - .HasColumnName("max_claims_per_user"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("Rules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TaskKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("task_key"); - - b.Property("TaskType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("task_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_point_activity_tasks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_activity_tasks_tenant_id_id"); - - b.HasIndex("TenantId", "TaskKey") - .IsUnique() - .HasDatabaseName("ix_point_activity_tasks_tenant_id_task_key"); - - b.HasIndex("TenantId", "Status", "SortOrder") - .HasDatabaseName("ix_point_activity_tasks_tenant_id_status_sort_order"); - - b.ToTable("point_activity_tasks", null, t => - { - t.HasCheckConstraint("ck_point_activity_tasks_max_claims", "max_claims_per_user > 0"); - - t.HasCheckConstraint("ck_point_activity_tasks_points", "points > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("EndsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ends_at"); - - b.Property("FulfillmentPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("fulfillment_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ItemKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("item_key"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("PointsCost") - .HasColumnType("integer") - .HasColumnName("points_cost"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Stock") - .HasColumnType("integer") - .HasColumnName("stock"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_point_exchange_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_exchange_items_tenant_id_id"); - - b.HasIndex("TenantId", "ItemKey") - .IsUnique() - .HasDatabaseName("ix_point_exchange_items_tenant_id_item_key"); - - b.HasIndex("TenantId", "RegionId", "Status", "SortOrder") - .HasDatabaseName("ix_point_exchange_items_tenant_id_region_id_status_sort_order"); - - b.ToTable("point_exchange_items", null, t => - { - t.HasCheckConstraint("ck_point_exchange_items_days", "days is null or days >= 0"); - - t.HasCheckConstraint("ck_point_exchange_items_points_cost", "points_cost > 0"); - - t.HasCheckConstraint("ck_point_exchange_items_stock", "stock is null or stock >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeOrder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CancelledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("cancelled_at"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FulfillmentSnapshot") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("fulfillment_snapshot") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("ItemName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("item_name"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("OrderedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("ordered_at") - .HasDefaultValueSql("now()"); - - b.Property("PointsCost") - .HasColumnType("integer") - .HasColumnName("points_cost"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_point_exchange_orders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_exchange_orders_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_point_exchange_orders_user_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_point_exchange_orders_tenant_id_item_id"); - - b.HasIndex("TenantId", "OrderNo") - .IsUnique() - .HasDatabaseName("ix_point_exchange_orders_tenant_id_order_no"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_point_exchange_orders_tenant_id_user_id_created_at"); - - b.ToTable("point_exchange_orders", null, t => - { - t.HasCheckConstraint("ck_point_exchange_orders_points_cost", "points_cost > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Cover") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cover"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DetailImages") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("detail_images") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true) - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyPriceText") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_price_text"); - - b.Property("Link") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("link"); - - b.Property("PreviewIframe") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_iframe"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_products"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_products_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_products_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "Type", "IsActive", "SortOrder") - .HasDatabaseName("ix_products_tenant_id_region_id_type_is_active_sort_order"); - - b.ToTable("products", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.SvipPlan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Badge") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("badge"); - - b.Property("CouponOnly") - .HasColumnType("boolean") - .HasColumnName("coupon_only"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OriginalPriceCents") - .HasColumnType("integer") - .HasColumnName("original_price_cents"); - - b.Property("PerDayLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("per_day_label"); - - b.Property("PriceCents") - .HasColumnType("integer") - .HasColumnName("price_cents"); - - b.Property("Recommended") - .HasColumnType("boolean") - .HasColumnName("recommended"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VpEnabled") - .HasColumnType("boolean") - .HasColumnName("vp_enabled"); - - b.Property("VpProductId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("vp_product_id"); - - b.HasKey("Id") - .HasName("pk_svip_plans"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_svip_plans_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_svip_plans_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_svip_plans_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("svip_plans", null, t => - { - t.HasCheckConstraint("ck_svip_plans_days", "days >= 0"); - - t.HasCheckConstraint("ck_svip_plans_price", "price_cents >= 0 and (original_price_cents is null or original_price_cents >= 0)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BillingCycle") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("billing_cycle"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PlanCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("plan_code"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_subscriptions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_subscriptions_tenant_id_id"); - - b.HasIndex("TenantId", "Status", "ExpiresAt") - .HasDatabaseName("ix_tenant_subscriptions_tenant_id_status_expires_at"); - - b.ToTable("tenant_subscriptions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MetricKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("metric_key"); - - b.Property("MetricValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)") - .HasColumnName("metric_value"); - - b.Property("PeriodEnd") - .HasColumnType("date") - .HasColumnName("period_end"); - - b.Property("PeriodStart") - .HasColumnType("date") - .HasColumnName("period_start"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_usage_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_usage_records_tenant_id_id"); - - b.HasIndex("TenantId", "MetricKey", "PeriodStart", "PeriodEnd") - .HasDatabaseName("ix_tenant_usage_records_tenant_id_metric_key_period_start_peri~"); - - b.ToTable("tenant_usage_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContextPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("context_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("GeneratedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("generated_at"); - - b.Property("InputPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("input_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Model") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("model"); - - b.Property("PromptVersion") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("prompt_version"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("ResultPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("result_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_ai_recommendation_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_ai_recommendation_reports_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_ai_recommendation_reports_user_id"); - - b.HasIndex("TenantId", "RegionId", "CreatedAt") - .HasDatabaseName("ix_ai_recommendation_reports_tenant_id_region_id_created_at"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_ai_recommendation_reports_tenant_id_user_id_created_at"); - - b.ToTable("ai_recommendation_reports", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AppAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssetKey") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("asset_key"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_app_assets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_app_assets_tenant_id_id"); - - b.HasIndex("TenantId", "AssetKey") - .IsUnique() - .HasDatabaseName("ix_app_assets_tenant_id_asset_key"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_app_assets_tenant_id_legacy_id"); - - b.ToTable("app_assets", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssetKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("asset_key"); - - b.Property("AssetType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("asset_type"); - - b.Property("Bucket") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("bucket"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("CdnUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cdn_url"); - - b.Property("ChecksumSha256") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("checksum_sha256"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("DownloadCount") - .HasColumnType("integer") - .HasColumnName("download_count"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("FileSizeBytes") - .HasColumnType("bigint") - .HasColumnName("file_size_bytes"); - - b.Property("IsPublic") - .HasColumnType("boolean") - .HasColumnName("is_public"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MimeType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("mime_type"); - - b.Property("ObjectKey") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("object_key"); - - b.Property("PreviewObjectKey") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("preview_object_key"); - - b.Property("PreviewStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("preview_status"); - - b.Property("PreviewUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_url"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SecurityFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("security_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SecurityScanProvider") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("security_scan_provider"); - - b.Property("SecurityScanStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("not_required") - .HasColumnName("security_scan_status"); - - b.Property("SecurityScanSummary") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("security_scan_summary") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SecurityScannedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("security_scanned_at"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StorageProvider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_provider"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("UploadStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("upload_status"); - - b.Property("VerificationDetails") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("verification_details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.Property("VerifiedBy") - .HasColumnType("uuid") - .HasColumnName("verified_by"); - - b.Property("VerifiedChecksumSha256") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("verified_checksum_sha256"); - - b.Property("VerifiedSizeBytes") - .HasColumnType("bigint") - .HasColumnName("verified_size_bytes"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_assets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_assets_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_assets_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_assets_updated_by"); - - b.HasIndex("VerifiedBy") - .HasDatabaseName("ix_content_assets_verified_by"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_content_assets_tenant_id_category_id"); - - b.HasIndex("TenantId", "ChecksumSha256") - .HasDatabaseName("ix_content_assets_tenant_id_checksum_sha256") - .HasFilter("checksum_sha256 is not null"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_content_assets_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_assets_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PreviewStatus") - .HasDatabaseName("ix_content_assets_tenant_id_preview_status") - .HasFilter("preview_status <> 'none'"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_content_assets_tenant_id_region_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_content_assets_tenant_id_subject_id"); - - b.HasIndex("TenantId", "StorageProvider", "Bucket", "ObjectKey") - .HasDatabaseName("ix_content_assets_tenant_id_storage_provider_bucket_object_key"); - - b.HasIndex("TenantId", "UploadStatus", "Status", "UpdatedAt") - .HasDatabaseName("ix_content_assets_tenant_id_upload_status_status_updated_at"); - - b.HasIndex("TenantId", "SecurityScanStatus", "UploadStatus", "Status", "UpdatedAt") - .HasDatabaseName("ix_content_assets_tenant_id_security_scan_status_upload_status~"); - - b.HasIndex("TenantId", "Status", "Visibility", "AssetType", "RegionId", "SubjectId", "CategoryId", "SortOrder") - .HasDatabaseName("ix_content_assets_tenant_id_status_visibility_asset_type_regio~"); - - b.ToTable("content_assets", null, t => - { - t.HasCheckConstraint("ck_content_assets_download_count", "download_count >= 0"); - - t.HasCheckConstraint("ck_content_assets_file_size", "file_size_bytes is null or file_size_bytes >= 0"); - - t.HasCheckConstraint("ck_content_assets_verified_checksum", "verified_checksum_sha256 is null or verified_checksum_sha256 ~ '^[a-f0-9]{64}$'"); - - t.HasCheckConstraint("ck_content_assets_verified_size", "verified_size_bytes is null or verified_size_bytes >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetAccessEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("access_type"); - - b.Property("ActorRole") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("actor_role"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("AssetType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("asset_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DenyCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("deny_code"); - - b.Property("Disposition") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("disposition"); - - b.Property("ExpiresInSeconds") - .HasColumnType("integer") - .HasColumnName("expires_in_seconds"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Result") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("result"); - - b.Property("SignatureMode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("signature_mode"); - - b.Property("StorageProvider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("storage_provider"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("Visibility") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_asset_access_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_asset_access_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_content_asset_access_events_user_id"); - - b.HasIndex("TenantId", "AssetId", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_asset_id_created_at"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_user_id_created_at") - .HasFilter("user_id is not null"); - - b.HasIndex("TenantId", "AccessType", "Result", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_access_type_result_cr~"); - - b.ToTable("content_asset_access_events", null, t => - { - t.HasCheckConstraint("ck_content_asset_access_events_expiry", "expires_in_seconds is null or expires_in_seconds > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetSecurityScanEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.PrimitiveCollection("IssueCodes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("issue_codes") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RiskLevel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("risk_level"); - - b.Property("ScanStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scan_status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_content_asset_security_scan_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_asset_security_scan_events_tenant_id_id"); - - b.HasIndex("TenantId", "AssetId", "CreatedAt") - .HasDatabaseName("ix_content_asset_security_scan_events_tenant_id_asset_id_creat~"); - - b.HasIndex("TenantId", "ScanStatus", "RiskLevel", "CreatedAt") - .HasDatabaseName("ix_content_asset_security_scan_events_tenant_id_scan_status_ri~"); - - b.ToTable("content_asset_security_scan_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("description"); - - b.Property("EntryKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("entry_key"); - - b.Property("EntryType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("entry_type"); - - b.Property("Icon") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LayoutConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("layout_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Route") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("route"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_entries"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_entries_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_entries_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_entries_updated_by"); - - b.HasIndex("TenantId", "EntryKey") - .IsUnique() - .HasDatabaseName("ix_content_entries_tenant_id_entry_key"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_entries_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "EntryType", "IsActive", "SortOrder") - .HasDatabaseName("ix_content_entries_tenant_id_region_id_entry_type_is_active_so~"); - - b.ToTable("content_entries", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FieldPath") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("field_path"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("JobId") - .HasColumnType("uuid") - .HasColumnName("job_id"); - - b.Property("Message") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("message"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_content_import_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_issues_tenant_id_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_content_import_issues_tenant_id_item_id"); - - b.HasIndex("TenantId", "JobId", "Severity", "RowNo") - .HasDatabaseName("ix_content_import_issues_tenant_id_job_id_severity_row_no"); - - b.ToTable("content_import_issues", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("content_hash"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExternalId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("external_id"); - - b.Property("IssuesCount") - .HasColumnType("integer") - .HasColumnName("issues_count"); - - b.Property("JobId") - .HasColumnType("uuid") - .HasColumnName("job_id"); - - b.Property("NormalizedPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("normalized_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("SourcePayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("source_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TargetId") - .HasColumnType("uuid") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_content_import_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_items_tenant_id_id"); - - b.HasIndex("TenantId", "JobId", "RowNo") - .IsUnique() - .HasDatabaseName("ix_content_import_items_tenant_id_job_id_row_no"); - - b.HasIndex("TenantId", "JobId", "Status", "RowNo") - .HasDatabaseName("ix_content_import_items_tenant_id_job_id_status_row_no"); - - b.ToTable("content_import_items", null, t => - { - t.HasCheckConstraint("ck_content_import_items_issues_count", "issues_count >= 0"); - - t.HasCheckConstraint("ck_content_import_items_row_no", "row_no >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DryRun") - .HasColumnType("boolean") - .HasColumnName("dry_run"); - - b.Property("ErrorCount") - .HasColumnType("integer") - .HasColumnName("error_count"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("ImportType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("import_type"); - - b.Property("InsertedCount") - .HasColumnType("integer") - .HasColumnName("inserted_count"); - - b.Property("NormalizedPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("normalized_payload") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SkippedCount") - .HasColumnType("integer") - .HasColumnName("skipped_count"); - - b.Property("SourceFormat") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_format"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SourceName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("source_name"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("summary") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TargetCategoryId") - .HasColumnType("uuid") - .HasColumnName("target_category_id"); - - b.Property("TargetContentNodeId") - .HasColumnType("uuid") - .HasColumnName("target_content_node_id"); - - b.Property("TargetQuestionBankId") - .HasColumnType("uuid") - .HasColumnName("target_question_bank_id"); - - b.Property("TargetRegionId") - .HasColumnType("uuid") - .HasColumnName("target_region_id"); - - b.Property("TargetSubjectId") - .HasColumnType("uuid") - .HasColumnName("target_subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedCount") - .HasColumnType("integer") - .HasColumnName("updated_count"); - - b.Property("ValidCount") - .HasColumnType("integer") - .HasColumnName("valid_count"); - - b.Property("WarningCount") - .HasColumnType("integer") - .HasColumnName("warning_count"); - - b.HasKey("Id") - .HasName("pk_content_import_jobs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_jobs_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_import_jobs_created_by"); - - b.HasIndex("TenantId", "TargetCategoryId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_category_id"); - - b.HasIndex("TenantId", "TargetContentNodeId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_content_node_id"); - - b.HasIndex("TenantId", "TargetQuestionBankId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_question_bank_id"); - - b.HasIndex("TenantId", "TargetRegionId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_region_id"); - - b.HasIndex("TenantId", "TargetSubjectId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_subject_id"); - - b.HasIndex("TenantId", "ImportType", "Status", "CreatedAt") - .HasDatabaseName("ix_content_import_jobs_tenant_id_import_type_status_created_at"); - - b.ToTable("content_import_jobs", null, t => - { - t.HasCheckConstraint("ck_content_import_jobs_counts", "total_count >= 0 and valid_count >= 0 and error_count >= 0 and warning_count >= 0 and inserted_count >= 0 and updated_count >= 0 and skipped_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Depth") - .HasColumnType("integer") - .HasColumnName("depth"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsLeaf") - .HasColumnType("boolean") - .HasColumnName("is_leaf"); - - b.Property("IsSelectable") - .HasColumnType("boolean") - .HasColumnName("is_selectable"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MarkerConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("marker_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MarkerType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("marker_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("node_key"); - - b.Property("NodeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("node_type"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("Path") - .HasColumnType("ltree") - .HasColumnName("path"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_content_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_nodes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_nodes_created_by"); - - b.HasIndex("Path") - .HasDatabaseName("ix_content_nodes_path"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Path"), "gist"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_nodes_updated_by"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_nodes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MarkerType") - .HasDatabaseName("ix_content_nodes_tenant_id_marker_type") - .HasFilter("marker_type is not null"); - - b.HasIndex("TenantId", "ParentId") - .HasDatabaseName("ix_content_nodes_tenant_id_parent_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_content_nodes_tenant_id_region_id"); - - b.HasIndex("TenantId", "EntryId", "NodeKey") - .IsUnique() - .HasDatabaseName("ix_content_nodes_tenant_id_entry_id_node_key"); - - b.HasIndex("TenantId", "EntryId", "ParentId", "SortOrder") - .HasDatabaseName("ix_content_nodes_tenant_id_entry_id_parent_id_sort_order"); - - b.ToTable("content_nodes", null, t => - { - t.HasCheckConstraint("ck_content_nodes_depth", "depth >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookChapter", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_chapters"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_chapters_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_chapters_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_chapters_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_handbook_chapters_tenant_id_subject_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_chapters_tenant_id_entry_id_content_node_id_subjec~"); - - b.ToTable("handbook_chapters", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ChapterId") - .HasColumnType("uuid") - .HasColumnName("chapter_id"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("Summary") - .HasColumnType("text") - .HasColumnName("summary"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_entries"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_entries_tenant_id_id"); - - b.HasIndex("TenantId", "ChapterId") - .HasDatabaseName("ix_handbook_entries_tenant_id_chapter_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_entries_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_entries_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "ChapterId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_entries_tenant_id_entry_id_content_node_id_chapter~"); - - b.ToTable("handbook_entries", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookSubject", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorLegacyIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("major_legacy_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_subjects"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_subjects_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_subjects_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_major_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_school_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_subjects_tenant_id_entry_id_content_node_id_region~"); - - b.ToTable("handbook_subjects", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ImageAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("CdnUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cdn_url"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("IsPublic") - .HasColumnType("boolean") - .HasColumnName("is_public"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_images"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_images_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_images_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "Category", "IsPublic") - .HasDatabaseName("ix_images_tenant_id_category_is_public"); - - b.ToTable("images", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.PracticeBlueprint", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssemblyType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assembly_type"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("mode"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("PassScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("pass_score"); - - b.Property("QuestionLimit") - .HasColumnType("integer") - .HasColumnName("question_limit"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Rules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Sections") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("sections") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_practice_blueprints"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_blueprints_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_practice_blueprints_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_practice_blueprints_updated_by"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_collection_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_practice_blueprints_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_region_id"); - - b.HasIndex("TenantId", "NodeId", "CollectionId", "Mode", "Status", "SortOrder") - .HasDatabaseName("ix_practice_blueprints_tenant_id_node_id_collection_id_mode_st~"); - - b.ToTable("practice_blueprints", null, t => - { - t.HasCheckConstraint("ck_practice_blueprints_duration", "duration_minutes is null or duration_minutes > 0"); - - t.HasCheckConstraint("ck_practice_blueprints_question_limit", "question_limit is null or question_limit > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("CollectionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("collection_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Filters") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("filters") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_question_collections"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_collections_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_question_collections_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_question_collections_updated_by"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_question_collections_tenant_id_category_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_question_collections_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_collections_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "QuestionBankId") - .HasDatabaseName("ix_question_collections_tenant_id_question_bank_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_question_collections_tenant_id_region_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_question_collections_tenant_id_subject_id"); - - b.HasIndex("TenantId", "NodeId", "Status", "SortOrder") - .HasDatabaseName("ix_question_collections_tenant_id_node_id_status_sort_order"); - - b.ToTable("question_collections", null, t => - { - t.HasCheckConstraint("ck_question_collections_duration", "duration_minutes is null or duration_minutes > 0"); - - t.HasCheckConstraint("ck_question_collections_question_count", "question_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollectionItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("Required") - .HasColumnType("boolean") - .HasColumnName("required"); - - b.Property("Score") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("score"); - - b.Property("SectionKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("section_key"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_collection_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_collection_items_tenant_id_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_question_collection_items_question_owner_tenant_id_question~"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_question_collection_items_tenant_id_question_reference_id"); - - b.HasIndex("TenantId", "CollectionId", "QuestionReferenceId") - .IsUnique() - .HasDatabaseName("ix_question_collection_items_tenant_id_collection_id_question_~"); - - b.HasIndex("TenantId", "CollectionId", "SectionKey", "SortOrder") - .HasDatabaseName("ix_question_collection_items_tenant_id_collection_id_section_k~"); - - b.ToTable("question_collection_items", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionTypeGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("display_name"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Types") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("types") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_type_groups"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_type_groups_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_type_groups_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_question_type_groups_tenant_id_subject_id_is_active_sort_or~"); - - b.ToTable("question_type_groups", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionVideo", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyQuestionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_question_id"); - - b.Property("LegacyVideoId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_video_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VideoId") - .HasColumnType("uuid") - .HasColumnName("video_id"); - - b.Property("VideoType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("video_type"); - - b.HasKey("Id") - .HasName("pk_question_videos"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_videos_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_videos_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "VideoId") - .HasDatabaseName("ix_question_videos_tenant_id_video_id"); - - b.HasIndex("TenantId", "QuestionId", "VideoId", "VideoType") - .IsUnique() - .HasDatabaseName("ix_question_videos_tenant_id_question_id_video_id_video_type"); - - b.ToTable("question_videos", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.SubjectShare", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("SourceSubjectId") - .HasColumnType("uuid") - .HasColumnName("source_subject_id"); - - b.Property("TargetSubjectId") - .HasColumnType("uuid") - .HasColumnName("target_subject_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Id") - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacySourceSubjectId") - .HasColumnType("text") - .HasColumnName("legacy_source_subject_id"); - - b.Property("LegacyTargetSubjectId") - .HasColumnType("text") - .HasColumnName("legacy_target_subject_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("TenantId", "SourceSubjectId", "TargetSubjectId") - .HasName("pk_subject_shares"); - - b.HasIndex("TenantId", "TargetSubjectId") - .HasDatabaseName("ix_subject_shares_tenant_id_target_subject_id"); - - b.ToTable("subject_shares", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionBankPreference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Alias") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("alias"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("IsVisible") - .HasColumnType("boolean") - .HasColumnName("is_visible"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NavigationLocation") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("navigation_location"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("QuestionBankOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_bank_owner_tenant_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_question_bank_preferences"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_question_bank_preferences_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_question_bank_preferences_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_question_bank_preferences_updated_by"); - - b.HasIndex("QuestionBankOwnerTenantId", "QuestionBankId") - .HasDatabaseName("ix_tenant_question_bank_preferences_question_bank_owner_tenant~"); - - b.HasIndex("TenantId", "QuestionBankOwnerTenantId", "QuestionBankId") - .IsUnique() - .HasDatabaseName("ix_tenant_question_bank_preferences_tenant_id_question_bank_ow~"); - - b.ToTable("tenant_question_bank_preferences", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionReference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_question_references"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_question_references_tenant_id_id"); - - b.HasAlternateKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasName("ak_tenant_question_references_tenant_id_question_owner_tenant_~"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_question_references_created_by"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_tenant_question_references_question_owner_tenant_id_questio~"); - - b.ToTable("tenant_question_references", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordFavorite", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FavoritedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("favorited_at"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacyUserId") - .HasColumnType("text") - .HasColumnName("legacy_user_id"); - - b.Property("LegacyWordId") - .HasColumnType("text") - .HasColumnName("legacy_word_id"); - - b.Property("Note") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WordId") - .HasColumnType("uuid") - .HasColumnName("word_id"); - - b.HasKey("Id") - .HasName("pk_user_word_favorites"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_word_favorites_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_word_favorites_user_id"); - - b.HasIndex("TenantId", "WordId") - .HasDatabaseName("ix_user_word_favorites_tenant_id_word_id"); - - b.HasIndex("TenantId", "UserId", "WordId") - .IsUnique() - .HasDatabaseName("ix_user_word_favorites_tenant_id_user_id_word_id"); - - b.ToTable("user_word_favorites", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordProgress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CorrectStreak") - .HasColumnType("integer") - .HasColumnName("correct_streak"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DueLevel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("due_level"); - - b.Property("EaseFactor") - .ValueGeneratedOnAdd() - .HasPrecision(4, 2) - .HasColumnType("numeric(4,2)") - .HasDefaultValue(2.50m) - .HasColumnName("ease_factor"); - - b.Property("LastResult") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("last_result"); - - b.Property("LastReviewAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_review_at"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacyUserId") - .HasColumnType("text") - .HasColumnName("legacy_user_id"); - - b.Property("LegacyWordId") - .HasColumnType("text") - .HasColumnName("legacy_word_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NextReviewAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_review_at"); - - b.Property("ReviewCount") - .HasColumnType("integer") - .HasColumnName("review_count"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WordId") - .HasColumnType("uuid") - .HasColumnName("word_id"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.HasKey("Id") - .HasName("pk_user_word_progress"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_word_progress_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_word_progress_user_id"); - - b.HasIndex("TenantId", "WordId") - .HasDatabaseName("ix_user_word_progress_tenant_id_word_id"); - - b.HasIndex("TenantId", "UserId", "WordId") - .IsUnique() - .HasDatabaseName("ix_user_word_progress_tenant_id_user_id_word_id"); - - b.HasIndex("TenantId", "UserId", "NextReviewAt", "Status", "DueLevel") - .HasDatabaseName("ix_user_word_progress_tenant_id_user_id_next_review_at_status_~"); - - b.ToTable("user_word_progress", null, t => - { - t.HasCheckConstraint("ck_user_word_progress_correct_streak", "correct_streak >= 0"); - - t.HasCheckConstraint("ck_user_word_progress_ease_factor", "ease_factor >= 1.30 and ease_factor <= 3.00"); - - t.HasCheckConstraint("ck_user_word_progress_review_count", "review_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoExplanation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("DurationSeconds") - .HasColumnType("integer") - .HasColumnName("duration_seconds"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsGeneral") - .HasColumnType("boolean") - .HasColumnName("is_general"); - - b.Property("KnowledgeTags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("knowledge_tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacySubjectId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_subject_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ThumbnailUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("thumbnail_url"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VideoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("video_url"); - - b.HasKey("Id") - .HasName("pk_video_explanations"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_video_explanations_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_video_explanations_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SourceHash") - .HasDatabaseName("ix_video_explanations_tenant_id_source_hash") - .HasFilter("source_hash is not null"); - - b.HasIndex("TenantId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_video_explanations_tenant_id_subject_id_is_active_sort_order"); - - b.ToTable("video_explanations", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyUnit", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WordCount") - .HasColumnType("integer") - .HasColumnName("word_count"); - - b.HasKey("Id") - .HasName("pk_vocabulary_units"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_vocabulary_units_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_vocabulary_units_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_vocabulary_units_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_vocabulary_units_tenant_id_region_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_vocabulary_units_tenant_id_entry_id_content_node_id_region_~"); - - b.ToTable("vocabulary_units", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyWord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Example") - .HasColumnType("text") - .HasColumnName("example"); - - b.Property("ExampleTranslation") - .HasColumnType("text") - .HasColumnName("example_translation"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Meaning") - .HasColumnType("text") - .HasColumnName("meaning"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Phonetic") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("phonetic"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitId") - .HasColumnType("uuid") - .HasColumnName("unit_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Word") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("word"); - - b.HasKey("Id") - .HasName("pk_vocabulary_words"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_vocabulary_words_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_vocabulary_words_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_vocabulary_words_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UnitId", "IsActive", "SortOrder", "CreatedAt") - .HasDatabaseName("ix_vocabulary_words_tenant_id_unit_id_is_active_sort_order_cre~"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "UnitId", "IsActive", "SortOrder") - .HasDatabaseName("ix_vocabulary_words_tenant_id_entry_id_content_node_id_unit_id~"); - - b.ToTable("vocabulary_words", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CommissionAmountCents") - .HasColumnType("integer") - .HasColumnName("commission_amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DefaultRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("default_rate"); - - b.Property("EffectiveRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("effective_rate"); - - b.Property("GeneratedBy") - .HasColumnType("uuid") - .HasColumnName("generated_by"); - - b.Property("GrossAmountCents") - .HasColumnType("integer") - .HasColumnName("gross_amount_cents"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaidBy") - .HasColumnType("uuid") - .HasColumnName("paid_by"); - - b.Property("PaidUserCount") - .HasColumnType("integer") - .HasColumnName("paid_user_count"); - - b.Property("PaymentAccount") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("payment_account"); - - b.Property("PaymentMethod") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_method"); - - b.Property("PeriodEnd") - .HasColumnType("date") - .HasColumnName("period_end"); - - b.Property("PeriodStart") - .HasColumnType("date") - .HasColumnName("period_start"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("SettlementNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("settlement_no"); - - b.Property("SourceCount") - .HasColumnType("integer") - .HasColumnName("source_count"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlements_tenant_id_id"); - - b.HasIndex("GeneratedBy") - .HasDatabaseName("ix_commission_settlements_generated_by"); - - b.HasIndex("PaidBy") - .HasDatabaseName("ix_commission_settlements_paid_by"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_commission_settlements_referrer_user_id"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commission_settlements_reviewed_by"); - - b.HasIndex("TenantId", "SettlementNo") - .IsUnique() - .HasDatabaseName("ix_commission_settlements_tenant_id_settlement_no"); - - b.HasIndex("TenantId", "ReferrerUserId", "PeriodStart") - .HasDatabaseName("ix_commission_settlements_referrer_start"); - - b.HasIndex("TenantId", "ReferrerUserId", "PeriodStart", "PeriodEnd") - .IsUnique() - .HasDatabaseName("ux_commission_settlements_referrer_period"); - - b.ToTable("commission_settlements", null, t => - { - t.HasCheckConstraint("ck_commission_settlements_amounts", "gross_amount_cents >= 0 and commission_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commission_settlements_counts", "source_count >= 0 and paid_user_count >= 0"); - - t.HasCheckConstraint("ck_commission_settlements_period", "period_end >= period_start"); - - t.HasCheckConstraint("ck_commission_settlements_rates", "default_rate >= 0 and default_rate <= 1 and (effective_rate is null or (effective_rate >= 0 and effective_rate <= 1))"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementExportEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentSha256") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("content_sha256"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExportFormat") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasColumnName("export_format"); - - b.Property("ExportedBy") - .HasColumnType("uuid") - .HasColumnName("exported_by"); - - b.Property("Filename") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("filename"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RowCount") - .HasColumnType("integer") - .HasColumnName("row_count"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_export_events"); - - b.HasIndex("ExportedBy") - .HasDatabaseName("ix_commission_settlement_export_events_exported_by"); - - b.HasIndex("TenantId", "SettlementId", "CreatedAt") - .HasDatabaseName("ix_commission_settlement_export_events_tenant_id_settlement_id~"); - - b.ToTable("commission_settlement_export_events", null, t => - { - t.HasCheckConstraint("ck_commission_export_events_rows", "row_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AttributionType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("attribution_type"); - - b.Property("CommissionAmountCents") - .HasColumnType("integer") - .HasColumnName("commission_amount_cents"); - - b.Property("CommissionRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("commission_rate"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("GrossAmountCents") - .HasColumnType("integer") - .HasColumnName("gross_amount_cents"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RateSource") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("rate_source"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_no"); - - b.Property("SourcePaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("source_paid_at"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_type"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlement_items_tenant_id_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_commission_settlement_items_referrer_user_id"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_commission_settlement_items_student_user_id"); - - b.HasIndex("TenantId", "SettlementId") - .HasDatabaseName("ix_commission_settlement_items_tenant_id_settlement_id"); - - b.HasIndex("TenantId", "ReferrerUserId", "SourcePaidAt") - .HasDatabaseName("ix_commission_settlement_items_tenant_id_referrer_user_id_sour~"); - - b.HasIndex("TenantId", "SourceType", "SourceId") - .IsUnique() - .HasDatabaseName("ix_commission_settlement_items_tenant_id_source_type_source_id"); - - b.ToTable("commission_settlement_items", null, t => - { - t.HasCheckConstraint("ck_commission_settlement_items_amounts", "gross_amount_cents >= 0 and commission_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commission_settlement_items_rate", "commission_rate >= 0 and commission_rate <= 1"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementProof", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("ExternalUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("external_url"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentAccount") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("payment_account"); - - b.Property("PaymentMethod") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_method"); - - b.Property("ProofType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("proof_type"); - - b.Property("ReviewNote") - .HasColumnType("text") - .HasColumnName("review_note"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubmittedBy") - .HasColumnType("uuid") - .HasColumnName("submitted_by"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_proofs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlement_proofs_tenant_id_id"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commission_settlement_proofs_reviewed_by"); - - b.HasIndex("SubmittedBy") - .HasDatabaseName("ix_commission_settlement_proofs_submitted_by"); - - b.HasIndex("TenantId", "AssetId") - .HasDatabaseName("ix_commission_settlement_proofs_tenant_id_asset_id"); - - b.HasIndex("TenantId", "SettlementId", "Status", "CreatedAt") - .HasDatabaseName("ix_commission_settlement_proofs_tenant_id_settlement_id_status~"); - - b.ToTable("commission_settlement_proofs", null, t => - { - t.HasCheckConstraint("ck_commission_settlement_proofs_amount", "amount_cents is null or amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignmentConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("assignment_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssignmentCursor") - .HasColumnType("integer") - .HasColumnName("assignment_cursor"); - - b.Property("AssignmentMode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assignment_mode"); - - b.Property("AssignmentPool") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("assignment_pool") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DelaySeconds") - .HasColumnType("integer") - .HasColumnName("delay_seconds"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("ExamType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("exam_type"); - - b.Property("FormName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("form_name"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TimeoutSeconds") - .HasColumnType("integer") - .HasColumnName("timeout_seconds"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Url") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_crm_config"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_config_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_crm_config_tenant_id"); - - b.HasIndex("TenantId", "AssignmentMode") - .HasDatabaseName("ix_crm_config_tenant_id_assignment_mode"); - - b.ToTable("crm_config", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempt") - .HasColumnType("integer") - .HasColumnName("attempt"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("HttpCode") - .HasColumnType("integer") - .HasColumnName("http_code"); - - b.Property("LeadId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Outcome") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("outcome"); - - b.Property("RecordId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("record_id"); - - b.Property("RequestBody") - .HasColumnType("text") - .HasColumnName("request_body"); - - b.Property("RequestPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("request_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ResponseSummary") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("response_summary"); - - b.Property("SignedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("signed_at"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_crm_webhook_log"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_webhook_log_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_log_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RecordId", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_log_tenant_id_record_id_created_at"); - - b.ToTable("crm_webhook_log", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookQueueItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IdempotencyKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("idempotency_key"); - - b.Property("LastAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_attempt_at"); - - b.Property("LastError") - .HasColumnType("text") - .HasColumnName("last_error"); - - b.Property("LastHttpCode") - .HasColumnType("integer") - .HasColumnName("last_http_code"); - - b.Property("LastResponseSummary") - .HasColumnType("text") - .HasColumnName("last_response_summary"); - - b.Property("LeadId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("NextAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_attempt_at"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Provider") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RecordId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("record_id"); - - b.Property("ScheduledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("scheduled_at"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Source") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TargetUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("target_url"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_crm_webhook_queue"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_webhook_queue_tenant_id_id"); - - b.HasIndex("TenantId", "IdempotencyKey") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_idempotency_key") - .HasFilter("idempotency_key is not null"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RecordId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_record_id") - .HasFilter("record_id is not null"); - - b.HasIndex("TenantId", "RecordId", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_record_id_created_at") - .HasFilter("record_id is not null"); - - b.HasIndex("TenantId", "Source", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_source_created_at") - .HasFilter("source = 'tenant.student.crm_push'"); - - b.HasIndex("TenantId", "Status", "ScheduledAt", "NextAttemptAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_status_scheduled_at_next_attemp~"); - - b.ToTable("crm_webhook_queue", null, t => - { - t.HasCheckConstraint("ck_crm_webhook_queue_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Channel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LandingPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("landing_path"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_referral_codes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_codes_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_referral_codes_user_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_referral_codes_tenant_id_code"); - - b.HasIndex("TenantId", "UserId") - .IsUnique() - .HasDatabaseName("ix_referral_codes_tenant_id_user_id"); - - b.HasIndex("TenantId", "Code", "Status") - .HasDatabaseName("ix_referral_codes_tenant_id_code_status"); - - b.ToTable("referral_codes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralLead", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("assigned_at"); - - b.Property("AssignedBy") - .HasColumnType("uuid") - .HasColumnName("assigned_by"); - - b.Property("AssignedToUserId") - .HasColumnType("uuid") - .HasColumnName("assigned_to_user_id"); - - b.Property("AssignmentMode") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assignment_mode"); - - b.Property("BindType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("bind_type"); - - b.Property("BoundAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("bound_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FirstTrackId") - .HasColumnType("uuid") - .HasColumnName("first_track_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ProtectedUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("protected_until"); - - b.Property("RefCode") - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Source") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_referral_leads"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_leads_tenant_id_id"); - - b.HasIndex("AssignedBy") - .HasDatabaseName("ix_referral_leads_assigned_by"); - - b.HasIndex("AssignedToUserId") - .HasDatabaseName("ix_referral_leads_assigned_to_user_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_referral_leads_referrer_user_id"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_referral_leads_student_user_id"); - - b.HasIndex("TenantId", "FirstTrackId") - .HasDatabaseName("ix_referral_leads_tenant_id_first_track_id"); - - b.HasIndex("TenantId", "StudentUserId") - .IsUnique() - .HasDatabaseName("ix_referral_leads_tenant_id_student_user_id"); - - b.HasIndex("TenantId", "ReferrerUserId", "BoundAt") - .HasDatabaseName("ix_referral_leads_tenant_id_referrer_user_id_bound_at"); - - b.HasIndex("TenantId", "AssignedToUserId", "Status", "BoundAt") - .HasDatabaseName("ix_referral_leads_tenant_id_assigned_to_user_id_status_bound_at"); - - b.ToTable("referral_leads", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralQrcode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Page") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("page"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("QrcodeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("qrcode_url"); - - b.Property("RefCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("Scene") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("scene"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_referral_qrcodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_qrcodes_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_referral_qrcodes_user_id"); - - b.HasIndex("TenantId", "RefCode", "Status") - .HasDatabaseName("ix_referral_qrcodes_tenant_id_ref_code_status"); - - b.HasIndex("TenantId", "Provider", "Scene", "Page") - .IsUnique() - .HasDatabaseName("ix_referral_qrcodes_tenant_id_provider_scene_page"); - - b.ToTable("referral_qrcodes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTeamEdge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LeaderUserId") - .HasColumnType("uuid") - .HasColumnName("leader_user_id"); - - b.Property("MemberUserId") - .HasColumnType("uuid") - .HasColumnName("member_user_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RelationType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("relation_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_referral_team_edges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_team_edges_tenant_id_id"); - - b.HasIndex("LeaderUserId") - .HasDatabaseName("ix_referral_team_edges_leader_user_id"); - - b.HasIndex("MemberUserId") - .HasDatabaseName("ix_referral_team_edges_member_user_id"); - - b.HasIndex("TenantId", "LeaderUserId", "Status") - .HasDatabaseName("ix_referral_team_edges_tenant_id_leader_user_id_status"); - - b.HasIndex("TenantId", "MemberUserId", "RelationType") - .IsUnique() - .HasDatabaseName("ix_referral_team_edges_tenant_id_member_user_id_relation_type"); - - b.ToTable("referral_team_edges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTrack", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("LeadId") - .HasColumnType("uuid") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefCode") - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Source") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source"); - - b.Property("TargetUserId") - .HasColumnType("uuid") - .HasColumnName("target_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_referral_tracks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_tracks_tenant_id_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_referral_tracks_referrer_user_id"); - - b.HasIndex("TargetUserId") - .HasDatabaseName("ix_referral_tracks_target_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_referral_tracks_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "LeadId", "CreatedAt") - .HasDatabaseName("ix_referral_tracks_tenant_id_lead_id_created_at"); - - b.HasIndex("TenantId", "RefCode", "EventType", "CreatedAt") - .HasDatabaseName("ix_referral_tracks_tenant_id_ref_code_event_type_created_at"); - - b.ToTable("referral_tracks", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.TenantCommissionSetting", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Config") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DefaultRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("default_rate"); - - b.Property("MinSettlementCents") - .HasColumnType("integer") - .HasColumnName("min_settlement_cents"); - - b.Property("SettlementCycle") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("settlement_cycle"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_commission_settings"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_commission_settings_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_commission_settings_tenant_id"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_commission_settings_updated_by"); - - b.ToTable("tenant_commission_settings", null, t => - { - t.HasCheckConstraint("ck_tenant_commission_settings_default_rate", "default_rate >= 0 and default_rate <= 1"); - - t.HasCheckConstraint("ck_tenant_commission_settings_min_settlement", "min_settlement_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.StudentProfile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AvatarPreset") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("male") - .HasColumnName("avatar_preset"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LastCheckInDate") - .HasColumnType("date") - .HasColumnName("last_check_in_date"); - - b.Property("LegacyUserId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_user_id"); - - b.Property("MasteredWordsCount") - .HasColumnType("integer") - .HasColumnName("mastered_words_count"); - - b.Property("ModuleSelections") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("module_selections") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Progress") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("progress") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionsAnsweredToday") - .HasColumnType("integer") - .HasColumnName("questions_answered_today"); - - b.Property("RecentActivities") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("recent_activities") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SelectedMajorId") - .HasColumnType("uuid") - .HasColumnName("selected_major_id"); - - b.Property("SelectedSchoolId") - .HasColumnType("uuid") - .HasColumnName("selected_school_id"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_student_profiles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_student_profiles_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_student_profiles_user_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_student_profiles_tenant_id_region_id"); - - b.HasIndex("TenantId", "SelectedMajorId") - .HasDatabaseName("ix_student_profiles_tenant_id_selected_major_id"); - - b.HasIndex("TenantId", "SelectedSchoolId") - .HasDatabaseName("ix_student_profiles_tenant_id_selected_school_id"); - - b.HasIndex("TenantId", "UserId") - .IsUnique() - .HasDatabaseName("ix_student_profiles_tenant_id_user_id"); - - b.ToTable("student_profiles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessFailedCount") - .HasColumnType("integer") - .HasColumnName("access_failed_count"); - - b.Property("AvatarUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("avatar_url"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("concurrency_stamp"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Email") - .HasMaxLength(320) - .HasColumnType("citext") - .HasColumnName("email"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean") - .HasColumnName("email_confirmed"); - - b.Property("ForcePasswordChange") - .HasColumnType("boolean") - .HasColumnName("force_password_change"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_seen_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean") - .HasColumnName("lockout_enabled"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone") - .HasColumnName("lockout_end"); - - b.Property("Name") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("NormalizedEmail") - .HasMaxLength(320) - .HasColumnType("character varying(320)") - .HasColumnName("normalized_email"); - - b.Property("NormalizedUserName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("normalized_user_name"); - - b.Property("PasswordHash") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("password_hash"); - - b.Property("Phone") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("PhoneNumber") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone_number"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean") - .HasColumnName("phone_number_confirmed"); - - b.Property("PrimaryRole") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("primary_role"); - - b.Property("RawProfile") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_profile") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Score") - .HasColumnType("integer") - .HasColumnName("score"); - - b.Property("SecurityStamp") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("security_stamp"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean") - .HasColumnName("two_factor_enabled"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("user_name"); - - b.HasKey("Id") - .HasName("pk_users"); - - b.HasIndex("LegacyId") - .IsUnique() - .HasDatabaseName("ix_users_legacy_id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("email_index"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("user_name_index"); - - b.HasIndex("Phone") - .IsUnique() - .HasDatabaseName("ix_users_phone"); - - b.ToTable("users", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.UserIdentity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Email") - .HasMaxLength(320) - .HasColumnType("citext") - .HasColumnName("email"); - - b.Property("OpenId") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("open_id"); - - b.Property("Phone") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderSubject") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("provider_subject"); - - b.Property("UnionId") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("union_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_identities"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_identities_user_id"); - - b.HasIndex("Provider", "ProviderSubject") - .IsUnique() - .HasDatabaseName("ix_user_identities_provider_provider_subject"); - - b.ToTable("user_identities", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CollectionName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("collection_name"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FieldPath") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("field_path"); - - b.Property("IssueCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("LegacyId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_id"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("RawValueSample") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("raw_value_sample"); - - b.Property("RunId") - .HasColumnType("uuid") - .HasColumnName("run_id"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_pb_import_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_pb_import_issues_tenant_id_id"); - - b.HasIndex("TenantId", "RunId") - .HasDatabaseName("ix_pb_import_issues_tenant_id_run_id"); - - b.HasIndex("RunId", "Severity", "CollectionName") - .HasDatabaseName("ix_pb_import_issues_run_id_severity_collection_name"); - - b.HasIndex("TenantId", "CollectionName", "LegacyId") - .HasDatabaseName("ix_pb_import_issues_tenant_id_collection_name_legacy_id"); - - b.ToTable("pb_import_issues", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportRun", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("SourceKind") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_kind"); - - b.Property("SourceName") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("source_name"); - - b.Property("StartedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at") - .HasDefaultValueSql("now()"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_pb_import_runs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_pb_import_runs_tenant_id_id"); - - b.HasIndex("TenantId", "Status", "StartedAt") - .HasDatabaseName("ix_pb_import_runs_tenant_id_status_started_at"); - - b.ToTable("pb_import_runs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseRawRecord", b => - { - b.Property("RunId") - .HasColumnType("uuid") - .HasColumnName("run_id"); - - b.Property("CollectionName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("collection_name"); - - b.Property("LegacyId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_id"); - - b.Property("Errors") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("errors") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("ImportedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("imported_at") - .HasDefaultValueSql("now()"); - - b.Property("Normalized") - .HasColumnType("boolean") - .HasColumnName("normalized"); - - b.Property("Record") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("record") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("RunId", "CollectionName", "LegacyId") - .HasName("pk_pb_raw_records"); - - b.HasIndex("TenantId", "RunId") - .HasDatabaseName("ix_pb_raw_records_tenant_id_run_id"); - - b.HasIndex("TenantId", "CollectionName", "LegacyId") - .HasDatabaseName("ix_pb_raw_records_tenant_id_collection_name_legacy_id"); - - b.ToTable("pb_raw_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.AnswerRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AnswerText") - .HasColumnType("text") - .HasColumnName("answer_text"); - - b.Property("AnsweredAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("answered_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsCorrect") - .HasColumnType("boolean") - .HasColumnName("is_correct"); - - b.Property("LegacyCategoryId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_category_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyQuestionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_question_id"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("SelectedOptions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("selected_options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SessionQuestionId") - .HasColumnType("uuid") - .HasColumnName("session_question_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_answer_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_answer_records_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_answer_records_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_answer_records_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PracticeSessionId", "SessionQuestionId") - .HasDatabaseName("ix_answer_records_tenant_id_practice_session_id_session_questi~"); - - b.HasIndex("TenantId", "UserId", "PracticeSessionId") - .HasDatabaseName("ix_answer_records_tenant_id_user_id_practice_session_id"); - - b.ToTable("answer_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.DashboardDailyStat", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActiveUsers") - .HasColumnType("integer") - .HasColumnName("active_users"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("NewOrders") - .HasColumnType("integer") - .HasColumnName("new_orders"); - - b.Property("NewQuestions") - .HasColumnType("integer") - .HasColumnName("new_questions"); - - b.Property("NewRevenueCents") - .HasColumnType("integer") - .HasColumnName("new_revenue_cents"); - - b.Property("NewUsers") - .HasColumnType("integer") - .HasColumnName("new_users"); - - b.Property("RebuiltAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rebuilt_at"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("StatDate") - .HasColumnType("date") - .HasColumnName("stat_date"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_dashboard_daily_stats"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_dashboard_daily_stats_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_region_id"); - - b.HasIndex("TenantId", "StatDate", "RegionId") - .IsUnique() - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_stat_date_region_id"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "StatDate", "RegionId"), false); - - b.ToTable("dashboard_daily_stats", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ExamDate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("ExamAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("exam_at"); - - b.Property("ExamName") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("exam_name"); - - b.Property("ExamType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("exam_type"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_exam_dates"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_exam_dates_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_exam_dates_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_exam_dates_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId", "ExamAt") - .HasDatabaseName("ix_exam_dates_tenant_id_school_id_exam_at"); - - b.ToTable("exam_dates", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.FavoriteQuestion", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.HasKey("TenantId", "UserId", "QuestionReferenceId") - .HasName("pk_favorite_questions"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_favorite_questions_user_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_favorite_questions_question_owner_tenant_id_question_id"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_favorite_questions_tenant_id_question_reference_id"); - - b.ToTable("favorite_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeAccessEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessMode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("access_mode"); - - b.Property("ConsumedFreeQuota") - .HasColumnType("integer") - .HasColumnName("consumed_free_quota"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementId") - .HasColumnType("uuid") - .HasColumnName("entitlement_id"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("event_type"); - - b.Property("GrantedCount") - .HasColumnType("integer") - .HasColumnName("granted_count"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("Reason") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("reason"); - - b.Property("RequestedCount") - .HasColumnType("integer") - .HasColumnName("requested_count"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_access_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_access_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_access_events_user_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .HasDatabaseName("ix_practice_access_events_tenant_id_practice_session_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_practice_access_events_tenant_id_user_id_created_at"); - - b.ToTable("practice_access_events", null, t => - { - t.HasCheckConstraint("ck_practice_access_events_consumed_free_quota", "consumed_free_quota >= 0"); - - t.HasCheckConstraint("ck_practice_access_events_granted_count", "granted_count >= 0"); - - t.HasCheckConstraint("ck_practice_access_events_requested_count", "requested_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeDailyUsage", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FreeLimit") - .HasColumnType("integer") - .HasColumnName("free_limit"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsageDate") - .ValueGeneratedOnAdd() - .HasColumnType("date") - .HasColumnName("usage_date") - .HasDefaultValueSql("current_date"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_daily_usage"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_daily_usage_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_daily_usage_user_id"); - - b.HasIndex("TenantId", "UserId", "UsageDate") - .HasDatabaseName("ix_practice_daily_usage_tenant_id_user_id_usage_date"); - - b.HasIndex("TenantId", "UserId", "UsageDate", "ScopeType", "ScopeId") - .IsUnique() - .HasDatabaseName("ix_practice_daily_usage_tenant_id_user_id_usage_date_scope_typ~"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "UserId", "UsageDate", "ScopeType", "ScopeId"), false); - - b.ToTable("practice_daily_usage", null, t => - { - t.HasCheckConstraint("ck_practice_daily_usage_free_limit", "free_limit >= 0"); - - t.HasCheckConstraint("ck_practice_daily_usage_used_count", "used_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessEntitlementId") - .HasColumnType("uuid") - .HasColumnName("access_entitlement_id"); - - b.Property("AccessMode") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("free") - .HasColumnName("access_mode"); - - b.Property("AccessSnapshot") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_snapshot") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("BlueprintId") - .HasColumnType("uuid") - .HasColumnName("blueprint_id"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("ConsumedFreeQuota") - .HasColumnType("integer") - .HasColumnName("consumed_free_quota"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("mode"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("StartedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at") - .HasDefaultValueSql("now()"); - - b.Property("TargetId") - .HasColumnType("uuid") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_sessions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_sessions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "UserId", "Id") - .HasName("ak_practice_sessions_tenant_id_user_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_sessions_user_id"); - - b.HasIndex("TenantId", "BlueprintId") - .HasDatabaseName("ix_practice_sessions_tenant_id_blueprint_id"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_sessions_tenant_id_collection_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_practice_sessions_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_practice_sessions_tenant_id_entry_id"); - - b.HasIndex("TenantId", "UserId", "StartedAt") - .HasDatabaseName("ix_practice_sessions_tenant_id_user_id_started_at"); - - b.ToTable("practice_sessions", null, t => - { - t.HasCheckConstraint("ck_practice_sessions_consumed_free_quota", "consumed_free_quota >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionQuestion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Position") - .HasColumnType("integer") - .HasColumnName("position"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("QuestionVersionId") - .HasColumnType("uuid") - .HasColumnName("question_version_id"); - - b.Property("Score") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("score"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_practice_session_questions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_questions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "PracticeSessionId", "Id") - .HasName("ak_practice_session_questions_tenant_id_practice_session_id_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId", "QuestionVersionId") - .HasDatabaseName("ix_practice_session_questions_question_owner_tenant_id_questio~"); - - b.HasIndex("TenantId", "PracticeSessionId", "Position") - .IsUnique() - .HasDatabaseName("ix_practice_session_questions_tenant_id_practice_session_id_po~"); - - b.HasIndex("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_practice_session_questions_tenant_id_question_owner_tenant_~"); - - b.ToTable("practice_session_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReport", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Accuracy") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("accuracy"); - - b.Property("AnsweredCount") - .HasColumnType("integer") - .HasColumnName("answered_count"); - - b.Property("BlueprintId") - .HasColumnType("uuid") - .HasColumnName("blueprint_id"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DurationSeconds") - .HasColumnType("integer") - .HasColumnName("duration_seconds"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("mode"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionResults") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("question_results") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Score") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("score"); - - b.Property("SectionStats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("section_stats") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("SubmittedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("submitted_at") - .HasDefaultValueSql("now()"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalQuestions") - .HasColumnType("integer") - .HasColumnName("total_questions"); - - b.Property("TotalScore") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("total_score"); - - b.Property("UnansweredCount") - .HasColumnType("integer") - .HasColumnName("unanswered_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.Property("WrongQuestionIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("wrong_question_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.HasKey("Id") - .HasName("pk_practice_session_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_reports_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_session_reports_user_id"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_session_reports_tenant_id_collection_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .IsUnique() - .HasDatabaseName("ix_practice_session_reports_tenant_id_practice_session_id"); - - b.HasIndex("TenantId", "BlueprintId", "SubmittedAt") - .HasDatabaseName("ix_practice_session_reports_tenant_id_blueprint_id_submitted_at") - .HasFilter("blueprint_id is not null"); - - b.HasIndex("TenantId", "UserId", "SubmittedAt") - .HasDatabaseName("ix_practice_session_reports_tenant_id_user_id_submitted_at"); - - b.ToTable("practice_session_reports", null, t => - { - t.HasCheckConstraint("ck_practice_session_reports_accuracy", "accuracy >= 0 and accuracy <= 1"); - - t.HasCheckConstraint("ck_practice_session_reports_counts", "total_questions >= 0 and answered_count >= 0 and correct_count >= 0 and wrong_count >= 0 and unanswered_count >= 0"); - - t.HasCheckConstraint("ck_practice_session_reports_duration", "duration_seconds >= 0"); - - t.HasCheckConstraint("ck_practice_session_reports_scores", "score >= 0 and total_score >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReportSection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Accuracy") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("accuracy"); - - b.Property("AnsweredCount") - .HasColumnType("integer") - .HasColumnName("answered_count"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("QuestionType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("question_type"); - - b.Property("ReportId") - .HasColumnType("uuid") - .HasColumnName("report_id"); - - b.Property("Score") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("score"); - - b.Property("SectionKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("section_key"); - - b.Property("SectionName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("section_name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("total_score"); - - b.Property("UnansweredCount") - .HasColumnType("integer") - .HasColumnName("unanswered_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.HasKey("Id") - .HasName("pk_practice_session_report_sections"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_report_sections_tenant_id_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_practice_session~"); - - b.HasIndex("TenantId", "ReportId", "SectionKey") - .IsUnique() - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_report_id_sectio~"); - - b.HasIndex("TenantId", "ReportId", "SortOrder") - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_report_id_sort_o~"); - - b.ToTable("practice_session_report_sections", null, t => - { - t.HasCheckConstraint("ck_practice_session_report_sections_accuracy", "accuracy >= 0 and accuracy <= 1"); - - t.HasCheckConstraint("ck_practice_session_report_sections_counts", "question_count >= 0 and answered_count >= 0 and correct_count >= 0 and wrong_count >= 0 and unanswered_count >= 0"); - - t.HasCheckConstraint("ck_practice_session_report_sections_scores", "score >= 0 and total_score >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RecentPractice", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LastAccessAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_access_at"); - - b.Property("LastPracticeAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_practice_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("practice_type"); - - b.Property("Progress") - .HasColumnType("integer") - .HasColumnName("progress"); - - b.Property("TargetLegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("target_legacy_id"); - - b.Property("TargetName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("target_name"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_recent_practices"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_recent_practices_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_recent_practices_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_recent_practices_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UserId", "LastAccessAt") - .HasDatabaseName("ix_recent_practices_tenant_id_user_id_last_access_at"); - - b.ToTable("recent_practices", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.Report", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attachments") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("attachments") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("Contact") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("contact"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("HandledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("handled_at"); - - b.Property("HandledBy") - .HasColumnType("uuid") - .HasColumnName("handled_by"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("priority"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("Resolution") - .HasColumnType("text") - .HasColumnName("resolution"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_reports_tenant_id_id"); - - b.HasIndex("HandledBy") - .HasDatabaseName("ix_reports_handled_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_reports_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_reports_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "QuestionId", "CreatedAt") - .HasDatabaseName("ix_reports_tenant_id_question_id_created_at") - .HasFilter("question_id is not null"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_reports_tenant_id_status_created_at"); - - b.ToTable("reports", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ReportStatusEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("ReportId") - .HasColumnType("uuid") - .HasColumnName("report_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_report_status_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_report_status_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_report_status_events_actor_user_id"); - - b.HasIndex("TenantId", "ReportId", "CreatedAt") - .HasDatabaseName("ix_report_status_events_tenant_id_report_id_created_at"); - - b.ToTable("report_status_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RevenueDailyStat", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CodeCount") - .HasColumnType("integer") - .HasColumnName("code_count"); - - b.Property("CodeEstimated") - .HasColumnType("integer") - .HasColumnName("code_estimated"); - - b.Property("CodeUsed") - .HasColumnType("integer") - .HasColumnName("code_used"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EstimatedRevenueCents") - .HasColumnType("integer") - .HasColumnName("estimated_revenue_cents"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("OrderCount") - .HasColumnType("integer") - .HasColumnName("order_count"); - - b.Property("RealRevenueCents") - .HasColumnType("integer") - .HasColumnName("real_revenue_cents"); - - b.Property("RebuiltAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rebuilt_at"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("StatDate") - .HasColumnType("date") - .HasColumnName("stat_date"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_revenue_daily_stats"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_revenue_daily_stats_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_region_id"); - - b.HasIndex("TenantId", "StatDate", "RegionId", "SaleType") - .IsUnique() - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_stat_date_region_id_sale_type"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "StatDate", "RegionId", "SaleType"), false); - - b.ToTable("revenue_daily_stats", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.UserScoreEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BalanceAfter") - .HasColumnType("integer") - .HasColumnName("balance_after"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("event_type"); - - b.Property("IdempotencyKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("idempotency_key"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_score_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_score_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_score_events_user_id"); - - b.HasIndex("TenantId", "IdempotencyKey") - .IsUnique() - .HasDatabaseName("ix_user_score_events_tenant_id_idempotency_key"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_user_score_events_tenant_id_user_id_created_at"); - - b.ToTable("user_score_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.WrongQuestion", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("LastWrongAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_wrong_at") - .HasDefaultValueSql("now()"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("WrongCount") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(1) - .HasColumnName("wrong_count"); - - b.HasKey("TenantId", "UserId", "QuestionReferenceId") - .HasName("pk_wrong_questions"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_wrong_questions_user_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_wrong_questions_question_owner_tenant_id_question_id"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_wrong_questions_tenant_id_question_reference_id"); - - b.ToTable("wrong_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Announcement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BackgroundColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("background_color"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Link") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("link"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_announcements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_announcements_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_announcements_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "IsActive", "SortOrder") - .HasDatabaseName("ix_announcements_tenant_id_is_active_sort_order"); - - b.ToTable("announcements", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.AuditLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Action") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("action"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("TargetId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_audit_logs"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_audit_logs_actor_user_id"); - - b.HasIndex("TenantId", "CreatedAt") - .HasDatabaseName("ix_audit_logs_tenant_id_created_at"); - - b.HasIndex("TenantId", "TargetType", "TargetId", "CreatedAt") - .HasDatabaseName("ix_audit_logs_tenant_id_target_type_target_id_created_at"); - - b.ToTable("audit_logs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Area") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("area"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Icon") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("ParentCode") - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("parent_code"); - - b.Property("Path") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("path"); - - b.Property("PermissionCode") - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_backend_menus"); - - b.HasAlternateKey("Code") - .HasName("ak_backend_menus_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_backend_menus_code"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_backend_menus_permission_code"); - - b.HasIndex("Area", "ParentCode", "SortOrder") - .HasDatabaseName("ix_backend_menus_area_parent_code_sort_order"); - - b.ToTable("backend_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendPermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Area") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("area"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Module") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("module"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_backend_permissions"); - - b.HasAlternateKey("Code") - .HasName("ak_backend_permissions_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_backend_permissions_code"); - - b.HasIndex("Area", "Module", "SortOrder") - .HasDatabaseName("ix_backend_permissions_area_module_sort_order"); - - b.ToTable("backend_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("JobType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("job_type"); - - b.Property("LastError") - .HasMaxLength(4000) - .HasColumnType("character varying(4000)") - .HasColumnName("last_error"); - - b.Property("LockExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("lock_expires_at"); - - b.Property("LockedBy") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("locked_by"); - - b.Property("MaxRetries") - .HasColumnType("integer") - .HasColumnName("max_retries"); - - b.Property("OutputAssetId") - .HasColumnType("uuid") - .HasColumnName("output_asset_id"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Result") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("result") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RetryCount") - .HasColumnType("integer") - .HasColumnName("retry_count"); - - b.Property("RunAfter") - .HasColumnType("timestamp with time zone") - .HasColumnName("run_after"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_background_jobs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_background_jobs_tenant_id_id"); - - b.HasIndex("TenantId", "OutputAssetId") - .HasDatabaseName("ix_background_jobs_tenant_id_output_asset_id"); - - b.HasIndex("TenantId", "JobType", "Status", "CreatedAt") - .HasDatabaseName("ix_background_jobs_tenant_id_job_type_status_created_at"); - - b.HasIndex("TenantId", "Status", "RunAfter", "CreatedAt") - .HasDatabaseName("ix_background_jobs_tenant_id_status_run_after_created_at"); - - b.ToTable("background_jobs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Badge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("ConditionExtra") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("condition_extra") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ConditionField") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("condition_field"); - - b.Property("ConditionOperator") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("condition_operator"); - - b.Property("ConditionValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)") - .HasColumnName("condition_value"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IconUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon_url"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Level") - .HasColumnType("integer") - .HasColumnName("level"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnlockType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("unlock_type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_badges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_badges_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_badges_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "Category", "IsActive", "SortOrder") - .HasDatabaseName("ix_badges_tenant_id_category_is_active_sort_order"); - - b.ToTable("badges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Banner", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BackgroundColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("background_color"); - - b.Property("BorderColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("border_color"); - - b.Property("ButtonLink") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("button_link"); - - b.Property("ButtonText") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("button_text"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Subtitle") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("subtitle"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_banners"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_banners_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_banners_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_banners_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("banners", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Faq", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Answer") - .HasColumnType("text") - .HasColumnName("answer"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Question") - .HasColumnType("text") - .HasColumnName("question"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_faqs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_faqs_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_faqs_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_faqs_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("faqs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_backend_roles"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_backend_roles_code"); - - b.HasIndex("Status", "Code") - .HasDatabaseName("ix_platform_backend_roles_status_code"); - - b.ToTable("platform_backend_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("MenuCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("menu_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_role_menus"); - - b.HasIndex("MenuCode") - .HasDatabaseName("ix_platform_backend_role_menus_menu_code"); - - b.HasIndex("RoleId", "MenuCode") - .IsUnique() - .HasDatabaseName("ix_platform_backend_role_menus_role_id_menu_code"); - - b.ToTable("platform_backend_role_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("PermissionCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_role_permissions"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_platform_backend_role_permissions_permission_code"); - - b.HasIndex("RoleId", "PermissionCode") - .IsUnique() - .HasDatabaseName("ix_platform_backend_role_permissions_role_id_permission_code"); - - b.ToTable("platform_backend_role_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_user_roles"); - - b.HasIndex("RoleId") - .HasDatabaseName("ix_platform_backend_user_roles_role_id"); - - b.HasIndex("UserId", "RoleId") - .IsUnique() - .HasDatabaseName("ix_platform_backend_user_roles_user_id_role_id"); - - b.ToTable("platform_backend_user_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DataScope") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("data_scope") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_roles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_roles_tenant_id_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_roles_tenant_id_code"); - - b.HasIndex("TenantId", "Status", "Code") - .HasDatabaseName("ix_tenant_backend_roles_tenant_id_status_code"); - - b.ToTable("tenant_backend_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("MenuCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("menu_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_role_menus"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_role_menus_tenant_id_id"); - - b.HasIndex("MenuCode") - .HasDatabaseName("ix_tenant_backend_role_menus_menu_code"); - - b.HasIndex("TenantId", "RoleId", "MenuCode") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_role_menus_tenant_id_role_id_menu_code"); - - b.ToTable("tenant_backend_role_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("PermissionCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_role_permissions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_role_permissions_tenant_id_id"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_tenant_backend_role_permissions_permission_code"); - - b.HasIndex("TenantId", "RoleId", "PermissionCode") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_role_permissions_tenant_id_role_id_permissio~"); - - b.ToTable("tenant_backend_role_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_user_roles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_user_roles_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_backend_user_roles_user_id"); - - b.HasIndex("TenantId", "RoleId") - .HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_role_id"); - - b.HasIndex("TenantId", "UserId", "RoleId") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_user_id_role_id"); - - b.ToTable("tenant_backend_user_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActionLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("action_label"); - - b.Property("ActionPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("action_path"); - - b.Property("AdoptionId") - .HasColumnType("uuid") - .HasColumnName("adoption_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DedupeKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("dedupe_key"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NotificationType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("notification_type"); - - b.Property("ReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("read_at"); - - b.Property("ReadBy") - .HasColumnType("uuid") - .HasColumnName("read_by"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("SourceQuestionBankId") - .HasColumnType("uuid") - .HasColumnName("source_question_bank_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_content_notifications"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_content_notifications_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_content_notifications_created_by"); - - b.HasIndex("ReadBy") - .HasDatabaseName("ix_tenant_content_notifications_read_by"); - - b.HasIndex("TenantId", "SourceQuestionBankId") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_source_question_bank~"); - - b.HasIndex("TenantId", "NotificationType", "DedupeKey") - .IsUnique() - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_notification_type_de~") - .HasFilter("dedupe_key is not null"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_status_created_at"); - - b.HasIndex("TenantId", "AdoptionId", "NotificationType", "Status") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_adoption_id_notifica~"); - - b.ToTable("tenant_content_notifications", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActivePublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("active_public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ActiveTemplateCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("active_template_code"); - - b.Property("ActiveTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("active_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DraftPublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftTemplateCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("draft_template_code"); - - b.Property("DraftTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftUpdatedBy") - .HasColumnType("uuid") - .HasColumnName("draft_updated_by"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublishedBy") - .HasColumnType("uuid") - .HasColumnName("published_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_theme_configs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_theme_configs_tenant_id_id"); - - b.HasIndex("ActiveTemplateCode") - .HasDatabaseName("ix_tenant_theme_configs_active_template_code"); - - b.HasIndex("DraftTemplateCode") - .HasDatabaseName("ix_tenant_theme_configs_draft_template_code"); - - b.HasIndex("DraftUpdatedBy") - .HasDatabaseName("ix_tenant_theme_configs_draft_updated_by"); - - b.HasIndex("PublishedBy") - .HasDatabaseName("ix_tenant_theme_configs_published_by"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_theme_configs_tenant_id"); - - b.ToTable("tenant_theme_configs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("PreviewImageUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_image_url"); - - b.Property("PublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Theme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_theme_templates"); - - b.HasAlternateKey("Code") - .HasName("ak_tenant_theme_templates_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_tenant_theme_templates_code"); - - b.HasIndex("Status", "SortOrder", "Code") - .HasDatabaseName("ix_tenant_theme_templates_status_sort_order_code"); - - b.ToTable("tenant_theme_templates", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserBadge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BadgeId") - .HasColumnType("uuid") - .HasColumnName("badge_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("GrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("granted_at"); - - b.Property("GrantedBy") - .HasColumnType("uuid") - .HasColumnName("granted_by"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_badges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_badges_tenant_id_id"); - - b.HasIndex("GrantedBy") - .HasDatabaseName("ix_user_badges_granted_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_badges_user_id"); - - b.HasIndex("TenantId", "BadgeId") - .HasDatabaseName("ix_user_badges_tenant_id_badge_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_user_badges_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UserId", "BadgeId") - .IsUnique() - .HasDatabaseName("ix_user_badges_tenant_id_user_id_badge_id"); - - b.ToTable("user_badges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserNotification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActionLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("action_label"); - - b.Property("ActionPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("action_path"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DedupeKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("dedupe_key"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NotificationType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("notification_type"); - - b.Property("ReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("read_at"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_notifications"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_notifications_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_user_notifications_created_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_notifications_user_id"); - - b.HasIndex("TenantId", "SourceType", "SourceId") - .HasDatabaseName("ix_user_notifications_tenant_id_source_type_source_id"); - - b.HasIndex("TenantId", "UserId", "NotificationType", "DedupeKey") - .IsUnique() - .HasDatabaseName("ix_user_notifications_tenant_id_user_id_notification_type_dedu~") - .HasFilter("dedupe_key is not null"); - - b.HasIndex("TenantId", "UserId", "Status", "CreatedAt") - .HasDatabaseName("ix_user_notifications_tenant_id_user_id_status_created_at"); - - b.ToTable("user_notifications", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AcknowledgedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("acknowledged_at"); - - b.Property("AcknowledgedBy") - .HasColumnType("uuid") - .HasColumnName("acknowledged_by"); - - b.Property("Action") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("action"); - - b.Property("AuditLogId") - .HasColumnType("uuid") - .HasColumnName("audit_log_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FirstSeenAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("first_seen_at") - .HasDefaultValueSql("now()"); - - b.Property("LastSeenAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_seen_at") - .HasDefaultValueSql("now()"); - - b.Property("ResolutionNote") - .HasColumnType("text") - .HasColumnName("resolution_note"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("ResolvedBy") - .HasColumnType("uuid") - .HasColumnName("resolved_by"); - - b.Property("RuleId") - .HasColumnType("uuid") - .HasColumnName("rule_id"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .HasColumnType("text") - .HasColumnName("summary"); - - b.Property("TargetId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_audit_alerts"); - - b.HasIndex("AcknowledgedBy") - .HasDatabaseName("ix_platform_audit_alerts_acknowledged_by"); - - b.HasIndex("AuditLogId") - .HasDatabaseName("ix_platform_audit_alerts_audit_log_id"); - - b.HasIndex("ResolvedBy") - .HasDatabaseName("ix_platform_audit_alerts_resolved_by"); - - b.HasIndex("RuleId", "AuditLogId") - .IsUnique() - .HasDatabaseName("ix_platform_audit_alerts_rule_id_audit_log_id"); - - b.HasIndex("Status", "Severity", "CreatedAt") - .HasDatabaseName("ix_platform_audit_alerts_status_severity_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_audit_alerts_tenant_id_status_created_at"); - - b.ToTable("platform_audit_alerts", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlertRule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.PrimitiveCollection("ActionPatterns") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("action_patterns") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("Conditions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("conditions") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.PrimitiveCollection("TargetTypes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("target_types") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_audit_alert_rules"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_audit_alert_rules_code"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_platform_audit_alert_rules_tenant_id"); - - b.HasIndex("Enabled", "Severity", "Code") - .HasDatabaseName("ix_platform_audit_alert_rules_enabled_severity_code"); - - b.ToTable("platform_audit_alert_rules", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationChannel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ChannelCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel_code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MinReminderLevel") - .HasColumnType("integer") - .HasColumnName("min_reminder_level"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("provider"); - - b.PrimitiveCollection("ReminderChannels") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("reminder_channels") - .HasDefaultValueSql("array['internal']::text[]"); - - b.PrimitiveCollection("ReminderTypes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("reminder_types") - .HasDefaultValueSql("array['overdue', 'final_notice']::text[]"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.PrimitiveCollection("TenantIds") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("uuid[]") - .HasColumnName("tenant_ids") - .HasDefaultValueSql("'{}'::uuid[]"); - - b.Property("TimeoutSeconds") - .HasColumnType("integer") - .HasColumnName("timeout_seconds"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WebhookUrl") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("webhook_url"); - - b.HasKey("Id") - .HasName("pk_platform_dunning_notification_channels"); - - b.HasIndex("ChannelCode") - .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_channels_channel_code"); - - b.HasIndex("Enabled", "MinReminderLevel", "ChannelCode") - .HasDatabaseName("ix_platform_dunning_notification_channels_enabled_min_reminder~"); - - b.ToTable("platform_dunning_notification_channels", null, t => - { - t.HasCheckConstraint("ck_platform_dunning_channels_level", "min_reminder_level between 1 and 20"); - - t.HasCheckConstraint("ck_platform_dunning_channels_timeout", "timeout_seconds between 1 and 60"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("ChannelId") - .HasColumnType("uuid") - .HasColumnName("channel_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("LastAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_attempt_at"); - - b.Property("LastError") - .HasColumnType("text") - .HasColumnName("last_error"); - - b.Property("LastHttpCode") - .HasColumnType("integer") - .HasColumnName("last_http_code"); - - b.Property("LastResponseSummary") - .HasColumnType("text") - .HasColumnName("last_response_summary"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NextAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_attempt_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("provider"); - - b.Property("ReminderId") - .HasColumnType("uuid") - .HasColumnName("reminder_id"); - - b.Property("RequestPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("request_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ScheduledAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("scheduled_at") - .HasDefaultValueSql("now()"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_dunning_notification_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_platform_dunning_notification_events_tenant_id_id"); - - b.HasIndex("ChannelId") - .HasDatabaseName("ix_platform_dunning_notification_events_channel_id"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_invoice_id"); - - b.HasIndex("TenantId", "ReminderId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_reminder_id"); - - b.HasIndex("InvoiceId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_invoice_id_status_crea~"); - - b.HasIndex("ReminderId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_reminder_id_status_cre~"); - - b.HasIndex("TenantId", "ChannelId", "ReminderId") - .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_channel_id_r~"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_status_creat~"); - - b.HasIndex("Status", "NextAttemptAt", "ScheduledAt", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_status_next_attempt_at~"); - - b.ToTable("platform_dunning_notification_events", null, t => - { - t.HasCheckConstraint("ck_platform_dunning_events_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformSaasPlan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BaseAmountCents") - .HasColumnType("integer") - .HasColumnName("base_amount_cents"); - - b.Property("BillingCycle") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("billing_cycle"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("FeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("IncludedQuotas") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("included_quotas") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OveragePrices") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("overage_prices") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_saas_plans"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_saas_plans_code"); - - b.HasIndex("Status", "SortOrder") - .HasDatabaseName("ix_platform_saas_plans_status_sort_order"); - - b.ToTable("platform_saas_plans", null, t => - { - t.HasCheckConstraint("ck_platform_saas_plans_amount", "base_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("BankAccountMasked") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("bank_account_masked"); - - b.Property("BankName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("bank_name"); - - b.Property("BillingAddress") - .HasColumnType("text") - .HasColumnName("billing_address"); - - b.Property("BillingName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("billing_name"); - - b.Property("ContactEmail") - .HasMaxLength(300) - .HasColumnType("citext") - .HasColumnName("contact_email"); - - b.Property("ContactName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("contact_name"); - - b.Property("ContactPhone") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("contact_phone"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceTitle") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("invoice_title"); - - b.Property("InvoiceType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TaxId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("tax_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_billing_profiles"); - - b.ToTable("tenant_billing_profiles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BalanceCents") - .HasColumnType("integer") - .HasColumnName("balance_cents"); - - b.Property("BillingPeriodEnd") - .HasColumnType("date") - .HasColumnName("billing_period_end"); - - b.Property("BillingPeriodStart") - .HasColumnType("date") - .HasColumnName("billing_period_start"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("DiscountCents") - .HasColumnType("integer") - .HasColumnName("discount_cents"); - - b.Property("DueDate") - .HasColumnType("date") - .HasColumnName("due_date"); - - b.Property("InvoiceNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("invoice_no"); - - b.Property("InvoiceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - - b.Property("IssuedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("issued_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaidCents") - .HasColumnType("integer") - .HasColumnName("paid_cents"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubtotalCents") - .HasColumnType("integer") - .HasColumnName("subtotal_cents"); - - b.Property("TaxCents") - .HasColumnType("integer") - .HasColumnName("tax_cents"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCents") - .HasColumnType("integer") - .HasColumnName("total_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoices"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoices_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoices_created_by"); - - b.HasIndex("TenantId", "InvoiceNo") - .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_invoice_no"); - - b.HasIndex("TenantId", "BillingPeriodStart", "BillingPeriodEnd") - .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_billing_period_start_billing_peri~") - .HasFilter("invoice_type = 'usage_overage' and status <> 'void' and metadata->>'source' = 'usage_overage_auto'"); - - b.HasIndex("TenantId", "Status", "DueDate") - .HasDatabaseName("ix_tenant_invoices_tenant_id_status_due_date"); - - b.ToTable("tenant_invoices", null, t => - { - t.HasCheckConstraint("ck_tenant_invoices_amounts", "subtotal_cents >= 0 and discount_cents >= 0 and tax_cents >= 0 and total_cents >= 0 and paid_cents >= 0 and balance_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoices_period", "billing_period_end is null or billing_period_start is null or billing_period_end >= billing_period_start"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("description"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("ItemRefId") - .HasColumnType("uuid") - .HasColumnName("item_ref_id"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Quantity") - .HasPrecision(12, 2) - .HasColumnType("numeric(12,2)") - .HasColumnName("quantity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitAmountCents") - .HasColumnType("integer") - .HasColumnName("unit_amount_cents"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_items_tenant_id_id"); - - b.HasIndex("InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_invoice_id"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_tenant_id_invoice_id"); - - b.ToTable("tenant_invoice_items", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_items_amounts", "unit_amount_cents >= 0 and amount_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoice_items_quantity", "quantity > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("Method") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("method"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_no"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ReceivedBy") - .HasColumnType("uuid") - .HasColumnName("received_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_payments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_payments_tenant_id_id"); - - b.HasIndex("ReceivedBy") - .HasDatabaseName("ix_tenant_invoice_payments_received_by"); - - b.HasIndex("InvoiceId", "Status") - .HasDatabaseName("ix_tenant_invoice_payments_invoice_id_status"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_invoice_id"); - - b.HasIndex("TenantId", "PaymentNo") - .IsUnique() - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_payment_no"); - - b.ToTable("tenant_invoice_payments", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_payments_amount", "amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AcknowledgedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("acknowledged_at"); - - b.Property("BalanceCentsSnapshot") - .HasColumnType("integer") - .HasColumnName("balance_cents_snapshot"); - - b.Property("Channel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("channel"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DueDate") - .HasColumnType("date") - .HasColumnName("due_date"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("Message") - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ReminderDate") - .HasColumnType("date") - .HasColumnName("reminder_date"); - - b.Property("ReminderLevel") - .HasColumnType("integer") - .HasColumnName("reminder_level"); - - b.Property("ReminderType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("reminder_type"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_reminders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_reminders_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoice_reminders_created_by"); - - b.HasIndex("InvoiceId", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_invoice_id_reminder_date"); - - b.HasIndex("TenantId", "Status", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_status_reminder_date"); - - b.HasIndex("TenantId", "InvoiceId", "ReminderType", "Channel", "ReminderDate") - .IsUnique() - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_invoice_id_reminder_type~"); - - b.ToTable("tenant_invoice_reminders", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_reminders_balance", "balance_cents_snapshot >= 0"); - - t.HasCheckConstraint("ck_tenant_invoice_reminders_level", "reminder_level between 1 and 20"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CurrentVersionId") - .HasColumnType("uuid") - .HasColumnName("current_version_id"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("ExamMarkers") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("exam_markers") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("HasVideoExplanation") - .HasColumnType("boolean") - .HasColumnName("has_video_explanation"); - - b.Property("LegacyCategoryId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_category_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyNodeId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_node_id"); - - b.Property("LegacySubjectId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_subject_id"); - - b.Property("MediaUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("media_url"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("PrimaryCollectionId") - .HasColumnType("uuid") - .HasColumnName("primary_collection_id"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("type"); - - b.Property("TypeLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("type_label"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_questions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_questions_tenant_id_id"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_questions_tenant_id_category_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_questions_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_questions_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_questions_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_questions_tenant_id_node_id"); - - b.HasIndex("TenantId", "PrimaryCollectionId") - .HasDatabaseName("ix_questions_tenant_id_primary_collection_id"); - - b.HasIndex("TenantId", "QuestionBankId") - .HasDatabaseName("ix_questions_tenant_id_question_bank_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_questions_tenant_id_subject_id"); - - b.HasIndex("TenantId", "Id", "CurrentVersionId") - .HasDatabaseName("ix_questions_tenant_id_id_current_version_id"); - - b.ToTable("questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionBank", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_banks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_banks_tenant_id_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_question_banks_tenant_id_region_id"); - - b.ToTable("question_banks", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AnswerText") - .HasColumnType("text") - .HasColumnName("answer_text"); - - b.Property("CodeLang") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("code_lang"); - - b.Property("CodeTemplate") - .HasColumnType("text") - .HasColumnName("code_template"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CorrectOptionIndex") - .HasColumnType("integer") - .HasColumnName("correct_option_index"); - - b.Property("CorrectOptionIndices") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("correct_option_indices") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Explanation") - .HasColumnType("text") - .HasColumnName("explanation"); - - b.Property("Options") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubQuestions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("sub_questions") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("VersionNo") - .HasColumnType("integer") - .HasColumnName("version_no"); - - b.HasKey("Id") - .HasName("pk_question_versions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_versions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "QuestionId", "Id") - .HasName("ak_question_versions_tenant_id_question_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_question_versions_created_by"); - - b.HasIndex("TenantId", "QuestionId", "VersionNo") - .IsUnique() - .HasDatabaseName("ix_question_versions_tenant_id_question_id_version_no"); - - b.ToTable("question_versions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ConsumedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("consumed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("purpose"); - - b.Property("Realm") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("realm"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("security_stamp"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("token_hash"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_challenges"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_auth_challenges_tenant_id"); - - b.HasIndex("TokenHash") - .IsUnique() - .HasDatabaseName("ix_auth_challenges_token_hash"); - - b.HasIndex("UserId", "Purpose", "ExpiresAt") - .HasDatabaseName("ix_auth_challenges_user_id_purpose_expires_at"); - - b.ToTable("auth_challenges", null, t => - { - t.HasCheckConstraint("ck_auth_challenges_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FailureCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("failure_code"); - - b.Property("Identifier") - .HasMaxLength(320) - .HasColumnType("character varying(320)") - .HasColumnName("identifier"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Result") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("result"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_login_events"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_auth_login_events_user_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_auth_login_events_tenant_id_user_id_created_at"); - - b.ToTable("auth_login_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MfaSatisfied") - .HasColumnType("boolean") - .HasColumnName("mfa_satisfied"); - - b.Property("ParentSessionId") - .HasColumnType("uuid") - .HasColumnName("parent_session_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Realm") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("realm"); - - b.Property("ReplacedBySessionId") - .HasColumnType("uuid") - .HasColumnName("replaced_by_session_id"); - - b.Property("RevokedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("revoked_at"); - - b.Property("RevokedReason") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("revoked_reason"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("security_stamp"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TokenFamilyId") - .HasColumnType("uuid") - .HasColumnName("token_family_id"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("token_hash"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_sessions"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_auth_sessions_tenant_id"); - - b.HasIndex("TokenHash") - .IsUnique() - .HasDatabaseName("ix_auth_sessions_token_hash") - .HasAnnotation("Tiku:GlobalUnique", true); - - b.HasIndex("UserId") - .HasDatabaseName("ix_auth_sessions_user_id"); - - b.HasIndex("TokenFamilyId", "RevokedAt") - .HasDatabaseName("ix_auth_sessions_token_family_id_revoked_at"); - - b.HasIndex("Realm", "TenantId", "UserId", "ExpiresAt") - .HasDatabaseName("ix_auth_sessions_realm_tenant_id_user_id_expires_at") - .HasFilter("revoked_at is null"); - - b.ToTable("auth_sessions", null, t => - { - t.HasCheckConstraint("ck_auth_sessions_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsSendRateLimit", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Dimension") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("dimension"); - - b.Property("ScopeHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("scope_hash"); - - b.Property("BucketStart") - .HasColumnType("timestamp with time zone") - .HasColumnName("bucket_start"); - - b.Property("RequestCount") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(0) - .HasColumnName("request_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId", "Dimension", "ScopeHash", "BucketStart") - .HasName("pk_sms_send_rate_limits"); - - b.HasIndex("UpdatedAt") - .HasDatabaseName("ix_sms_send_rate_limits_updated_at"); - - b.ToTable("sms_send_rate_limits", null, t => - { - t.HasCheckConstraint("ck_sms_send_rate_limits_request_count", "request_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsVerificationCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("CodeHash") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("code_hash"); - - b.Property("ConsumedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("consumed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Phone") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("purpose"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_sms_verification_codes"); - - b.HasIndex("TenantId", "Phone", "Purpose") - .IsUnique() - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose") - .HasFilter("consumed_at is null and status in ('pending', 'sent')"); - - b.HasIndex("TenantId", "Phone", "Purpose", "CreatedAt") - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose_created_at") - .HasFilter("consumed_at is null and status in ('pending', 'sent')"); - - b.HasIndex("TenantId", "Phone", "Purpose", "ExpiresAt") - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose_expires_at"); - - b.ToTable("sms_verification_codes", null, t => - { - t.HasCheckConstraint("ck_sms_verification_codes_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.Tenant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BillingStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("billing_status"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegalName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("legal_name"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("mode"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OwnerUserId") - .HasColumnType("uuid") - .HasColumnName("owner_user_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("slug"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenants"); - - b.HasIndex("LegacyId") - .IsUnique() - .HasDatabaseName("ix_tenants_legacy_id"); - - b.HasIndex("Mode") - .IsUnique() - .HasDatabaseName("ix_tenants_mode") - .HasFilter("mode = 'platform_owned'"); - - b.HasIndex("OwnerUserId") - .HasDatabaseName("ix_tenants_owner_user_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_tenants_slug"); - - b.ToTable("tenants", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantBranding", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("BrandName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("brand_name"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FaviconUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("favicon_url"); - - b.Property("LogoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("logo_url"); - - b.Property("OrganizationName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("organization_name"); - - b.Property("PublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ServiceAccountName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("service_account_name"); - - b.Property("ServiceWechat") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("service_wechat"); - - b.Property("ShortName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("short_name"); - - b.Property("Slogan") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("slogan"); - - b.Property("Theme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_branding"); - - b.ToTable("tenant_branding", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClass", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_classes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_classes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_classes_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_classes_updated_by"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_tenant_classes_tenant_id_code") - .HasFilter("code is not null and code <> ''"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_tenant_classes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_tenant_classes_tenant_id_region_id") - .HasFilter("region_id is not null"); - - b.HasIndex("TenantId", "Status", "SortOrder", "CreatedAt") - .HasDatabaseName("ix_tenant_classes_tenant_id_status_sort_order_created_at"); - - b.ToTable("tenant_classes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClassMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClassId") - .HasColumnType("uuid") - .HasColumnName("class_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("JoinedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at") - .HasDefaultValueSql("now()"); - - b.Property("LeftAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("left_at"); - - b.Property("MemberType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("member_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_class_members"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_class_members_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_class_members_user_id"); - - b.HasIndex("TenantId", "ClassId", "Status", "MemberType") - .HasDatabaseName("ix_tenant_class_members_tenant_id_class_id_status_member_type"); - - b.HasIndex("TenantId", "ClassId", "UserId", "MemberType") - .IsUnique() - .HasDatabaseName("ix_tenant_class_members_tenant_id_class_id_user_id_member_type"); - - b.HasIndex("TenantId", "UserId", "Status", "MemberType") - .HasDatabaseName("ix_tenant_class_members_tenant_id_user_id_status_member_type"); - - b.ToTable("tenant_class_members", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantDomain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DnsVerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("dns_verified_at"); - - b.Property("DomainType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("domain_type"); - - b.Property("Host") - .IsRequired() - .HasMaxLength(253) - .HasColumnType("citext") - .HasColumnName("host"); - - b.Property("IsPrimary") - .HasColumnType("boolean") - .HasColumnName("is_primary"); - - b.Property("LastCheckedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_checked_at"); - - b.Property("LastFailureReason") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("last_failure_reason"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TlsReadyAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("tls_ready_at"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VerificationToken") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("verification_token"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_tenant_domains"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_domains_tenant_id_id"); - - b.HasIndex("Host") - .IsUnique() - .HasDatabaseName("ix_tenant_domains_host") - .HasAnnotation("Tiku:GlobalUnique", true); - - b.HasIndex("TenantId", "IsPrimary") - .IsUnique() - .HasDatabaseName("ix_tenant_domains_tenant_id_is_primary") - .HasFilter("is_primary"); - - b.ToTable("tenant_domains", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantExternalProvider", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Capability") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("capability"); - - b.Property("ConfigPublic") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config_public") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("display_name"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_external_providers"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_external_providers_tenant_id_id"); - - b.HasIndex("TenantId", "Capability", "Provider") - .IsUnique() - .HasDatabaseName("ix_tenant_external_providers_tenant_id_capability_provider"); - - b.HasIndex("TenantId", "Capability", "Status", "Priority") - .HasDatabaseName("ix_tenant_external_providers_tenant_id_capability_status_prior~"); - - b.ToTable("tenant_external_providers", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantFrontendConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ConfigVersion") - .IsConcurrencyToken() - .HasColumnType("integer") - .HasColumnName("config_version"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DraftBranding") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_branding") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftFeatures") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_features") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftHomeModules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_home_modules") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("DraftNavigation") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_navigation") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("DraftTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublishedBranding") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_branding") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedFeatures") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_features") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedHomeModules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_home_modules") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("PublishedNavigation") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_navigation") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("PublishedTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SchemaVersion") - .HasColumnType("integer") - .HasColumnName("schema_version"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_frontend_configs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_frontend_configs_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_frontend_configs_tenant_id"); - - b.ToTable("tenant_frontend_configs", null, t => - { - t.HasCheckConstraint("ck_tenant_frontend_configs_config_version", "config_version > 0"); - - t.HasCheckConstraint("ck_tenant_frontend_configs_schema_version", "schema_version > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantMembership", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyRole") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("legacy_role"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("role"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_memberships"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_memberships_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_memberships_user_id"); - - b.HasIndex("TenantId", "UserId", "Role") - .IsUnique() - .HasDatabaseName("ix_tenant_memberships_tenant_id_user_id_role"); - - b.ToTable("tenant_memberships", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EncryptedPayload") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encrypted_payload"); - - b.Property("EncryptionKeyId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("encryption_key_id"); - - b.Property("EncryptionNonce") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encryption_nonce"); - - b.Property("EncryptionTag") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encryption_tag"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)") - .HasColumnName("purpose"); - - b.Property("RotatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rotated_at"); - - b.Property("SecretKey") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)") - .HasColumnName("secret_key"); - - b.Property("SecretRef") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_secrets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_secrets_tenant_id_id"); - - b.HasIndex("TenantId", "SecretRef") - .IsUnique() - .HasDatabaseName("ix_tenant_secrets_tenant_id_secret_ref"); - - b.HasIndex("TenantId", "Purpose", "Provider", "SecretKey") - .IsUnique() - .HasDatabaseName("ix_tenant_secrets_tenant_id_purpose_provider_secret_key"); - - b.HasIndex("TenantId", "Purpose", "Provider", "Status") - .HasDatabaseName("ix_tenant_secrets_tenant_id_purpose_provider_status"); - - b.ToTable("tenant_secrets", null, t => - { - t.HasCheckConstraint("ck_tenant_secrets_encryption_envelope", "octet_length(encrypted_payload) > 0 and octet_length(encryption_nonce) = 12 and octet_length(encryption_tag) = 16 and encryption_key_id <> ''"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSettings", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("AdminFeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("admin_feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublicConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_settings"); - - b.ToTable("tenant_settings", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentFollowup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignedToUserId") - .HasColumnType("uuid") - .HasColumnName("assigned_to_user_id"); - - b.Property("ClassId") - .HasColumnType("uuid") - .HasColumnName("class_id"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CompletedBy") - .HasColumnType("uuid") - .HasColumnName("completed_by"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("DueAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("due_at"); - - b.Property("FollowupType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("followup_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("priority"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_student_followups"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_student_followups_tenant_id_id"); - - b.HasIndex("AssignedToUserId") - .HasDatabaseName("ix_tenant_student_followups_assigned_to_user_id"); - - b.HasIndex("CompletedBy") - .HasDatabaseName("ix_tenant_student_followups_completed_by"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_tenant_student_followups_student_user_id"); - - b.HasIndex("TenantId", "AssignedToUserId", "Status", "DueAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_assigned_to_user_id_stat~") - .HasFilter("assigned_to_user_id is not null"); - - b.HasIndex("TenantId", "ClassId", "Status", "DueAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_class_id_status_due_at") - .HasFilter("class_id is not null"); - - b.HasIndex("TenantId", "StudentUserId", "Status", "DueAt", "CreatedAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_student_user_id_status_d~"); - - b.ToTable("tenant_student_followups", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentNote", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Content") - .IsRequired() - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("IsPinned") - .HasColumnType("boolean") - .HasColumnName("is_pinned"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NoteType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("note_type"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_tenant_student_notes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_student_notes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_student_notes_created_by"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_tenant_student_notes_student_user_id"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_student_notes_updated_by"); - - b.HasIndex("TenantId", "CreatedBy", "CreatedAt") - .HasDatabaseName("ix_tenant_student_notes_tenant_id_created_by_created_at") - .HasFilter("created_by is not null"); - - b.HasIndex("TenantId", "StudentUserId", "IsPinned", "CreatedAt") - .HasDatabaseName("ix_tenant_student_notes_tenant_id_student_user_id_is_pinned_cr~"); - - b.ToTable("tenant_student_notes", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_claims_users_user_id"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_logins_users_user_id"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_tokens_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Category", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_categories_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_categories_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_categories_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Major", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_majors_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_majors_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_majors_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ModuleNode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_module_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_module_nodes_tenant_id_parent_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.QuestionTaxonomyAssignment", b => - { - b.HasOne("Tiku.Domain.Catalog.TaxonomyNode", null) - .WithMany() - .HasForeignKey("TaxonomyOwnerTenantId", "TaxonomyNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_question_taxonomy_assignments_taxonomy_nodes_taxonomy_owner~"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_taxonomy_assignments_questions_tenant_id_question_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Region", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_regions_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.RegionModule", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_region_modules_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_region_modules_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.School", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_schools_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_schools_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_schools_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineField", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_scoreline_fields_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_fields_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_scoreline_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Subject", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subjects_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.TaxonomyNode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_taxonomy_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.TaxonomyNode", null) - .WithMany() - .HasForeignKey("ParentOwnerTenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_taxonomy_nodes_taxonomy_nodes_parent_owner_tenant_id_parent~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.ActivationCode", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AgentUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_users_agent_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activation_codes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UsedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_users_used_by"); - - b.HasOne("Tiku.Domain.Commerce.CodeBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_code_batches_tenant_id_batch_id"); - - b.HasOne("Tiku.Domain.Commerce.CouponRedemption", null) - .WithMany() - .HasForeignKey("TenantId", "CouponRedemptionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_coupon_redemptions_tenant_id_coupon_redemp~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "UsedRegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_regions_tenant_id_used_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CodeBatch", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_code_batches_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucher", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_adjustment_vouchers_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_reconciliation_batche~"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationIssue", null) - .WithMany() - .HasForeignKey("TenantId", "IssueId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_reconciliation_issues~"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_reconciliation_items_~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_refund_requests_tenan~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucherEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_voucher_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_adjustment_voucher_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceAdjustmentVoucher", null) - .WithMany() - .HasForeignKey("TenantId", "VoucherId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_adjustment_voucher_events_commerce_adjustment_vouc~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationBatch", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_batches_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_batches_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssue", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedTo") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_assigned_to"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ResolvedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_resolved_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_reconciliation_batc~"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_reconciliation_item~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_refund_requests_ten~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssueEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issue_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issue_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationIssue", null) - .WithMany() - .HasForeignKey("TenantId", "IssueId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issue_events_commerce_reconciliatio~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_items_commerce_reconciliation_batch~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_commerce_refund_requests_tena~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_events_commerce_refund_requests_tenant_id_r~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundRequest", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ProcessedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_processed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("RequestedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_requested_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_requests_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_requests_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_refund_requests_payments_tenant_id_payment_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Coupon", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_coupons_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_coupons_svip_plans_tenant_id_plan_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CouponRedemption", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_coupon_redemptions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.Coupon", null) - .WithMany() - .HasForeignKey("TenantId", "CouponId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_coupons_tenant_id_coupon_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_svip_plans_tenant_id_plan_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Entitlement", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("RevokedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_entitlements_users_revoked_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entitlements_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entitlements_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Order", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_orders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_orders_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_orders_svip_plans_tenant_id_plan_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_orders_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.OrderItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_order_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_order_items_orders_tenant_id_order_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Payment", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payments_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payments_orders_tenant_id_order_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PaymentEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_payment_events_payments_tenant_id_payment_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityClaim", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.PointActivityTask", null) - .WithMany() - .HasForeignKey("TenantId", "TaskId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_point_activity_tasks_tenant_id_task_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityTask", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_tasks_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_point_exchange_items_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeOrder", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.PointExchangeItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_point_exchange_items_tenant_id_item_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Product", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_products_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_products_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.SvipPlan", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_svip_plans_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_svip_plans_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_subscriptions_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_usage_records_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_ai_recommendation_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_ai_recommendation_reports_users_user_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_ai_recommendation_reports_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AppAsset", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_app_assets_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAsset", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_assets_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_updated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("VerifiedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_verified_by"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetAccessEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_asset_access_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_asset_access_events_users_user_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_asset_access_events_content_assets_tenant_id_asset_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetSecurityScanEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_asset_security_scan_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_asset_security_scan_events_content_assets_tenant_id~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentEntry", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_entries_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_entries_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_entries_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_entries_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportIssue", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_content_import_issues_content_import_items_tenant_id_item_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportJob", null) - .WithMany() - .HasForeignKey("TenantId", "JobId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_issues_content_import_jobs_tenant_id_job_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportJob", null) - .WithMany() - .HasForeignKey("TenantId", "JobId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_items_content_import_jobs_tenant_id_job_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportJob", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_import_jobs_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_jobs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "TargetCategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_categories_tenant_id_target_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "TargetContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_content_nodes_tenant_id_target_content_~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "TargetQuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_question_banks_tenant_id_target_questio~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "TargetRegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_regions_tenant_id_target_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "TargetSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_subjects_tenant_id_target_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentNode", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_nodes_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_nodes_users_updated_by"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_nodes_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_content_nodes_content_nodes_tenant_id_parent_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_nodes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookChapter", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_chapters_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_chapters_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_chapters_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.HandbookSubject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_handbook_chapters_handbook_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookEntry", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_entries_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.HandbookChapter", null) - .WithMany() - .HasForeignKey("TenantId", "ChapterId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_handbook_entries_handbook_chapters_tenant_id_chapter_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_entries_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_entries_content_entries_tenant_id_entry_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookSubject", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_subjects_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ImageAsset", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_images_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.PracticeBlueprint", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_blueprints_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_blueprints_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_blueprints_users_updated_by"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_question_collections_tenant_id_collecti~"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_content_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollection", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_collections_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collections_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_collections_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_content_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_question_banks_tenant_id_question_bank~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollectionItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_question_collection_items_questions_question_owner_tenant_i~"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_question_collections_tenant_id_co~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_tenant_question_references_tenant~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionTypeGroup", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_type_groups_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_type_groups_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionVideo", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_videos_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_videos_questions_tenant_id_question_id"); - - b.HasOne("Tiku.Domain.Content.VideoExplanation", null) - .WithMany() - .HasForeignKey("TenantId", "VideoId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_videos_video_explanations_tenant_id_video_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.SubjectShare", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SourceSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_subjects_tenant_id_source_subject_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "TargetSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_subjects_tenant_id_target_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionBankPreference", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_bank_preferences_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_question_bank_preferences_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_bank_preferences_users_updated_by"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("QuestionBankOwnerTenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_question_bank_preferences_question_banks_question_ba~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionReference", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_references_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_question_references_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_question_references_questions_question_owner_tenant_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordFavorite", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_users_user_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyWord", null) - .WithMany() - .HasForeignKey("TenantId", "WordId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_vocabulary_words_tenant_id_word_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordProgress", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_users_user_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyWord", null) - .WithMany() - .HasForeignKey("TenantId", "WordId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_vocabulary_words_tenant_id_word_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoExplanation", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_explanations_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_video_explanations_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyUnit", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_vocabulary_units_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyWord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_vocabulary_words_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyUnit", null) - .WithMany() - .HasForeignKey("TenantId", "UnitId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_vocabulary_units_tenant_id_unit_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlement", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("GeneratedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_generated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("PaidBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_paid_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlements_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlements_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementExportEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ExportedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_export_events_users_exported_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_export_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_export_events_commission_settlements_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementItem", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_items_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_items_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_items_commission_settlements_tenant_i~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementProof", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("SubmittedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_users_submitted_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_proofs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_content_assets_tenant_id_asset~"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_proofs_commission_settlements_tenant_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmConfig", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_config_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookLog", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_webhook_log_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookQueueItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_webhook_queue_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralCode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_codes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_codes_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralLead", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_assigned_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedToUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_assigned_to_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_leads_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_leads_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.ReferralTrack", null) - .WithMany() - .HasForeignKey("TenantId", "FirstTrackId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_referral_tracks_tenant_id_first_track_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralQrcode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_qrcodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_qrcodes_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTeamEdge", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("LeaderUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_team_edges_users_leader_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("MemberUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_team_edges_users_member_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_team_edges_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTrack", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("TargetUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_users_target_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_tracks_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.ReferralLead", null) - .WithMany() - .HasForeignKey("TenantId", "LeadId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_referral_leads_tenant_id_lead_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.TenantCommissionSetting", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_commission_settings_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_commission_settings_users_updated_by"); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.StudentProfile", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_student_profiles_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_student_profiles_users_user_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "SelectedMajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_majors_tenant_id_selected_major_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SelectedSchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_schools_tenant_id_selected_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.UserIdentity", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_identities_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportIssue", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_import_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Import.PocketBaseImportRun", null) - .WithMany() - .HasForeignKey("TenantId", "RunId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_pb_import_issues_pb_import_runs_tenant_id_run_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportRun", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_import_runs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseRawRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_raw_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Import.PocketBaseImportRun", null) - .WithMany() - .HasForeignKey("TenantId", "RunId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_raw_records_pb_import_runs_tenant_id_run_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.AnswerRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_answer_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_answer_records_users_user_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSessionQuestion", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId", "SessionQuestionId") - .HasPrincipalKey("TenantId", "PracticeSessionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_answer_records_practice_session_questions_tenant_id_practic~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "UserId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "UserId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_answer_records_practice_sessions_tenant_id_user_id_practice~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.DashboardDailyStat", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_dashboard_daily_stats_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_dashboard_daily_stats_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ExamDate", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_exam_dates_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_exam_dates_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_exam_dates_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.FavoriteQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_favorite_questions_questions_question_owner_tenant_id_quest~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_tenant_question_references_tenant_id_que~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeAccessEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_access_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_access_events_users_user_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_access_events_practice_sessions_tenant_id_practice~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeDailyUsage", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_daily_usage_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_daily_usage_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSession", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_sessions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_sessions_users_user_id"); - - b.HasOne("Tiku.Domain.Content.PracticeBlueprint", null) - .WithMany() - .HasForeignKey("TenantId", "BlueprintId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_practice_blueprints_tenant_id_blueprint_id"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_question_collections_tenant_id_collection~"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_content_entries_tenant_id_entry_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_practice_sessions_tenant_id_prac~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionVersion", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId", "QuestionVersionId") - .HasPrincipalKey("TenantId", "QuestionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_question_versions_question_owner~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_tenant_question_references_tenan~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReport", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_users_user_id"); - - b.HasOne("Tiku.Domain.Content.PracticeBlueprint", null) - .WithMany() - .HasForeignKey("TenantId", "BlueprintId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_session_reports_practice_blueprints_tenant_id_blue~"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_session_reports_question_collections_tenant_id_col~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_practice_sessions_tenant_id_practi~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReportSection", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_practice_sessions_tenant_i~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSessionReport", null) - .WithMany() - .HasForeignKey("TenantId", "ReportId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_practice_session_reports_t~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RecentPractice", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_recent_practices_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_recent_practices_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.Report", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("HandledBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_reports_users_handled_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_reports_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_reports_questions_tenant_id_question_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ReportStatusEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_report_status_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_report_status_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.Report", null) - .WithMany() - .HasForeignKey("TenantId", "ReportId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_report_status_events_reports_tenant_id_report_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RevenueDailyStat", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_revenue_daily_stats_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_revenue_daily_stats_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.UserScoreEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_score_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_score_events_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.WrongQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_wrong_questions_questions_question_owner_tenant_id_question~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_tenant_question_references_tenant_id_questi~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Announcement", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_announcements_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.AuditLog", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_audit_logs_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_audit_logs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_backend_menus_backend_permissions_permission_code"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_background_jobs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "OutputAssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_background_jobs_content_assets_tenant_id_output_asset_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Badge", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Banner", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_banners_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_banners_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Faq", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_faqs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_faqs_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendMenu", null) - .WithMany() - .HasForeignKey("MenuCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_menus_backend_menus_menu_code"); - - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_menus_platform_backend_roles_role_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_permissions_backend_permissions_permi~"); - - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_permissions_platform_backend_roles_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b => - { - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_user_roles_platform_backend_roles_role_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_user_roles_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_roles_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendMenu", null) - .WithMany() - .HasForeignKey("MenuCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_menus_backend_menus_menu_code"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_menus_tenant_backend_roles_tenant_id_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_permissions_backend_permissions_permiss~"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_permissions_tenant_backend_roles_tenant~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_user_roles_users_user_id"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_user_roles_tenant_backend_roles_tenant_id_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_content_notifications_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReadBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_content_notifications_users_read_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_content_notifications_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "SourceQuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_tenant_content_notifications_question_banks_tenant_id_sourc~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeConfig", b => - { - b.HasOne("Tiku.Domain.Operations.TenantThemeTemplate", null) - .WithMany() - .HasForeignKey("ActiveTemplateCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_tenant_theme_templates_active_template~"); - - b.HasOne("Tiku.Domain.Operations.TenantThemeTemplate", null) - .WithMany() - .HasForeignKey("DraftTemplateCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_tenant_theme_templates_draft_template_~"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("DraftUpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_users_draft_updated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("PublishedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_users_published_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_theme_configs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserBadge", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("GrantedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_user_badges_users_granted_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_badges_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_user_badges_users_user_id"); - - b.HasOne("Tiku.Domain.Operations.Badge", null) - .WithMany() - .HasForeignKey("TenantId", "BadgeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_user_badges_badges_tenant_id_badge_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserNotification", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_user_notifications_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_notifications_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_notifications_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AcknowledgedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_platform_audit_alerts_users_acknowledged_by"); - - b.HasOne("Tiku.Domain.Operations.AuditLog", null) - .WithMany() - .HasForeignKey("AuditLogId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_audit_alerts_audit_logs_audit_log_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ResolvedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_platform_audit_alerts_users_resolved_by"); - - b.HasOne("Tiku.Domain.Platform.PlatformAuditAlertRule", null) - .WithMany() - .HasForeignKey("RuleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_audit_alerts_platform_audit_alert_rules_rule_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_platform_audit_alerts_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlertRule", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_platform_audit_alert_rules_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => - { - b.HasOne("Tiku.Domain.Platform.PlatformDunningNotificationChannel", null) - .WithMany() - .HasForeignKey("ChannelId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_platform_dunning_notif~"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoices_tenant~"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoiceReminder", null) - .WithMany() - .HasForeignKey("TenantId", "ReminderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoice_reminde~"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Platform.TenantBillingProfile", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_billing_profiles_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoices_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoices_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReceivedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_payments_users_received_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_reminders_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenant_invoices_tenant_id_invoice_~"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "PrimaryCollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_collections_tenant_id_primary_collection~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_banks_tenant_id_question_bank_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_subjects_tenant_id_subject_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionVersion", null) - .WithMany() - .HasForeignKey("TenantId", "Id", "CurrentVersionId") - .HasPrincipalKey("TenantId", "QuestionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_versions_tenant_id_id_current_version_id"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionBank", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_banks_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_banks_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionVersion", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_versions_users_created_by"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_versions_questions_tenant_id_question_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_auth_challenges_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_auth_login_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_auth_login_events_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthSession", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_auth_sessions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsSendRateLimit", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sms_send_rate_limits_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsVerificationCode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sms_verification_codes_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.Tenant", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("OwnerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenants_users_owner_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantBranding", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Tenancy.TenantBranding", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_branding_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClass", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_classes_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_classes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_classes_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_tenant_classes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClassMember", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_users_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.TenantClass", null) - .WithMany() - .HasForeignKey("TenantId", "ClassId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_tenant_classes_tenant_id_class_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantDomain", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_domains_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantExternalProvider", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_external_providers_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantFrontendConfig", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_frontend_configs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantMembership", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_memberships_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_memberships_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_secrets_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSettings", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Tenancy.TenantSettings", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_settings_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentFollowup", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedToUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_users_assigned_to_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CompletedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_users_completed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_followups_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_followups_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Tenancy.TenantClass", null) - .WithMany() - .HasForeignKey("TenantId", "ClassId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_tenant_classes_tenant_id_class_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentNote", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_notes_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_notes_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_notes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_notes_users_updated_by"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260728062148_AddCommerceAdjustmentVouchers.cs b/Tiku.Infrastructure/Persistence/Migrations/20260728062148_AddCommerceAdjustmentVouchers.cs deleted file mode 100644 index eefe6f7..0000000 --- a/Tiku.Infrastructure/Persistence/Migrations/20260728062148_AddCommerceAdjustmentVouchers.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System; -using System.Text.Json; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Tiku.Infrastructure.Persistence.Migrations -{ - /// - public partial class AddCommerceAdjustmentVouchers : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "commerce_adjustment_vouchers", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - issue_id = table.Column(type: "uuid", nullable: true), - batch_id = table.Column(type: "uuid", nullable: true), - item_id = table.Column(type: "uuid", nullable: true), - order_id = table.Column(type: "uuid", nullable: true), - payment_id = table.Column(type: "uuid", nullable: true), - refund_request_id = table.Column(type: "uuid", nullable: true), - created_by = table.Column(type: "uuid", nullable: true), - reviewed_by = table.Column(type: "uuid", nullable: true), - voucher_no = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - direction = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - amount_cents = table.Column(type: "integer", nullable: false), - currency = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), - reason = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: false), - proof_asset_key = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), - reviewed_at = table.Column(type: "timestamp with time zone", nullable: true), - closed_at = table.Column(type: "timestamp with time zone", nullable: true), - metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), - tenant_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), - updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("pk_commerce_adjustment_vouchers", x => x.id); - table.UniqueConstraint("ak_commerce_adjustment_vouchers_tenant_id_id", x => new { x.tenant_id, x.id }); - table.CheckConstraint("ck_commerce_adjustment_vouchers_amount", "amount_cents > 0"); - table.ForeignKey( - name: "fk_commerce_adjustment_vouchers_commerce_reconciliation_batche~", - columns: x => new { x.tenant_id, x.batch_id }, - principalTable: "commerce_reconciliation_batches", - principalColumns: new[] { "tenant_id", "id" }, - onDelete: ReferentialAction.SetNull); - table.ForeignKey( - name: "fk_commerce_adjustment_vouchers_commerce_reconciliation_issues~", - columns: x => new { x.tenant_id, x.issue_id }, - principalTable: "commerce_reconciliation_issues", - principalColumns: new[] { "tenant_id", "id" }, - onDelete: ReferentialAction.SetNull); - table.ForeignKey( - name: "fk_commerce_adjustment_vouchers_commerce_reconciliation_items_~", - columns: x => new { x.tenant_id, x.item_id }, - principalTable: "commerce_reconciliation_items", - principalColumns: new[] { "tenant_id", "id" }, - onDelete: ReferentialAction.SetNull); - table.ForeignKey( - name: "fk_commerce_adjustment_vouchers_commerce_refund_requests_tenan~", - columns: x => new { x.tenant_id, x.refund_request_id }, - principalTable: "commerce_refund_requests", - principalColumns: new[] { "tenant_id", "id" }, - onDelete: ReferentialAction.SetNull); - table.ForeignKey( - name: "fk_commerce_adjustment_vouchers_orders_tenant_id_order_id", - columns: x => new { x.tenant_id, x.order_id }, - principalTable: "orders", - principalColumns: new[] { "tenant_id", "id" }, - onDelete: ReferentialAction.SetNull); - table.ForeignKey( - name: "fk_commerce_adjustment_vouchers_payments_tenant_id_payment_id", - columns: x => new { x.tenant_id, x.payment_id }, - principalTable: "payments", - principalColumns: new[] { "tenant_id", "id" }, - onDelete: ReferentialAction.SetNull); - table.ForeignKey( - name: "fk_commerce_adjustment_vouchers_tenants_tenant_id", - column: x => x.tenant_id, - principalTable: "tenants", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_commerce_adjustment_vouchers_users_created_by", - column: x => x.created_by, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.SetNull); - table.ForeignKey( - name: "fk_commerce_adjustment_vouchers_users_reviewed_by", - column: x => x.reviewed_by, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.SetNull); - }); - - migrationBuilder.CreateTable( - name: "commerce_adjustment_voucher_events", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - tenant_id = table.Column(type: "uuid", nullable: false), - voucher_id = table.Column(type: "uuid", nullable: false), - from_status = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), - to_status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - actor_user_id = table.Column(type: "uuid", nullable: true), - note = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), - details = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("pk_commerce_adjustment_voucher_events", x => x.id); - table.UniqueConstraint("ak_commerce_adjustment_voucher_events_tenant_id_id", x => new { x.tenant_id, x.id }); - table.ForeignKey( - name: "fk_commerce_adjustment_voucher_events_commerce_adjustment_vouc~", - columns: x => new { x.tenant_id, x.voucher_id }, - principalTable: "commerce_adjustment_vouchers", - principalColumns: new[] { "tenant_id", "id" }, - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_commerce_adjustment_voucher_events_tenants_tenant_id", - column: x => x.tenant_id, - principalTable: "tenants", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_commerce_adjustment_voucher_events_users_actor_user_id", - column: x => x.actor_user_id, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.SetNull); - }); - - migrationBuilder.CreateIndex( - name: "ix_commerce_adjustment_voucher_events_actor_user_id", - table: "commerce_adjustment_voucher_events", - column: "actor_user_id"); - - migrationBuilder.CreateIndex( - name: "ix_commerce_adjustment_voucher_events_tenant_id_voucher_id_cre~", - table: "commerce_adjustment_voucher_events", - columns: new[] { "tenant_id", "voucher_id", "created_at" }); - - migrationBuilder.CreateIndex( - name: "ix_commerce_adjustment_vouchers_created_by", - table: "commerce_adjustment_vouchers", - column: "created_by"); - - migrationBuilder.CreateIndex( - name: "ix_commerce_adjustment_vouchers_reviewed_by", - table: "commerce_adjustment_vouchers", - column: "reviewed_by"); - - migrationBuilder.CreateIndex( - name: "ix_commerce_adjustment_vouchers_tenant_id_batch_id", - table: "commerce_adjustment_vouchers", - columns: new[] { "tenant_id", "batch_id" }); - - migrationBuilder.CreateIndex( - name: "ix_commerce_adjustment_vouchers_tenant_id_issue_id_created_at", - table: "commerce_adjustment_vouchers", - columns: new[] { "tenant_id", "issue_id", "created_at" }); - - migrationBuilder.CreateIndex( - name: "ix_commerce_adjustment_vouchers_tenant_id_item_id", - table: "commerce_adjustment_vouchers", - columns: new[] { "tenant_id", "item_id" }); - - migrationBuilder.CreateIndex( - name: "ix_commerce_adjustment_vouchers_tenant_id_order_id", - table: "commerce_adjustment_vouchers", - columns: new[] { "tenant_id", "order_id" }); - - migrationBuilder.CreateIndex( - name: "ix_commerce_adjustment_vouchers_tenant_id_payment_id", - table: "commerce_adjustment_vouchers", - columns: new[] { "tenant_id", "payment_id" }); - - migrationBuilder.CreateIndex( - name: "ix_commerce_adjustment_vouchers_tenant_id_refund_request_id", - table: "commerce_adjustment_vouchers", - columns: new[] { "tenant_id", "refund_request_id" }); - - migrationBuilder.CreateIndex( - name: "ix_commerce_adjustment_vouchers_tenant_id_status_created_at", - table: "commerce_adjustment_vouchers", - columns: new[] { "tenant_id", "status", "created_at" }); - - migrationBuilder.CreateIndex( - name: "ix_commerce_adjustment_vouchers_tenant_id_voucher_no", - table: "commerce_adjustment_vouchers", - columns: new[] { "tenant_id", "voucher_no" }, - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "commerce_adjustment_voucher_events"); - - migrationBuilder.DropTable( - name: "commerce_adjustment_vouchers"); - } - } -} diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260728070357_AddVideoPlaybackProgress.Designer.cs b/Tiku.Infrastructure/Persistence/Migrations/20260728070357_AddVideoPlaybackProgress.Designer.cs deleted file mode 100644 index fcf84c8..0000000 --- a/Tiku.Infrastructure/Persistence/Migrations/20260728070357_AddVideoPlaybackProgress.Designer.cs +++ /dev/null @@ -1,18103 +0,0 @@ -// -using System; -using System.Text.Json; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Tiku.Infrastructure.Persistence; - -#nullable disable - -namespace Tiku.Infrastructure.Persistence.Migrations -{ - [DbContext(typeof(TikuDbContext))] - [Migration("20260728070357_AddVideoPlaybackProgress")] - partial class AddVideoPlaybackProgress - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.10") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "citext"); - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "ltree"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FriendlyName") - .HasColumnType("text") - .HasColumnName("friendly_name"); - - b.Property("Xml") - .HasColumnType("text") - .HasColumnName("xml"); - - b.HasKey("Id") - .HasName("pk_data_protection_keys"); - - b.ToTable("data_protection_keys", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text") - .HasColumnName("claim_type"); - - b.Property("ClaimValue") - .HasColumnType("text") - .HasColumnName("claim_value"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_claims"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_claims_user_id"); - - b.ToTable("user_claims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text") - .HasColumnName("login_provider"); - - b.Property("ProviderKey") - .HasColumnType("text") - .HasColumnName("provider_key"); - - b.Property("ProviderDisplayName") - .HasColumnType("text") - .HasColumnName("provider_display_name"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("LoginProvider", "ProviderKey") - .HasName("pk_user_logins"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_logins_user_id"); - - b.ToTable("user_logins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("LoginProvider") - .HasColumnType("text") - .HasColumnName("login_provider"); - - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); - - b.Property("Value") - .HasColumnType("text") - .HasColumnName("value"); - - b.HasKey("UserId", "LoginProvider", "Name") - .HasName("pk_user_tokens"); - - b.ToTable("user_tokens", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CategoryType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("category_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("SvipQuestionLimit") - .HasColumnType("integer") - .HasColumnName("svip_question_limit"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_categories"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_categories_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_categories_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_categories_tenant_id_node_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_categories_tenant_id_subject_id"); - - b.ToTable("categories", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Major", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StudyTips") - .HasColumnType("text") - .HasColumnName("study_tips"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_majors"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_majors_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_majors_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_majors_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_majors_tenant_id_school_id"); - - b.ToTable("majors", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ModuleNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyModuleId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_module_id"); - - b.Property("LegacyParentId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_parent_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("Path") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("path"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_module_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_module_nodes_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_module_nodes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_module_nodes_tenant_id_module_id"); - - b.HasIndex("TenantId", "ParentId") - .HasDatabaseName("ix_module_nodes_tenant_id_parent_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_module_nodes_tenant_id_region_id"); - - b.ToTable("module_nodes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.QuestionTaxonomyAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsPrimary") - .HasColumnType("boolean") - .HasColumnName("is_primary"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("TaxonomyNodeId") - .HasColumnType("uuid") - .HasColumnName("taxonomy_node_id"); - - b.Property("TaxonomyOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("taxonomy_owner_tenant_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_question_taxonomy_assignments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_taxonomy_assignments_tenant_id_id"); - - b.HasIndex("TaxonomyOwnerTenantId", "TaxonomyNodeId") - .HasDatabaseName("ix_question_taxonomy_assignments_taxonomy_owner_tenant_id_taxo~"); - - b.HasIndex("TenantId", "QuestionId", "TaxonomyOwnerTenantId", "TaxonomyNodeId") - .IsUnique() - .HasDatabaseName("ix_question_taxonomy_assignments_tenant_id_question_id_taxonom~"); - - b.ToTable("question_taxonomy_assignments", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Region", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("code"); - - b.Property("Config") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FullName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("full_name"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsHot") - .HasColumnType("boolean") - .HasColumnName("is_hot"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Pinyin") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("pinyin"); - - b.Property("ShortName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("short_name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_regions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_regions_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_regions_tenant_id_legacy_id"); - - b.ToTable("regions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.RegionModule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsPrimarySchoolModule") - .HasColumnType("boolean") - .HasColumnName("is_primary_school_module"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Route") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("route"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TextColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("text_color"); - - b.Property("Type") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_region_modules"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_region_modules_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_region_modules_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_region_modules_tenant_id_region_id"); - - b.ToTable("region_modules", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.School", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("ProfessionalExamDate") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("professional_exam_date"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_schools"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_schools_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_schools_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_schools_tenant_id_module_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_schools_tenant_id_region_id"); - - b.ToTable("schools", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineField", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("FieldKey") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("field_key"); - - b.Property("FieldName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("field_name"); - - b.Property("FieldType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("field_type"); - - b.Property("IsFilter") - .HasColumnType("boolean") - .HasColumnName("is_filter"); - - b.Property("IsRequired") - .HasColumnType("boolean") - .HasColumnName("is_required"); - - b.Property("IsTrend") - .HasColumnType("boolean") - .HasColumnName("is_trend"); - - b.Property("IsVisible") - .HasColumnType("boolean") - .HasColumnName("is_visible"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Options") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Placeholder") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("placeholder"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Unit") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("unit"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_scoreline_fields"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_scoreline_fields_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_scoreline_fields_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "FieldKey") - .IsUnique() - .HasDatabaseName("ix_scoreline_fields_tenant_id_region_id_field_key"); - - b.HasIndex("TenantId", "RegionId", "IsFilter", "SortOrder") - .HasDatabaseName("ix_scoreline_fields_tenant_id_region_id_is_filter_sort_order"); - - b.ToTable("scoreline_fields", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FieldValues") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("field_values") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("major_name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SchoolName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("school_name"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Year") - .HasColumnType("integer") - .HasColumnName("year"); - - b.HasKey("Id") - .HasName("pk_scoreline_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_scoreline_records_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_scoreline_records_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_scoreline_records_tenant_id_major_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_scoreline_records_tenant_id_school_id"); - - b.HasIndex("TenantId", "Year") - .HasDatabaseName("ix_scoreline_records_tenant_id_year"); - - b.HasIndex("TenantId", "RegionId", "SchoolId", "MajorId", "Year") - .HasDatabaseName("ix_scoreline_records_tenant_id_region_id_school_id_major_id_ye~"); - - b.ToTable("scoreline_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Subject", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorLegacyIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("major_legacy_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_subjects"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_subjects_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_subjects_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_subjects_tenant_id_major_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_subjects_tenant_id_module_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_subjects_tenant_id_node_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_subjects_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_subjects_tenant_id_school_id"); - - b.ToTable("subjects", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.TaxonomyNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Depth") - .HasColumnType("integer") - .HasColumnName("depth"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("node_type"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("ParentOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("parent_owner_tenant_id"); - - b.Property("Path") - .HasColumnType("ltree") - .HasColumnName("path"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_taxonomy_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_taxonomy_nodes_tenant_id_id"); - - b.HasIndex("ParentOwnerTenantId", "ParentId") - .HasDatabaseName("ix_taxonomy_nodes_parent_owner_tenant_id_parent_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_taxonomy_nodes_tenant_id_code"); - - b.ToTable("taxonomy_nodes", null, t => - { - t.HasCheckConstraint("ck_taxonomy_nodes_parent_pair", "(parent_owner_tenant_id is null) = (parent_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.ActivationCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AgentUserId") - .HasColumnType("uuid") - .HasColumnName("agent_user_id"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CouponCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("coupon_code"); - - b.Property("CouponRedemptionId") - .HasColumnType("uuid") - .HasColumnName("coupon_redemption_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("IsUsed") - .HasColumnType("boolean") - .HasColumnName("is_used"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("SoldTo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("sold_to"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitPriceCents") - .HasColumnType("integer") - .HasColumnName("unit_price_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("used_at"); - - b.Property("UsedBy") - .HasColumnType("uuid") - .HasColumnName("used_by"); - - b.Property("UsedRegionId") - .HasColumnType("uuid") - .HasColumnName("used_region_id"); - - b.HasKey("Id") - .HasName("pk_activation_codes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_activation_codes_tenant_id_id"); - - b.HasIndex("AgentUserId") - .HasDatabaseName("ix_activation_codes_agent_user_id"); - - b.HasIndex("UsedBy") - .HasDatabaseName("ix_activation_codes_used_by"); - - b.HasIndex("TenantId", "BatchId") - .HasDatabaseName("ix_activation_codes_tenant_id_batch_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_activation_codes_tenant_id_code"); - - b.HasIndex("TenantId", "CouponRedemptionId") - .HasDatabaseName("ix_activation_codes_tenant_id_coupon_redemption_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_activation_codes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UsedRegionId") - .HasDatabaseName("ix_activation_codes_tenant_id_used_region_id"); - - b.ToTable("activation_codes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CodeBatch", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CampaignName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("campaign_name"); - - b.Property("Channel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel"); - - b.Property("CommissionRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("commission_rate"); - - b.Property("CostPriceCents") - .HasColumnType("integer") - .HasColumnName("cost_price_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("DefaultUnitPriceCents") - .HasColumnType("integer") - .HasColumnName("default_unit_price_cents"); - - b.Property("IssuedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("issued_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_code_batches"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_code_batches_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_code_batches_tenant_id_legacy_id"); - - b.ToTable("code_batches", null, t => - { - t.HasCheckConstraint("ck_code_batches_amounts", "default_unit_price_cents >= 0 and cost_price_cents >= 0"); - - t.HasCheckConstraint("ck_code_batches_counts", "total_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("ClosedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("closed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("Direction") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("direction"); - - b.Property("IssueId") - .HasColumnType("uuid") - .HasColumnName("issue_id"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProofAssetKey") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("proof_asset_key"); - - b.Property("Reason") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("reason"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VoucherNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("voucher_no"); - - b.HasKey("Id") - .HasName("pk_commerce_adjustment_vouchers"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_adjustment_vouchers_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_adjustment_vouchers_created_by"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commerce_adjustment_vouchers_reviewed_by"); - - b.HasIndex("TenantId", "BatchId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_batch_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_item_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_order_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "VoucherNo") - .IsUnique() - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_voucher_no"); - - b.HasIndex("TenantId", "IssueId", "CreatedAt") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_issue_id_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_status_created_at"); - - b.ToTable("commerce_adjustment_vouchers", null, t => - { - t.HasCheckConstraint("ck_commerce_adjustment_vouchers_amount", "amount_cents > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucherEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("Note") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.Property("VoucherId") - .HasColumnType("uuid") - .HasColumnName("voucher_id"); - - b.HasKey("Id") - .HasName("pk_commerce_adjustment_voucher_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_adjustment_voucher_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_adjustment_voucher_events_actor_user_id"); - - b.HasIndex("TenantId", "VoucherId", "CreatedAt") - .HasDatabaseName("ix_commerce_adjustment_voucher_events_tenant_id_voucher_id_cre~"); - - b.ToTable("commerce_adjustment_voucher_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationBatch", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BillDate") - .HasColumnType("date") - .HasColumnName("bill_date"); - - b.Property("BillType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("bill_type"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DuplicateCount") - .HasColumnType("integer") - .HasColumnName("duplicate_count"); - - b.Property("Error") - .HasColumnType("text") - .HasColumnName("error"); - - b.Property("FeeCents") - .HasColumnType("integer") - .HasColumnName("fee_cents"); - - b.Property("IgnoredCount") - .HasColumnType("integer") - .HasColumnName("ignored_count"); - - b.Property("MatchedCount") - .HasColumnType("integer") - .HasColumnName("matched_count"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MismatchCount") - .HasColumnType("integer") - .HasColumnName("mismatch_count"); - - b.Property("MissingLocalCount") - .HasColumnType("integer") - .HasColumnName("missing_local_count"); - - b.Property("MissingProviderCount") - .HasColumnType("integer") - .HasColumnName("missing_provider_count"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source"); - - b.Property("SourceHash") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("source_hash"); - - b.Property("SourceName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("source_name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_batches"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_batches_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_reconciliation_batches_created_by"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_batches_tenant_id_status_created_at"); - - b.HasIndex("TenantId", "Provider", "BillDate", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_batches_tenant_id_provider_bill_dat~"); - - b.ToTable("commerce_reconciliation_batches", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_batches_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commerce_reconciliation_batches_counts", "total_count >= 0 and matched_count >= 0 and mismatch_count >= 0 and missing_local_count >= 0 and missing_provider_count >= 0 and duplicate_count >= 0 and ignored_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("AssignedTo") - .HasColumnType("uuid") - .HasColumnName("assigned_to"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DueAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("due_at"); - - b.Property("IssueCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("IssueNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_no"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("MatchStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("match_status"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("OrderNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("RefundNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("ResolutionNote") - .HasColumnType("text") - .HasColumnName("resolution_note"); - - b.Property("ResolutionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("resolution_type"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("ResolvedBy") - .HasColumnType("uuid") - .HasColumnName("resolved_by"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("summary"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TransactionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("transaction_type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_issues_tenant_id_id"); - - b.HasIndex("AssignedTo") - .HasDatabaseName("ix_commerce_reconciliation_issues_assigned_to"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_reconciliation_issues_created_by"); - - b.HasIndex("ResolvedBy") - .HasDatabaseName("ix_commerce_reconciliation_issues_resolved_by"); - - b.HasIndex("TenantId", "IssueNo") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_issue_no"); - - b.HasIndex("TenantId", "ItemId") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_item_id") - .HasFilter("item_id is not null and status in ('open', 'investigating', 'escalated')"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_order_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "OrderNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_order_no_created_at"); - - b.HasIndex("TenantId", "PaymentId", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_payment_id_created~"); - - b.HasIndex("TenantId", "AssignedTo", "Status", "DueAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_assigned_to_status~"); - - b.HasIndex("TenantId", "BatchId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_batch_id_status_cr~"); - - b.HasIndex("TenantId", "Status", "Severity", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_status_severity_cr~"); - - b.ToTable("commerce_reconciliation_issues", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_issues_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssueEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("IssueId") - .HasColumnType("uuid") - .HasColumnName("issue_id"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_issue_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_issue_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_reconciliation_issue_events_actor_user_id"); - - b.HasIndex("TenantId", "IssueId", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issue_events_tenant_id_issue_id_cre~"); - - b.ToTable("commerce_reconciliation_issue_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FeeCents") - .HasColumnType("integer") - .HasColumnName("fee_cents"); - - b.Property("IssueCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("LocalStatus") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("local_status"); - - b.Property("MatchStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("match_status"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("OrderNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("ProviderStatus") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider_status"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("RefundNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("RefundedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("refunded_at"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TransactionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("transaction_type"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_items_tenant_id_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_order_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "BatchId", "RowNo") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_batch_id_row_no"); - - b.HasIndex("TenantId", "OrderNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_order_no_created_at"); - - b.HasIndex("TenantId", "BatchId", "MatchStatus", "RowNo") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_batch_id_match_stat~"); - - b.HasIndex("TenantId", "Provider", "ProviderTradeNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_provider_provider_t~"); - - b.HasIndex("TenantId", "RefundNo", "ProviderRefundNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_refund_no_provider_~"); - - b.ToTable("commerce_reconciliation_items", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_items_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commerce_reconciliation_items_row", "row_no > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_commerce_refund_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_refund_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_refund_events_actor_user_id"); - - b.HasIndex("TenantId", "RefundRequestId", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_events_tenant_id_refund_request_id_created_~"); - - b.ToTable("commerce_refund_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundRequest", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CancelledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("cancelled_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementAction") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("entitlement_action"); - - b.Property("FailedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("failed_at"); - - b.Property("FailureReason") - .HasColumnType("text") - .HasColumnName("failure_reason"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProcessedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("processed_at"); - - b.Property("ProcessedBy") - .HasColumnType("uuid") - .HasColumnName("processed_by"); - - b.Property("Provider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("Reason") - .HasColumnType("text") - .HasColumnName("reason"); - - b.Property("RefundNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RequestedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("requested_at") - .HasDefaultValueSql("now()"); - - b.Property("RequestedBy") - .HasColumnType("uuid") - .HasColumnName("requested_by"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SucceededAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("succeeded_at"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_refund_requests"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_refund_requests_tenant_id_id"); - - b.HasIndex("ProcessedBy") - .HasDatabaseName("ix_commerce_refund_requests_processed_by"); - - b.HasIndex("RequestedBy") - .HasDatabaseName("ix_commerce_refund_requests_requested_by"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commerce_refund_requests_reviewed_by"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundNo") - .IsUnique() - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_refund_no"); - - b.HasIndex("TenantId", "OrderId", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_order_id_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_status_created_at"); - - b.ToTable("commerce_refund_requests", null, t => - { - t.HasCheckConstraint("ck_commerce_refund_requests_amount", "amount_cents > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Coupon", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DiscountType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("discount_type"); - - b.Property("DiscountValue") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("discount_value"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MaxUses") - .HasColumnType("integer") - .HasColumnName("max_uses"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("Source") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property("ValidFrom") - .HasColumnType("timestamp with time zone") - .HasColumnName("valid_from"); - - b.Property("ValidTo") - .HasColumnType("timestamp with time zone") - .HasColumnName("valid_to"); - - b.HasKey("Id") - .HasName("pk_coupons"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_coupons_tenant_id_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_coupons_tenant_id_code"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_coupons_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_coupons_tenant_id_plan_id"); - - b.ToTable("coupons", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CouponRedemption", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClaimedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("claimed_at"); - - b.Property("CouponCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("coupon_code"); - - b.Property("CouponId") - .HasColumnType("uuid") - .HasColumnName("coupon_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DiscountAppliedCents") - .HasColumnType("integer") - .HasColumnName("discount_applied_cents"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("Source") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("used_at"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_coupon_redemptions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_coupon_redemptions_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_coupon_redemptions_user_id"); - - b.HasIndex("TenantId", "CouponId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_coupon_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_coupon_redemptions_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_plan_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_region_id"); - - b.HasIndex("TenantId", "OrderId", "UserId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_order_id_user_id") - .HasFilter("order_id is not null"); - - b.HasIndex("TenantId", "UserId", "CouponId") - .IsUnique() - .HasDatabaseName("ix_coupon_redemptions_tenant_id_user_id_coupon_id") - .HasFilter("coupon_id is not null"); - - b.ToTable("coupon_redemptions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Entitlement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementType") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("entitlement_type"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("LegacySourceId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_source_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RevokedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("revoked_at"); - - b.Property("RevokedBy") - .HasColumnType("uuid") - .HasColumnName("revoked_by"); - - b.Property("RevokedReason") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("revoked_reason"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("StartsAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at") - .HasDefaultValueSql("now()"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_entitlements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_entitlements_tenant_id_id"); - - b.HasIndex("RevokedBy") - .HasDatabaseName("ix_entitlements_revoked_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_entitlements_user_id"); - - b.HasIndex("TenantId", "UserId", "Status", "ExpiresAt") - .HasDatabaseName("ix_entitlements_tenant_id_user_id_status_expires_at"); - - b.ToTable("entitlements", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyPlanId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_plan_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("LegacyUserId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_user_id"); - - b.Property("OrderNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PayMethod") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("pay_method"); - - b.Property("PayProvider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("pay_provider"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("ProductName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("product_name"); - - b.Property("ProductType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("product_type"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefundedAmountCents") - .HasColumnType("integer") - .HasColumnName("refunded_amount_cents"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("trade_no"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_orders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_orders_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_orders_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_orders_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderNo") - .IsUnique() - .HasDatabaseName("ix_orders_tenant_id_order_no"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_orders_tenant_id_plan_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_orders_tenant_id_region_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_orders_tenant_id_user_id_created_at"); - - b.ToTable("orders", null, t => - { - t.HasCheckConstraint("ck_orders_amount", "amount_cents >= 0"); - - t.HasCheckConstraint("ck_orders_refunded_amount", "refunded_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("item_type"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("Quantity") - .HasColumnType("integer") - .HasColumnName("quantity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalAmountCents") - .HasColumnType("integer") - .HasColumnName("total_amount_cents"); - - b.Property("UnitAmountCents") - .HasColumnType("integer") - .HasColumnName("unit_amount_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_order_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_order_items_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_order_items_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_order_items_tenant_id_order_id"); - - b.ToTable("order_items", null, t => - { - t.HasCheckConstraint("ck_order_items_amounts", "unit_amount_cents >= 0 and total_amount_cents >= 0"); - - t.HasCheckConstraint("ck_order_items_quantity", "quantity > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Payment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyOrderId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_order_id"); - - b.Property("Method") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("method"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefundedAmountCents") - .HasColumnType("integer") - .HasColumnName("refunded_amount_cents"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_payments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_payments_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_payments_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderId", "UpdatedAt") - .HasDatabaseName("ix_payments_tenant_id_order_id_updated_at"); - - b.HasIndex("TenantId", "Provider", "ProviderTradeNo") - .IsUnique() - .HasDatabaseName("ix_payments_tenant_id_provider_provider_trade_no") - .HasFilter("provider_trade_no is not null"); - - b.ToTable("payments", null, t => - { - t.HasCheckConstraint("ck_payments_amount", "amount_cents >= 0"); - - t.HasCheckConstraint("ck_payments_refunded_amount", "refunded_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PaymentEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Error") - .HasColumnType("text") - .HasColumnName("error"); - - b.Property("EventId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("event_id"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProcessedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("processed_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("SignatureValid") - .HasColumnType("boolean") - .HasColumnName("signature_valid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_payment_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_payment_events_tenant_id_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_payment_events_tenant_id_payment_id"); - - b.HasIndex("TenantId", "Provider", "EventId") - .IsUnique() - .HasDatabaseName("ix_payment_events_tenant_id_provider_event_id"); - - b.ToTable("payment_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClaimedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("claimed_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TaskId") - .HasColumnType("uuid") - .HasColumnName("task_id"); - - b.Property("TaskKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("task_key"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_point_activity_claims"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_activity_claims_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_point_activity_claims_user_id"); - - b.HasIndex("TenantId", "TaskId") - .HasDatabaseName("ix_point_activity_claims_tenant_id_task_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_point_activity_claims_tenant_id_user_id_created_at"); - - b.HasIndex("TenantId", "UserId", "TaskId", "SourceType", "SourceId") - .IsUnique() - .HasDatabaseName("ix_point_activity_claims_tenant_id_user_id_task_id_source_type~") - .HasFilter("source_type is not null and source_id is not null"); - - b.ToTable("point_activity_claims", null, t => - { - t.HasCheckConstraint("ck_point_activity_claims_points", "points > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityTask", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("EndsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ends_at"); - - b.Property("MaxClaimsPerUser") - .HasColumnType("integer") - .HasColumnName("max_claims_per_user"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("Rules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TaskKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("task_key"); - - b.Property("TaskType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("task_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_point_activity_tasks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_activity_tasks_tenant_id_id"); - - b.HasIndex("TenantId", "TaskKey") - .IsUnique() - .HasDatabaseName("ix_point_activity_tasks_tenant_id_task_key"); - - b.HasIndex("TenantId", "Status", "SortOrder") - .HasDatabaseName("ix_point_activity_tasks_tenant_id_status_sort_order"); - - b.ToTable("point_activity_tasks", null, t => - { - t.HasCheckConstraint("ck_point_activity_tasks_max_claims", "max_claims_per_user > 0"); - - t.HasCheckConstraint("ck_point_activity_tasks_points", "points > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("EndsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ends_at"); - - b.Property("FulfillmentPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("fulfillment_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ItemKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("item_key"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("PointsCost") - .HasColumnType("integer") - .HasColumnName("points_cost"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Stock") - .HasColumnType("integer") - .HasColumnName("stock"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_point_exchange_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_exchange_items_tenant_id_id"); - - b.HasIndex("TenantId", "ItemKey") - .IsUnique() - .HasDatabaseName("ix_point_exchange_items_tenant_id_item_key"); - - b.HasIndex("TenantId", "RegionId", "Status", "SortOrder") - .HasDatabaseName("ix_point_exchange_items_tenant_id_region_id_status_sort_order"); - - b.ToTable("point_exchange_items", null, t => - { - t.HasCheckConstraint("ck_point_exchange_items_days", "days is null or days >= 0"); - - t.HasCheckConstraint("ck_point_exchange_items_points_cost", "points_cost > 0"); - - t.HasCheckConstraint("ck_point_exchange_items_stock", "stock is null or stock >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeOrder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CancelledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("cancelled_at"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FulfillmentSnapshot") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("fulfillment_snapshot") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("ItemName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("item_name"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("OrderedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("ordered_at") - .HasDefaultValueSql("now()"); - - b.Property("PointsCost") - .HasColumnType("integer") - .HasColumnName("points_cost"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_point_exchange_orders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_exchange_orders_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_point_exchange_orders_user_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_point_exchange_orders_tenant_id_item_id"); - - b.HasIndex("TenantId", "OrderNo") - .IsUnique() - .HasDatabaseName("ix_point_exchange_orders_tenant_id_order_no"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_point_exchange_orders_tenant_id_user_id_created_at"); - - b.ToTable("point_exchange_orders", null, t => - { - t.HasCheckConstraint("ck_point_exchange_orders_points_cost", "points_cost > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Cover") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cover"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DetailImages") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("detail_images") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true) - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyPriceText") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_price_text"); - - b.Property("Link") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("link"); - - b.Property("PreviewIframe") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_iframe"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_products"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_products_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_products_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "Type", "IsActive", "SortOrder") - .HasDatabaseName("ix_products_tenant_id_region_id_type_is_active_sort_order"); - - b.ToTable("products", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.SvipPlan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Badge") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("badge"); - - b.Property("CouponOnly") - .HasColumnType("boolean") - .HasColumnName("coupon_only"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OriginalPriceCents") - .HasColumnType("integer") - .HasColumnName("original_price_cents"); - - b.Property("PerDayLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("per_day_label"); - - b.Property("PriceCents") - .HasColumnType("integer") - .HasColumnName("price_cents"); - - b.Property("Recommended") - .HasColumnType("boolean") - .HasColumnName("recommended"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VpEnabled") - .HasColumnType("boolean") - .HasColumnName("vp_enabled"); - - b.Property("VpProductId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("vp_product_id"); - - b.HasKey("Id") - .HasName("pk_svip_plans"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_svip_plans_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_svip_plans_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_svip_plans_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("svip_plans", null, t => - { - t.HasCheckConstraint("ck_svip_plans_days", "days >= 0"); - - t.HasCheckConstraint("ck_svip_plans_price", "price_cents >= 0 and (original_price_cents is null or original_price_cents >= 0)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BillingCycle") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("billing_cycle"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PlanCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("plan_code"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_subscriptions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_subscriptions_tenant_id_id"); - - b.HasIndex("TenantId", "Status", "ExpiresAt") - .HasDatabaseName("ix_tenant_subscriptions_tenant_id_status_expires_at"); - - b.ToTable("tenant_subscriptions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MetricKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("metric_key"); - - b.Property("MetricValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)") - .HasColumnName("metric_value"); - - b.Property("PeriodEnd") - .HasColumnType("date") - .HasColumnName("period_end"); - - b.Property("PeriodStart") - .HasColumnType("date") - .HasColumnName("period_start"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_usage_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_usage_records_tenant_id_id"); - - b.HasIndex("TenantId", "MetricKey", "PeriodStart", "PeriodEnd") - .HasDatabaseName("ix_tenant_usage_records_tenant_id_metric_key_period_start_peri~"); - - b.ToTable("tenant_usage_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContextPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("context_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("GeneratedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("generated_at"); - - b.Property("InputPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("input_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Model") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("model"); - - b.Property("PromptVersion") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("prompt_version"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("ResultPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("result_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_ai_recommendation_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_ai_recommendation_reports_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_ai_recommendation_reports_user_id"); - - b.HasIndex("TenantId", "RegionId", "CreatedAt") - .HasDatabaseName("ix_ai_recommendation_reports_tenant_id_region_id_created_at"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_ai_recommendation_reports_tenant_id_user_id_created_at"); - - b.ToTable("ai_recommendation_reports", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AppAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssetKey") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("asset_key"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_app_assets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_app_assets_tenant_id_id"); - - b.HasIndex("TenantId", "AssetKey") - .IsUnique() - .HasDatabaseName("ix_app_assets_tenant_id_asset_key"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_app_assets_tenant_id_legacy_id"); - - b.ToTable("app_assets", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssetKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("asset_key"); - - b.Property("AssetType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("asset_type"); - - b.Property("Bucket") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("bucket"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("CdnUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cdn_url"); - - b.Property("ChecksumSha256") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("checksum_sha256"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("DownloadCount") - .HasColumnType("integer") - .HasColumnName("download_count"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("FileSizeBytes") - .HasColumnType("bigint") - .HasColumnName("file_size_bytes"); - - b.Property("IsPublic") - .HasColumnType("boolean") - .HasColumnName("is_public"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MimeType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("mime_type"); - - b.Property("ObjectKey") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("object_key"); - - b.Property("PreviewObjectKey") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("preview_object_key"); - - b.Property("PreviewStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("preview_status"); - - b.Property("PreviewUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_url"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SecurityFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("security_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SecurityScanProvider") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("security_scan_provider"); - - b.Property("SecurityScanStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("not_required") - .HasColumnName("security_scan_status"); - - b.Property("SecurityScanSummary") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("security_scan_summary") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SecurityScannedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("security_scanned_at"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StorageProvider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_provider"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("UploadStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("upload_status"); - - b.Property("VerificationDetails") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("verification_details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.Property("VerifiedBy") - .HasColumnType("uuid") - .HasColumnName("verified_by"); - - b.Property("VerifiedChecksumSha256") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("verified_checksum_sha256"); - - b.Property("VerifiedSizeBytes") - .HasColumnType("bigint") - .HasColumnName("verified_size_bytes"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_assets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_assets_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_assets_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_assets_updated_by"); - - b.HasIndex("VerifiedBy") - .HasDatabaseName("ix_content_assets_verified_by"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_content_assets_tenant_id_category_id"); - - b.HasIndex("TenantId", "ChecksumSha256") - .HasDatabaseName("ix_content_assets_tenant_id_checksum_sha256") - .HasFilter("checksum_sha256 is not null"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_content_assets_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_assets_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PreviewStatus") - .HasDatabaseName("ix_content_assets_tenant_id_preview_status") - .HasFilter("preview_status <> 'none'"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_content_assets_tenant_id_region_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_content_assets_tenant_id_subject_id"); - - b.HasIndex("TenantId", "StorageProvider", "Bucket", "ObjectKey") - .HasDatabaseName("ix_content_assets_tenant_id_storage_provider_bucket_object_key"); - - b.HasIndex("TenantId", "UploadStatus", "Status", "UpdatedAt") - .HasDatabaseName("ix_content_assets_tenant_id_upload_status_status_updated_at"); - - b.HasIndex("TenantId", "SecurityScanStatus", "UploadStatus", "Status", "UpdatedAt") - .HasDatabaseName("ix_content_assets_tenant_id_security_scan_status_upload_status~"); - - b.HasIndex("TenantId", "Status", "Visibility", "AssetType", "RegionId", "SubjectId", "CategoryId", "SortOrder") - .HasDatabaseName("ix_content_assets_tenant_id_status_visibility_asset_type_regio~"); - - b.ToTable("content_assets", null, t => - { - t.HasCheckConstraint("ck_content_assets_download_count", "download_count >= 0"); - - t.HasCheckConstraint("ck_content_assets_file_size", "file_size_bytes is null or file_size_bytes >= 0"); - - t.HasCheckConstraint("ck_content_assets_verified_checksum", "verified_checksum_sha256 is null or verified_checksum_sha256 ~ '^[a-f0-9]{64}$'"); - - t.HasCheckConstraint("ck_content_assets_verified_size", "verified_size_bytes is null or verified_size_bytes >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetAccessEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("access_type"); - - b.Property("ActorRole") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("actor_role"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("AssetType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("asset_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DenyCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("deny_code"); - - b.Property("Disposition") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("disposition"); - - b.Property("ExpiresInSeconds") - .HasColumnType("integer") - .HasColumnName("expires_in_seconds"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Result") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("result"); - - b.Property("SignatureMode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("signature_mode"); - - b.Property("StorageProvider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("storage_provider"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("Visibility") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_asset_access_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_asset_access_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_content_asset_access_events_user_id"); - - b.HasIndex("TenantId", "AssetId", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_asset_id_created_at"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_user_id_created_at") - .HasFilter("user_id is not null"); - - b.HasIndex("TenantId", "AccessType", "Result", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_access_type_result_cr~"); - - b.ToTable("content_asset_access_events", null, t => - { - t.HasCheckConstraint("ck_content_asset_access_events_expiry", "expires_in_seconds is null or expires_in_seconds > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetSecurityScanEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.PrimitiveCollection("IssueCodes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("issue_codes") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RiskLevel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("risk_level"); - - b.Property("ScanStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scan_status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_content_asset_security_scan_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_asset_security_scan_events_tenant_id_id"); - - b.HasIndex("TenantId", "AssetId", "CreatedAt") - .HasDatabaseName("ix_content_asset_security_scan_events_tenant_id_asset_id_creat~"); - - b.HasIndex("TenantId", "ScanStatus", "RiskLevel", "CreatedAt") - .HasDatabaseName("ix_content_asset_security_scan_events_tenant_id_scan_status_ri~"); - - b.ToTable("content_asset_security_scan_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("description"); - - b.Property("EntryKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("entry_key"); - - b.Property("EntryType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("entry_type"); - - b.Property("Icon") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LayoutConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("layout_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Route") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("route"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_entries"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_entries_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_entries_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_entries_updated_by"); - - b.HasIndex("TenantId", "EntryKey") - .IsUnique() - .HasDatabaseName("ix_content_entries_tenant_id_entry_key"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_entries_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "EntryType", "IsActive", "SortOrder") - .HasDatabaseName("ix_content_entries_tenant_id_region_id_entry_type_is_active_so~"); - - b.ToTable("content_entries", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FieldPath") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("field_path"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("JobId") - .HasColumnType("uuid") - .HasColumnName("job_id"); - - b.Property("Message") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("message"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_content_import_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_issues_tenant_id_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_content_import_issues_tenant_id_item_id"); - - b.HasIndex("TenantId", "JobId", "Severity", "RowNo") - .HasDatabaseName("ix_content_import_issues_tenant_id_job_id_severity_row_no"); - - b.ToTable("content_import_issues", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("content_hash"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExternalId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("external_id"); - - b.Property("IssuesCount") - .HasColumnType("integer") - .HasColumnName("issues_count"); - - b.Property("JobId") - .HasColumnType("uuid") - .HasColumnName("job_id"); - - b.Property("NormalizedPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("normalized_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("SourcePayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("source_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TargetId") - .HasColumnType("uuid") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_content_import_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_items_tenant_id_id"); - - b.HasIndex("TenantId", "JobId", "RowNo") - .IsUnique() - .HasDatabaseName("ix_content_import_items_tenant_id_job_id_row_no"); - - b.HasIndex("TenantId", "JobId", "Status", "RowNo") - .HasDatabaseName("ix_content_import_items_tenant_id_job_id_status_row_no"); - - b.ToTable("content_import_items", null, t => - { - t.HasCheckConstraint("ck_content_import_items_issues_count", "issues_count >= 0"); - - t.HasCheckConstraint("ck_content_import_items_row_no", "row_no >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DryRun") - .HasColumnType("boolean") - .HasColumnName("dry_run"); - - b.Property("ErrorCount") - .HasColumnType("integer") - .HasColumnName("error_count"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("ImportType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("import_type"); - - b.Property("InsertedCount") - .HasColumnType("integer") - .HasColumnName("inserted_count"); - - b.Property("NormalizedPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("normalized_payload") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SkippedCount") - .HasColumnType("integer") - .HasColumnName("skipped_count"); - - b.Property("SourceFormat") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_format"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SourceName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("source_name"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("summary") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TargetCategoryId") - .HasColumnType("uuid") - .HasColumnName("target_category_id"); - - b.Property("TargetContentNodeId") - .HasColumnType("uuid") - .HasColumnName("target_content_node_id"); - - b.Property("TargetQuestionBankId") - .HasColumnType("uuid") - .HasColumnName("target_question_bank_id"); - - b.Property("TargetRegionId") - .HasColumnType("uuid") - .HasColumnName("target_region_id"); - - b.Property("TargetSubjectId") - .HasColumnType("uuid") - .HasColumnName("target_subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedCount") - .HasColumnType("integer") - .HasColumnName("updated_count"); - - b.Property("ValidCount") - .HasColumnType("integer") - .HasColumnName("valid_count"); - - b.Property("WarningCount") - .HasColumnType("integer") - .HasColumnName("warning_count"); - - b.HasKey("Id") - .HasName("pk_content_import_jobs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_jobs_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_import_jobs_created_by"); - - b.HasIndex("TenantId", "TargetCategoryId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_category_id"); - - b.HasIndex("TenantId", "TargetContentNodeId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_content_node_id"); - - b.HasIndex("TenantId", "TargetQuestionBankId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_question_bank_id"); - - b.HasIndex("TenantId", "TargetRegionId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_region_id"); - - b.HasIndex("TenantId", "TargetSubjectId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_subject_id"); - - b.HasIndex("TenantId", "ImportType", "Status", "CreatedAt") - .HasDatabaseName("ix_content_import_jobs_tenant_id_import_type_status_created_at"); - - b.ToTable("content_import_jobs", null, t => - { - t.HasCheckConstraint("ck_content_import_jobs_counts", "total_count >= 0 and valid_count >= 0 and error_count >= 0 and warning_count >= 0 and inserted_count >= 0 and updated_count >= 0 and skipped_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Depth") - .HasColumnType("integer") - .HasColumnName("depth"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsLeaf") - .HasColumnType("boolean") - .HasColumnName("is_leaf"); - - b.Property("IsSelectable") - .HasColumnType("boolean") - .HasColumnName("is_selectable"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MarkerConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("marker_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MarkerType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("marker_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("node_key"); - - b.Property("NodeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("node_type"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("Path") - .HasColumnType("ltree") - .HasColumnName("path"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_content_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_nodes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_nodes_created_by"); - - b.HasIndex("Path") - .HasDatabaseName("ix_content_nodes_path"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Path"), "gist"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_nodes_updated_by"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_nodes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MarkerType") - .HasDatabaseName("ix_content_nodes_tenant_id_marker_type") - .HasFilter("marker_type is not null"); - - b.HasIndex("TenantId", "ParentId") - .HasDatabaseName("ix_content_nodes_tenant_id_parent_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_content_nodes_tenant_id_region_id"); - - b.HasIndex("TenantId", "EntryId", "NodeKey") - .IsUnique() - .HasDatabaseName("ix_content_nodes_tenant_id_entry_id_node_key"); - - b.HasIndex("TenantId", "EntryId", "ParentId", "SortOrder") - .HasDatabaseName("ix_content_nodes_tenant_id_entry_id_parent_id_sort_order"); - - b.ToTable("content_nodes", null, t => - { - t.HasCheckConstraint("ck_content_nodes_depth", "depth >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookChapter", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_chapters"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_chapters_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_chapters_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_chapters_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_handbook_chapters_tenant_id_subject_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_chapters_tenant_id_entry_id_content_node_id_subjec~"); - - b.ToTable("handbook_chapters", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ChapterId") - .HasColumnType("uuid") - .HasColumnName("chapter_id"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("Summary") - .HasColumnType("text") - .HasColumnName("summary"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_entries"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_entries_tenant_id_id"); - - b.HasIndex("TenantId", "ChapterId") - .HasDatabaseName("ix_handbook_entries_tenant_id_chapter_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_entries_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_entries_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "ChapterId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_entries_tenant_id_entry_id_content_node_id_chapter~"); - - b.ToTable("handbook_entries", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookSubject", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorLegacyIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("major_legacy_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_subjects"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_subjects_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_subjects_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_major_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_school_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_subjects_tenant_id_entry_id_content_node_id_region~"); - - b.ToTable("handbook_subjects", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ImageAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("CdnUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cdn_url"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("IsPublic") - .HasColumnType("boolean") - .HasColumnName("is_public"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_images"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_images_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_images_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "Category", "IsPublic") - .HasDatabaseName("ix_images_tenant_id_category_is_public"); - - b.ToTable("images", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.PracticeBlueprint", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssemblyType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assembly_type"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("mode"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("PassScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("pass_score"); - - b.Property("QuestionLimit") - .HasColumnType("integer") - .HasColumnName("question_limit"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Rules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Sections") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("sections") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_practice_blueprints"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_blueprints_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_practice_blueprints_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_practice_blueprints_updated_by"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_collection_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_practice_blueprints_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_region_id"); - - b.HasIndex("TenantId", "NodeId", "CollectionId", "Mode", "Status", "SortOrder") - .HasDatabaseName("ix_practice_blueprints_tenant_id_node_id_collection_id_mode_st~"); - - b.ToTable("practice_blueprints", null, t => - { - t.HasCheckConstraint("ck_practice_blueprints_duration", "duration_minutes is null or duration_minutes > 0"); - - t.HasCheckConstraint("ck_practice_blueprints_question_limit", "question_limit is null or question_limit > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("CollectionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("collection_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Filters") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("filters") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_question_collections"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_collections_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_question_collections_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_question_collections_updated_by"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_question_collections_tenant_id_category_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_question_collections_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_collections_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "QuestionBankId") - .HasDatabaseName("ix_question_collections_tenant_id_question_bank_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_question_collections_tenant_id_region_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_question_collections_tenant_id_subject_id"); - - b.HasIndex("TenantId", "NodeId", "Status", "SortOrder") - .HasDatabaseName("ix_question_collections_tenant_id_node_id_status_sort_order"); - - b.ToTable("question_collections", null, t => - { - t.HasCheckConstraint("ck_question_collections_duration", "duration_minutes is null or duration_minutes > 0"); - - t.HasCheckConstraint("ck_question_collections_question_count", "question_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollectionItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("Required") - .HasColumnType("boolean") - .HasColumnName("required"); - - b.Property("Score") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("score"); - - b.Property("SectionKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("section_key"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_collection_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_collection_items_tenant_id_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_question_collection_items_question_owner_tenant_id_question~"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_question_collection_items_tenant_id_question_reference_id"); - - b.HasIndex("TenantId", "CollectionId", "QuestionReferenceId") - .IsUnique() - .HasDatabaseName("ix_question_collection_items_tenant_id_collection_id_question_~"); - - b.HasIndex("TenantId", "CollectionId", "SectionKey", "SortOrder") - .HasDatabaseName("ix_question_collection_items_tenant_id_collection_id_section_k~"); - - b.ToTable("question_collection_items", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionTypeGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("display_name"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Types") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("types") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_type_groups"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_type_groups_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_type_groups_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_question_type_groups_tenant_id_subject_id_is_active_sort_or~"); - - b.ToTable("question_type_groups", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionVideo", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyQuestionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_question_id"); - - b.Property("LegacyVideoId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_video_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VideoId") - .HasColumnType("uuid") - .HasColumnName("video_id"); - - b.Property("VideoType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("video_type"); - - b.HasKey("Id") - .HasName("pk_question_videos"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_videos_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_videos_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "VideoId") - .HasDatabaseName("ix_question_videos_tenant_id_video_id"); - - b.HasIndex("TenantId", "QuestionId", "VideoId", "VideoType") - .IsUnique() - .HasDatabaseName("ix_question_videos_tenant_id_question_id_video_id_video_type"); - - b.ToTable("question_videos", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.SubjectShare", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("SourceSubjectId") - .HasColumnType("uuid") - .HasColumnName("source_subject_id"); - - b.Property("TargetSubjectId") - .HasColumnType("uuid") - .HasColumnName("target_subject_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Id") - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacySourceSubjectId") - .HasColumnType("text") - .HasColumnName("legacy_source_subject_id"); - - b.Property("LegacyTargetSubjectId") - .HasColumnType("text") - .HasColumnName("legacy_target_subject_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("TenantId", "SourceSubjectId", "TargetSubjectId") - .HasName("pk_subject_shares"); - - b.HasIndex("TenantId", "TargetSubjectId") - .HasDatabaseName("ix_subject_shares_tenant_id_target_subject_id"); - - b.ToTable("subject_shares", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionBankPreference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Alias") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("alias"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("IsVisible") - .HasColumnType("boolean") - .HasColumnName("is_visible"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NavigationLocation") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("navigation_location"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("QuestionBankOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_bank_owner_tenant_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_question_bank_preferences"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_question_bank_preferences_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_question_bank_preferences_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_question_bank_preferences_updated_by"); - - b.HasIndex("QuestionBankOwnerTenantId", "QuestionBankId") - .HasDatabaseName("ix_tenant_question_bank_preferences_question_bank_owner_tenant~"); - - b.HasIndex("TenantId", "QuestionBankOwnerTenantId", "QuestionBankId") - .IsUnique() - .HasDatabaseName("ix_tenant_question_bank_preferences_tenant_id_question_bank_ow~"); - - b.ToTable("tenant_question_bank_preferences", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionReference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_question_references"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_question_references_tenant_id_id"); - - b.HasAlternateKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasName("ak_tenant_question_references_tenant_id_question_owner_tenant_~"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_question_references_created_by"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_tenant_question_references_question_owner_tenant_id_questio~"); - - b.ToTable("tenant_question_references", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordFavorite", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FavoritedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("favorited_at"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacyUserId") - .HasColumnType("text") - .HasColumnName("legacy_user_id"); - - b.Property("LegacyWordId") - .HasColumnType("text") - .HasColumnName("legacy_word_id"); - - b.Property("Note") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WordId") - .HasColumnType("uuid") - .HasColumnName("word_id"); - - b.HasKey("Id") - .HasName("pk_user_word_favorites"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_word_favorites_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_word_favorites_user_id"); - - b.HasIndex("TenantId", "WordId") - .HasDatabaseName("ix_user_word_favorites_tenant_id_word_id"); - - b.HasIndex("TenantId", "UserId", "WordId") - .IsUnique() - .HasDatabaseName("ix_user_word_favorites_tenant_id_user_id_word_id"); - - b.ToTable("user_word_favorites", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordProgress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CorrectStreak") - .HasColumnType("integer") - .HasColumnName("correct_streak"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DueLevel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("due_level"); - - b.Property("EaseFactor") - .ValueGeneratedOnAdd() - .HasPrecision(4, 2) - .HasColumnType("numeric(4,2)") - .HasDefaultValue(2.50m) - .HasColumnName("ease_factor"); - - b.Property("LastResult") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("last_result"); - - b.Property("LastReviewAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_review_at"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacyUserId") - .HasColumnType("text") - .HasColumnName("legacy_user_id"); - - b.Property("LegacyWordId") - .HasColumnType("text") - .HasColumnName("legacy_word_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NextReviewAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_review_at"); - - b.Property("ReviewCount") - .HasColumnType("integer") - .HasColumnName("review_count"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WordId") - .HasColumnType("uuid") - .HasColumnName("word_id"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.HasKey("Id") - .HasName("pk_user_word_progress"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_word_progress_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_word_progress_user_id"); - - b.HasIndex("TenantId", "WordId") - .HasDatabaseName("ix_user_word_progress_tenant_id_word_id"); - - b.HasIndex("TenantId", "UserId", "WordId") - .IsUnique() - .HasDatabaseName("ix_user_word_progress_tenant_id_user_id_word_id"); - - b.HasIndex("TenantId", "UserId", "NextReviewAt", "Status", "DueLevel") - .HasDatabaseName("ix_user_word_progress_tenant_id_user_id_next_review_at_status_~"); - - b.ToTable("user_word_progress", null, t => - { - t.HasCheckConstraint("ck_user_word_progress_correct_streak", "correct_streak >= 0"); - - t.HasCheckConstraint("ck_user_word_progress_ease_factor", "ease_factor >= 1.30 and ease_factor <= 3.00"); - - t.HasCheckConstraint("ck_user_word_progress_review_count", "review_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoExplanation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("DurationSeconds") - .HasColumnType("integer") - .HasColumnName("duration_seconds"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsGeneral") - .HasColumnType("boolean") - .HasColumnName("is_general"); - - b.Property("KnowledgeTags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("knowledge_tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacySubjectId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_subject_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ThumbnailUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("thumbnail_url"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VideoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("video_url"); - - b.HasKey("Id") - .HasName("pk_video_explanations"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_video_explanations_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_video_explanations_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SourceHash") - .HasDatabaseName("ix_video_explanations_tenant_id_source_hash") - .HasFilter("source_hash is not null"); - - b.HasIndex("TenantId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_video_explanations_tenant_id_subject_id_is_active_sort_order"); - - b.ToTable("video_explanations", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoPlaybackProgress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DurationSeconds") - .HasColumnType("integer") - .HasColumnName("duration_seconds"); - - b.Property("IsCompleted") - .HasColumnType("boolean") - .HasColumnName("is_completed"); - - b.Property("LastPlayedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_played_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PlayCount") - .HasColumnType("integer") - .HasColumnName("play_count"); - - b.Property("PositionSeconds") - .HasColumnType("integer") - .HasColumnName("position_seconds"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("VideoId") - .HasColumnType("uuid") - .HasColumnName("video_id"); - - b.Property("WatchedSeconds") - .HasColumnType("integer") - .HasColumnName("watched_seconds"); - - b.HasKey("Id") - .HasName("pk_video_playback_progress"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_video_playback_progress_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_video_playback_progress_user_id"); - - b.HasIndex("TenantId", "QuestionId") - .HasDatabaseName("ix_video_playback_progress_tenant_id_question_id"); - - b.HasIndex("TenantId", "VideoId") - .HasDatabaseName("ix_video_playback_progress_tenant_id_video_id"); - - b.HasIndex("TenantId", "UserId", "LastPlayedAt") - .HasDatabaseName("ix_video_playback_progress_tenant_id_user_id_last_played_at"); - - b.HasIndex("TenantId", "UserId", "VideoId") - .IsUnique() - .HasDatabaseName("ix_video_playback_progress_tenant_id_user_id_video_id") - .HasFilter("question_id is null"); - - b.HasIndex("TenantId", "UserId", "VideoId", "QuestionId") - .IsUnique() - .HasDatabaseName("ix_video_playback_progress_tenant_id_user_id_video_id_question~") - .HasFilter("question_id is not null"); - - b.ToTable("video_playback_progress", null, t => - { - t.HasCheckConstraint("ck_video_playback_progress_duration", "duration_seconds is null or duration_seconds >= 0"); - - t.HasCheckConstraint("ck_video_playback_progress_play_count", "play_count >= 0"); - - t.HasCheckConstraint("ck_video_playback_progress_position", "position_seconds >= 0"); - - t.HasCheckConstraint("ck_video_playback_progress_watched", "watched_seconds >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyUnit", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WordCount") - .HasColumnType("integer") - .HasColumnName("word_count"); - - b.HasKey("Id") - .HasName("pk_vocabulary_units"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_vocabulary_units_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_vocabulary_units_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_vocabulary_units_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_vocabulary_units_tenant_id_region_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_vocabulary_units_tenant_id_entry_id_content_node_id_region_~"); - - b.ToTable("vocabulary_units", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyWord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Example") - .HasColumnType("text") - .HasColumnName("example"); - - b.Property("ExampleTranslation") - .HasColumnType("text") - .HasColumnName("example_translation"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Meaning") - .HasColumnType("text") - .HasColumnName("meaning"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Phonetic") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("phonetic"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitId") - .HasColumnType("uuid") - .HasColumnName("unit_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Word") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("word"); - - b.HasKey("Id") - .HasName("pk_vocabulary_words"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_vocabulary_words_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_vocabulary_words_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_vocabulary_words_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UnitId", "IsActive", "SortOrder", "CreatedAt") - .HasDatabaseName("ix_vocabulary_words_tenant_id_unit_id_is_active_sort_order_cre~"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "UnitId", "IsActive", "SortOrder") - .HasDatabaseName("ix_vocabulary_words_tenant_id_entry_id_content_node_id_unit_id~"); - - b.ToTable("vocabulary_words", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CommissionAmountCents") - .HasColumnType("integer") - .HasColumnName("commission_amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DefaultRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("default_rate"); - - b.Property("EffectiveRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("effective_rate"); - - b.Property("GeneratedBy") - .HasColumnType("uuid") - .HasColumnName("generated_by"); - - b.Property("GrossAmountCents") - .HasColumnType("integer") - .HasColumnName("gross_amount_cents"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaidBy") - .HasColumnType("uuid") - .HasColumnName("paid_by"); - - b.Property("PaidUserCount") - .HasColumnType("integer") - .HasColumnName("paid_user_count"); - - b.Property("PaymentAccount") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("payment_account"); - - b.Property("PaymentMethod") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_method"); - - b.Property("PeriodEnd") - .HasColumnType("date") - .HasColumnName("period_end"); - - b.Property("PeriodStart") - .HasColumnType("date") - .HasColumnName("period_start"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("SettlementNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("settlement_no"); - - b.Property("SourceCount") - .HasColumnType("integer") - .HasColumnName("source_count"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlements_tenant_id_id"); - - b.HasIndex("GeneratedBy") - .HasDatabaseName("ix_commission_settlements_generated_by"); - - b.HasIndex("PaidBy") - .HasDatabaseName("ix_commission_settlements_paid_by"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_commission_settlements_referrer_user_id"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commission_settlements_reviewed_by"); - - b.HasIndex("TenantId", "SettlementNo") - .IsUnique() - .HasDatabaseName("ix_commission_settlements_tenant_id_settlement_no"); - - b.HasIndex("TenantId", "ReferrerUserId", "PeriodStart") - .HasDatabaseName("ix_commission_settlements_referrer_start"); - - b.HasIndex("TenantId", "ReferrerUserId", "PeriodStart", "PeriodEnd") - .IsUnique() - .HasDatabaseName("ux_commission_settlements_referrer_period"); - - b.ToTable("commission_settlements", null, t => - { - t.HasCheckConstraint("ck_commission_settlements_amounts", "gross_amount_cents >= 0 and commission_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commission_settlements_counts", "source_count >= 0 and paid_user_count >= 0"); - - t.HasCheckConstraint("ck_commission_settlements_period", "period_end >= period_start"); - - t.HasCheckConstraint("ck_commission_settlements_rates", "default_rate >= 0 and default_rate <= 1 and (effective_rate is null or (effective_rate >= 0 and effective_rate <= 1))"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementExportEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentSha256") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("content_sha256"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExportFormat") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasColumnName("export_format"); - - b.Property("ExportedBy") - .HasColumnType("uuid") - .HasColumnName("exported_by"); - - b.Property("Filename") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("filename"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RowCount") - .HasColumnType("integer") - .HasColumnName("row_count"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_export_events"); - - b.HasIndex("ExportedBy") - .HasDatabaseName("ix_commission_settlement_export_events_exported_by"); - - b.HasIndex("TenantId", "SettlementId", "CreatedAt") - .HasDatabaseName("ix_commission_settlement_export_events_tenant_id_settlement_id~"); - - b.ToTable("commission_settlement_export_events", null, t => - { - t.HasCheckConstraint("ck_commission_export_events_rows", "row_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AttributionType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("attribution_type"); - - b.Property("CommissionAmountCents") - .HasColumnType("integer") - .HasColumnName("commission_amount_cents"); - - b.Property("CommissionRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("commission_rate"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("GrossAmountCents") - .HasColumnType("integer") - .HasColumnName("gross_amount_cents"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RateSource") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("rate_source"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_no"); - - b.Property("SourcePaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("source_paid_at"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_type"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlement_items_tenant_id_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_commission_settlement_items_referrer_user_id"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_commission_settlement_items_student_user_id"); - - b.HasIndex("TenantId", "SettlementId") - .HasDatabaseName("ix_commission_settlement_items_tenant_id_settlement_id"); - - b.HasIndex("TenantId", "ReferrerUserId", "SourcePaidAt") - .HasDatabaseName("ix_commission_settlement_items_tenant_id_referrer_user_id_sour~"); - - b.HasIndex("TenantId", "SourceType", "SourceId") - .IsUnique() - .HasDatabaseName("ix_commission_settlement_items_tenant_id_source_type_source_id"); - - b.ToTable("commission_settlement_items", null, t => - { - t.HasCheckConstraint("ck_commission_settlement_items_amounts", "gross_amount_cents >= 0 and commission_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commission_settlement_items_rate", "commission_rate >= 0 and commission_rate <= 1"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementProof", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("ExternalUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("external_url"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentAccount") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("payment_account"); - - b.Property("PaymentMethod") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_method"); - - b.Property("ProofType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("proof_type"); - - b.Property("ReviewNote") - .HasColumnType("text") - .HasColumnName("review_note"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubmittedBy") - .HasColumnType("uuid") - .HasColumnName("submitted_by"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_proofs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlement_proofs_tenant_id_id"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commission_settlement_proofs_reviewed_by"); - - b.HasIndex("SubmittedBy") - .HasDatabaseName("ix_commission_settlement_proofs_submitted_by"); - - b.HasIndex("TenantId", "AssetId") - .HasDatabaseName("ix_commission_settlement_proofs_tenant_id_asset_id"); - - b.HasIndex("TenantId", "SettlementId", "Status", "CreatedAt") - .HasDatabaseName("ix_commission_settlement_proofs_tenant_id_settlement_id_status~"); - - b.ToTable("commission_settlement_proofs", null, t => - { - t.HasCheckConstraint("ck_commission_settlement_proofs_amount", "amount_cents is null or amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignmentConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("assignment_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssignmentCursor") - .HasColumnType("integer") - .HasColumnName("assignment_cursor"); - - b.Property("AssignmentMode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assignment_mode"); - - b.Property("AssignmentPool") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("assignment_pool") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DelaySeconds") - .HasColumnType("integer") - .HasColumnName("delay_seconds"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("ExamType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("exam_type"); - - b.Property("FormName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("form_name"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TimeoutSeconds") - .HasColumnType("integer") - .HasColumnName("timeout_seconds"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Url") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_crm_config"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_config_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_crm_config_tenant_id"); - - b.HasIndex("TenantId", "AssignmentMode") - .HasDatabaseName("ix_crm_config_tenant_id_assignment_mode"); - - b.ToTable("crm_config", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempt") - .HasColumnType("integer") - .HasColumnName("attempt"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("HttpCode") - .HasColumnType("integer") - .HasColumnName("http_code"); - - b.Property("LeadId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Outcome") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("outcome"); - - b.Property("RecordId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("record_id"); - - b.Property("RequestBody") - .HasColumnType("text") - .HasColumnName("request_body"); - - b.Property("RequestPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("request_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ResponseSummary") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("response_summary"); - - b.Property("SignedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("signed_at"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_crm_webhook_log"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_webhook_log_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_log_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RecordId", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_log_tenant_id_record_id_created_at"); - - b.ToTable("crm_webhook_log", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookQueueItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IdempotencyKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("idempotency_key"); - - b.Property("LastAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_attempt_at"); - - b.Property("LastError") - .HasColumnType("text") - .HasColumnName("last_error"); - - b.Property("LastHttpCode") - .HasColumnType("integer") - .HasColumnName("last_http_code"); - - b.Property("LastResponseSummary") - .HasColumnType("text") - .HasColumnName("last_response_summary"); - - b.Property("LeadId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("NextAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_attempt_at"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Provider") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RecordId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("record_id"); - - b.Property("ScheduledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("scheduled_at"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Source") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TargetUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("target_url"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_crm_webhook_queue"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_webhook_queue_tenant_id_id"); - - b.HasIndex("TenantId", "IdempotencyKey") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_idempotency_key") - .HasFilter("idempotency_key is not null"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RecordId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_record_id") - .HasFilter("record_id is not null"); - - b.HasIndex("TenantId", "RecordId", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_record_id_created_at") - .HasFilter("record_id is not null"); - - b.HasIndex("TenantId", "Source", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_source_created_at") - .HasFilter("source = 'tenant.student.crm_push'"); - - b.HasIndex("TenantId", "Status", "ScheduledAt", "NextAttemptAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_status_scheduled_at_next_attemp~"); - - b.ToTable("crm_webhook_queue", null, t => - { - t.HasCheckConstraint("ck_crm_webhook_queue_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Channel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LandingPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("landing_path"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_referral_codes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_codes_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_referral_codes_user_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_referral_codes_tenant_id_code"); - - b.HasIndex("TenantId", "UserId") - .IsUnique() - .HasDatabaseName("ix_referral_codes_tenant_id_user_id"); - - b.HasIndex("TenantId", "Code", "Status") - .HasDatabaseName("ix_referral_codes_tenant_id_code_status"); - - b.ToTable("referral_codes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralLead", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("assigned_at"); - - b.Property("AssignedBy") - .HasColumnType("uuid") - .HasColumnName("assigned_by"); - - b.Property("AssignedToUserId") - .HasColumnType("uuid") - .HasColumnName("assigned_to_user_id"); - - b.Property("AssignmentMode") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assignment_mode"); - - b.Property("BindType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("bind_type"); - - b.Property("BoundAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("bound_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FirstTrackId") - .HasColumnType("uuid") - .HasColumnName("first_track_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ProtectedUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("protected_until"); - - b.Property("RefCode") - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Source") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_referral_leads"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_leads_tenant_id_id"); - - b.HasIndex("AssignedBy") - .HasDatabaseName("ix_referral_leads_assigned_by"); - - b.HasIndex("AssignedToUserId") - .HasDatabaseName("ix_referral_leads_assigned_to_user_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_referral_leads_referrer_user_id"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_referral_leads_student_user_id"); - - b.HasIndex("TenantId", "FirstTrackId") - .HasDatabaseName("ix_referral_leads_tenant_id_first_track_id"); - - b.HasIndex("TenantId", "StudentUserId") - .IsUnique() - .HasDatabaseName("ix_referral_leads_tenant_id_student_user_id"); - - b.HasIndex("TenantId", "ReferrerUserId", "BoundAt") - .HasDatabaseName("ix_referral_leads_tenant_id_referrer_user_id_bound_at"); - - b.HasIndex("TenantId", "AssignedToUserId", "Status", "BoundAt") - .HasDatabaseName("ix_referral_leads_tenant_id_assigned_to_user_id_status_bound_at"); - - b.ToTable("referral_leads", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralQrcode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Page") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("page"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("QrcodeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("qrcode_url"); - - b.Property("RefCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("Scene") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("scene"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_referral_qrcodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_qrcodes_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_referral_qrcodes_user_id"); - - b.HasIndex("TenantId", "RefCode", "Status") - .HasDatabaseName("ix_referral_qrcodes_tenant_id_ref_code_status"); - - b.HasIndex("TenantId", "Provider", "Scene", "Page") - .IsUnique() - .HasDatabaseName("ix_referral_qrcodes_tenant_id_provider_scene_page"); - - b.ToTable("referral_qrcodes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTeamEdge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LeaderUserId") - .HasColumnType("uuid") - .HasColumnName("leader_user_id"); - - b.Property("MemberUserId") - .HasColumnType("uuid") - .HasColumnName("member_user_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RelationType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("relation_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_referral_team_edges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_team_edges_tenant_id_id"); - - b.HasIndex("LeaderUserId") - .HasDatabaseName("ix_referral_team_edges_leader_user_id"); - - b.HasIndex("MemberUserId") - .HasDatabaseName("ix_referral_team_edges_member_user_id"); - - b.HasIndex("TenantId", "LeaderUserId", "Status") - .HasDatabaseName("ix_referral_team_edges_tenant_id_leader_user_id_status"); - - b.HasIndex("TenantId", "MemberUserId", "RelationType") - .IsUnique() - .HasDatabaseName("ix_referral_team_edges_tenant_id_member_user_id_relation_type"); - - b.ToTable("referral_team_edges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTrack", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("LeadId") - .HasColumnType("uuid") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefCode") - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Source") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source"); - - b.Property("TargetUserId") - .HasColumnType("uuid") - .HasColumnName("target_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_referral_tracks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_tracks_tenant_id_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_referral_tracks_referrer_user_id"); - - b.HasIndex("TargetUserId") - .HasDatabaseName("ix_referral_tracks_target_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_referral_tracks_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "LeadId", "CreatedAt") - .HasDatabaseName("ix_referral_tracks_tenant_id_lead_id_created_at"); - - b.HasIndex("TenantId", "RefCode", "EventType", "CreatedAt") - .HasDatabaseName("ix_referral_tracks_tenant_id_ref_code_event_type_created_at"); - - b.ToTable("referral_tracks", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.TenantCommissionSetting", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Config") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DefaultRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("default_rate"); - - b.Property("MinSettlementCents") - .HasColumnType("integer") - .HasColumnName("min_settlement_cents"); - - b.Property("SettlementCycle") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("settlement_cycle"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_commission_settings"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_commission_settings_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_commission_settings_tenant_id"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_commission_settings_updated_by"); - - b.ToTable("tenant_commission_settings", null, t => - { - t.HasCheckConstraint("ck_tenant_commission_settings_default_rate", "default_rate >= 0 and default_rate <= 1"); - - t.HasCheckConstraint("ck_tenant_commission_settings_min_settlement", "min_settlement_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.StudentProfile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AvatarPreset") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("male") - .HasColumnName("avatar_preset"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LastCheckInDate") - .HasColumnType("date") - .HasColumnName("last_check_in_date"); - - b.Property("LegacyUserId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_user_id"); - - b.Property("MasteredWordsCount") - .HasColumnType("integer") - .HasColumnName("mastered_words_count"); - - b.Property("ModuleSelections") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("module_selections") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Progress") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("progress") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionsAnsweredToday") - .HasColumnType("integer") - .HasColumnName("questions_answered_today"); - - b.Property("RecentActivities") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("recent_activities") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SelectedMajorId") - .HasColumnType("uuid") - .HasColumnName("selected_major_id"); - - b.Property("SelectedSchoolId") - .HasColumnType("uuid") - .HasColumnName("selected_school_id"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_student_profiles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_student_profiles_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_student_profiles_user_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_student_profiles_tenant_id_region_id"); - - b.HasIndex("TenantId", "SelectedMajorId") - .HasDatabaseName("ix_student_profiles_tenant_id_selected_major_id"); - - b.HasIndex("TenantId", "SelectedSchoolId") - .HasDatabaseName("ix_student_profiles_tenant_id_selected_school_id"); - - b.HasIndex("TenantId", "UserId") - .IsUnique() - .HasDatabaseName("ix_student_profiles_tenant_id_user_id"); - - b.ToTable("student_profiles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessFailedCount") - .HasColumnType("integer") - .HasColumnName("access_failed_count"); - - b.Property("AvatarUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("avatar_url"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("concurrency_stamp"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Email") - .HasMaxLength(320) - .HasColumnType("citext") - .HasColumnName("email"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean") - .HasColumnName("email_confirmed"); - - b.Property("ForcePasswordChange") - .HasColumnType("boolean") - .HasColumnName("force_password_change"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_seen_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean") - .HasColumnName("lockout_enabled"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone") - .HasColumnName("lockout_end"); - - b.Property("Name") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("NormalizedEmail") - .HasMaxLength(320) - .HasColumnType("character varying(320)") - .HasColumnName("normalized_email"); - - b.Property("NormalizedUserName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("normalized_user_name"); - - b.Property("PasswordHash") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("password_hash"); - - b.Property("Phone") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("PhoneNumber") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone_number"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean") - .HasColumnName("phone_number_confirmed"); - - b.Property("PrimaryRole") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("primary_role"); - - b.Property("RawProfile") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_profile") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Score") - .HasColumnType("integer") - .HasColumnName("score"); - - b.Property("SecurityStamp") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("security_stamp"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean") - .HasColumnName("two_factor_enabled"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("user_name"); - - b.HasKey("Id") - .HasName("pk_users"); - - b.HasIndex("LegacyId") - .IsUnique() - .HasDatabaseName("ix_users_legacy_id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("email_index"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("user_name_index"); - - b.HasIndex("Phone") - .IsUnique() - .HasDatabaseName("ix_users_phone"); - - b.ToTable("users", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.UserIdentity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Email") - .HasMaxLength(320) - .HasColumnType("citext") - .HasColumnName("email"); - - b.Property("OpenId") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("open_id"); - - b.Property("Phone") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderSubject") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("provider_subject"); - - b.Property("UnionId") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("union_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_identities"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_identities_user_id"); - - b.HasIndex("Provider", "ProviderSubject") - .IsUnique() - .HasDatabaseName("ix_user_identities_provider_provider_subject"); - - b.ToTable("user_identities", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CollectionName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("collection_name"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FieldPath") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("field_path"); - - b.Property("IssueCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("LegacyId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_id"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("RawValueSample") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("raw_value_sample"); - - b.Property("RunId") - .HasColumnType("uuid") - .HasColumnName("run_id"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_pb_import_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_pb_import_issues_tenant_id_id"); - - b.HasIndex("TenantId", "RunId") - .HasDatabaseName("ix_pb_import_issues_tenant_id_run_id"); - - b.HasIndex("RunId", "Severity", "CollectionName") - .HasDatabaseName("ix_pb_import_issues_run_id_severity_collection_name"); - - b.HasIndex("TenantId", "CollectionName", "LegacyId") - .HasDatabaseName("ix_pb_import_issues_tenant_id_collection_name_legacy_id"); - - b.ToTable("pb_import_issues", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportRun", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("SourceKind") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_kind"); - - b.Property("SourceName") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("source_name"); - - b.Property("StartedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at") - .HasDefaultValueSql("now()"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_pb_import_runs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_pb_import_runs_tenant_id_id"); - - b.HasIndex("TenantId", "Status", "StartedAt") - .HasDatabaseName("ix_pb_import_runs_tenant_id_status_started_at"); - - b.ToTable("pb_import_runs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseRawRecord", b => - { - b.Property("RunId") - .HasColumnType("uuid") - .HasColumnName("run_id"); - - b.Property("CollectionName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("collection_name"); - - b.Property("LegacyId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_id"); - - b.Property("Errors") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("errors") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("ImportedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("imported_at") - .HasDefaultValueSql("now()"); - - b.Property("Normalized") - .HasColumnType("boolean") - .HasColumnName("normalized"); - - b.Property("Record") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("record") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("RunId", "CollectionName", "LegacyId") - .HasName("pk_pb_raw_records"); - - b.HasIndex("TenantId", "RunId") - .HasDatabaseName("ix_pb_raw_records_tenant_id_run_id"); - - b.HasIndex("TenantId", "CollectionName", "LegacyId") - .HasDatabaseName("ix_pb_raw_records_tenant_id_collection_name_legacy_id"); - - b.ToTable("pb_raw_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.AnswerRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AnswerText") - .HasColumnType("text") - .HasColumnName("answer_text"); - - b.Property("AnsweredAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("answered_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsCorrect") - .HasColumnType("boolean") - .HasColumnName("is_correct"); - - b.Property("LegacyCategoryId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_category_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyQuestionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_question_id"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("SelectedOptions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("selected_options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SessionQuestionId") - .HasColumnType("uuid") - .HasColumnName("session_question_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_answer_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_answer_records_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_answer_records_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_answer_records_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PracticeSessionId", "SessionQuestionId") - .HasDatabaseName("ix_answer_records_tenant_id_practice_session_id_session_questi~"); - - b.HasIndex("TenantId", "UserId", "PracticeSessionId") - .HasDatabaseName("ix_answer_records_tenant_id_user_id_practice_session_id"); - - b.ToTable("answer_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.DashboardDailyStat", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActiveUsers") - .HasColumnType("integer") - .HasColumnName("active_users"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("NewOrders") - .HasColumnType("integer") - .HasColumnName("new_orders"); - - b.Property("NewQuestions") - .HasColumnType("integer") - .HasColumnName("new_questions"); - - b.Property("NewRevenueCents") - .HasColumnType("integer") - .HasColumnName("new_revenue_cents"); - - b.Property("NewUsers") - .HasColumnType("integer") - .HasColumnName("new_users"); - - b.Property("RebuiltAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rebuilt_at"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("StatDate") - .HasColumnType("date") - .HasColumnName("stat_date"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_dashboard_daily_stats"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_dashboard_daily_stats_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_region_id"); - - b.HasIndex("TenantId", "StatDate", "RegionId") - .IsUnique() - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_stat_date_region_id"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "StatDate", "RegionId"), false); - - b.ToTable("dashboard_daily_stats", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ExamDate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("ExamAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("exam_at"); - - b.Property("ExamName") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("exam_name"); - - b.Property("ExamType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("exam_type"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_exam_dates"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_exam_dates_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_exam_dates_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_exam_dates_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId", "ExamAt") - .HasDatabaseName("ix_exam_dates_tenant_id_school_id_exam_at"); - - b.ToTable("exam_dates", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.FavoriteQuestion", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.HasKey("TenantId", "UserId", "QuestionReferenceId") - .HasName("pk_favorite_questions"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_favorite_questions_user_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_favorite_questions_question_owner_tenant_id_question_id"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_favorite_questions_tenant_id_question_reference_id"); - - b.ToTable("favorite_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeAccessEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessMode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("access_mode"); - - b.Property("ConsumedFreeQuota") - .HasColumnType("integer") - .HasColumnName("consumed_free_quota"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementId") - .HasColumnType("uuid") - .HasColumnName("entitlement_id"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("event_type"); - - b.Property("GrantedCount") - .HasColumnType("integer") - .HasColumnName("granted_count"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("Reason") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("reason"); - - b.Property("RequestedCount") - .HasColumnType("integer") - .HasColumnName("requested_count"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_access_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_access_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_access_events_user_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .HasDatabaseName("ix_practice_access_events_tenant_id_practice_session_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_practice_access_events_tenant_id_user_id_created_at"); - - b.ToTable("practice_access_events", null, t => - { - t.HasCheckConstraint("ck_practice_access_events_consumed_free_quota", "consumed_free_quota >= 0"); - - t.HasCheckConstraint("ck_practice_access_events_granted_count", "granted_count >= 0"); - - t.HasCheckConstraint("ck_practice_access_events_requested_count", "requested_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeDailyUsage", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FreeLimit") - .HasColumnType("integer") - .HasColumnName("free_limit"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsageDate") - .ValueGeneratedOnAdd() - .HasColumnType("date") - .HasColumnName("usage_date") - .HasDefaultValueSql("current_date"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_daily_usage"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_daily_usage_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_daily_usage_user_id"); - - b.HasIndex("TenantId", "UserId", "UsageDate") - .HasDatabaseName("ix_practice_daily_usage_tenant_id_user_id_usage_date"); - - b.HasIndex("TenantId", "UserId", "UsageDate", "ScopeType", "ScopeId") - .IsUnique() - .HasDatabaseName("ix_practice_daily_usage_tenant_id_user_id_usage_date_scope_typ~"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "UserId", "UsageDate", "ScopeType", "ScopeId"), false); - - b.ToTable("practice_daily_usage", null, t => - { - t.HasCheckConstraint("ck_practice_daily_usage_free_limit", "free_limit >= 0"); - - t.HasCheckConstraint("ck_practice_daily_usage_used_count", "used_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessEntitlementId") - .HasColumnType("uuid") - .HasColumnName("access_entitlement_id"); - - b.Property("AccessMode") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("free") - .HasColumnName("access_mode"); - - b.Property("AccessSnapshot") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_snapshot") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("BlueprintId") - .HasColumnType("uuid") - .HasColumnName("blueprint_id"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("ConsumedFreeQuota") - .HasColumnType("integer") - .HasColumnName("consumed_free_quota"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("mode"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("StartedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at") - .HasDefaultValueSql("now()"); - - b.Property("TargetId") - .HasColumnType("uuid") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_sessions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_sessions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "UserId", "Id") - .HasName("ak_practice_sessions_tenant_id_user_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_sessions_user_id"); - - b.HasIndex("TenantId", "BlueprintId") - .HasDatabaseName("ix_practice_sessions_tenant_id_blueprint_id"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_sessions_tenant_id_collection_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_practice_sessions_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_practice_sessions_tenant_id_entry_id"); - - b.HasIndex("TenantId", "UserId", "StartedAt") - .HasDatabaseName("ix_practice_sessions_tenant_id_user_id_started_at"); - - b.ToTable("practice_sessions", null, t => - { - t.HasCheckConstraint("ck_practice_sessions_consumed_free_quota", "consumed_free_quota >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionQuestion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Position") - .HasColumnType("integer") - .HasColumnName("position"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("QuestionVersionId") - .HasColumnType("uuid") - .HasColumnName("question_version_id"); - - b.Property("Score") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("score"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_practice_session_questions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_questions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "PracticeSessionId", "Id") - .HasName("ak_practice_session_questions_tenant_id_practice_session_id_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId", "QuestionVersionId") - .HasDatabaseName("ix_practice_session_questions_question_owner_tenant_id_questio~"); - - b.HasIndex("TenantId", "PracticeSessionId", "Position") - .IsUnique() - .HasDatabaseName("ix_practice_session_questions_tenant_id_practice_session_id_po~"); - - b.HasIndex("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_practice_session_questions_tenant_id_question_owner_tenant_~"); - - b.ToTable("practice_session_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReport", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Accuracy") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("accuracy"); - - b.Property("AnsweredCount") - .HasColumnType("integer") - .HasColumnName("answered_count"); - - b.Property("BlueprintId") - .HasColumnType("uuid") - .HasColumnName("blueprint_id"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DurationSeconds") - .HasColumnType("integer") - .HasColumnName("duration_seconds"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("mode"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionResults") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("question_results") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Score") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("score"); - - b.Property("SectionStats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("section_stats") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("SubmittedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("submitted_at") - .HasDefaultValueSql("now()"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalQuestions") - .HasColumnType("integer") - .HasColumnName("total_questions"); - - b.Property("TotalScore") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("total_score"); - - b.Property("UnansweredCount") - .HasColumnType("integer") - .HasColumnName("unanswered_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.Property("WrongQuestionIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("wrong_question_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.HasKey("Id") - .HasName("pk_practice_session_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_reports_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_session_reports_user_id"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_session_reports_tenant_id_collection_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .IsUnique() - .HasDatabaseName("ix_practice_session_reports_tenant_id_practice_session_id"); - - b.HasIndex("TenantId", "BlueprintId", "SubmittedAt") - .HasDatabaseName("ix_practice_session_reports_tenant_id_blueprint_id_submitted_at") - .HasFilter("blueprint_id is not null"); - - b.HasIndex("TenantId", "UserId", "SubmittedAt") - .HasDatabaseName("ix_practice_session_reports_tenant_id_user_id_submitted_at"); - - b.ToTable("practice_session_reports", null, t => - { - t.HasCheckConstraint("ck_practice_session_reports_accuracy", "accuracy >= 0 and accuracy <= 1"); - - t.HasCheckConstraint("ck_practice_session_reports_counts", "total_questions >= 0 and answered_count >= 0 and correct_count >= 0 and wrong_count >= 0 and unanswered_count >= 0"); - - t.HasCheckConstraint("ck_practice_session_reports_duration", "duration_seconds >= 0"); - - t.HasCheckConstraint("ck_practice_session_reports_scores", "score >= 0 and total_score >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReportSection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Accuracy") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("accuracy"); - - b.Property("AnsweredCount") - .HasColumnType("integer") - .HasColumnName("answered_count"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("QuestionType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("question_type"); - - b.Property("ReportId") - .HasColumnType("uuid") - .HasColumnName("report_id"); - - b.Property("Score") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("score"); - - b.Property("SectionKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("section_key"); - - b.Property("SectionName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("section_name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("total_score"); - - b.Property("UnansweredCount") - .HasColumnType("integer") - .HasColumnName("unanswered_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.HasKey("Id") - .HasName("pk_practice_session_report_sections"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_report_sections_tenant_id_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_practice_session~"); - - b.HasIndex("TenantId", "ReportId", "SectionKey") - .IsUnique() - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_report_id_sectio~"); - - b.HasIndex("TenantId", "ReportId", "SortOrder") - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_report_id_sort_o~"); - - b.ToTable("practice_session_report_sections", null, t => - { - t.HasCheckConstraint("ck_practice_session_report_sections_accuracy", "accuracy >= 0 and accuracy <= 1"); - - t.HasCheckConstraint("ck_practice_session_report_sections_counts", "question_count >= 0 and answered_count >= 0 and correct_count >= 0 and wrong_count >= 0 and unanswered_count >= 0"); - - t.HasCheckConstraint("ck_practice_session_report_sections_scores", "score >= 0 and total_score >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RecentPractice", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LastAccessAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_access_at"); - - b.Property("LastPracticeAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_practice_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("practice_type"); - - b.Property("Progress") - .HasColumnType("integer") - .HasColumnName("progress"); - - b.Property("TargetLegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("target_legacy_id"); - - b.Property("TargetName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("target_name"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_recent_practices"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_recent_practices_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_recent_practices_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_recent_practices_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UserId", "LastAccessAt") - .HasDatabaseName("ix_recent_practices_tenant_id_user_id_last_access_at"); - - b.ToTable("recent_practices", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.Report", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attachments") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("attachments") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("Contact") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("contact"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("HandledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("handled_at"); - - b.Property("HandledBy") - .HasColumnType("uuid") - .HasColumnName("handled_by"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("priority"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("Resolution") - .HasColumnType("text") - .HasColumnName("resolution"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_reports_tenant_id_id"); - - b.HasIndex("HandledBy") - .HasDatabaseName("ix_reports_handled_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_reports_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_reports_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "QuestionId", "CreatedAt") - .HasDatabaseName("ix_reports_tenant_id_question_id_created_at") - .HasFilter("question_id is not null"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_reports_tenant_id_status_created_at"); - - b.ToTable("reports", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ReportStatusEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("ReportId") - .HasColumnType("uuid") - .HasColumnName("report_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_report_status_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_report_status_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_report_status_events_actor_user_id"); - - b.HasIndex("TenantId", "ReportId", "CreatedAt") - .HasDatabaseName("ix_report_status_events_tenant_id_report_id_created_at"); - - b.ToTable("report_status_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RevenueDailyStat", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CodeCount") - .HasColumnType("integer") - .HasColumnName("code_count"); - - b.Property("CodeEstimated") - .HasColumnType("integer") - .HasColumnName("code_estimated"); - - b.Property("CodeUsed") - .HasColumnType("integer") - .HasColumnName("code_used"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EstimatedRevenueCents") - .HasColumnType("integer") - .HasColumnName("estimated_revenue_cents"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("OrderCount") - .HasColumnType("integer") - .HasColumnName("order_count"); - - b.Property("RealRevenueCents") - .HasColumnType("integer") - .HasColumnName("real_revenue_cents"); - - b.Property("RebuiltAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rebuilt_at"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("StatDate") - .HasColumnType("date") - .HasColumnName("stat_date"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_revenue_daily_stats"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_revenue_daily_stats_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_region_id"); - - b.HasIndex("TenantId", "StatDate", "RegionId", "SaleType") - .IsUnique() - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_stat_date_region_id_sale_type"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "StatDate", "RegionId", "SaleType"), false); - - b.ToTable("revenue_daily_stats", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.UserScoreEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BalanceAfter") - .HasColumnType("integer") - .HasColumnName("balance_after"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("event_type"); - - b.Property("IdempotencyKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("idempotency_key"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_score_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_score_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_score_events_user_id"); - - b.HasIndex("TenantId", "IdempotencyKey") - .IsUnique() - .HasDatabaseName("ix_user_score_events_tenant_id_idempotency_key"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_user_score_events_tenant_id_user_id_created_at"); - - b.ToTable("user_score_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.WrongQuestion", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("LastWrongAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_wrong_at") - .HasDefaultValueSql("now()"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("WrongCount") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(1) - .HasColumnName("wrong_count"); - - b.HasKey("TenantId", "UserId", "QuestionReferenceId") - .HasName("pk_wrong_questions"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_wrong_questions_user_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_wrong_questions_question_owner_tenant_id_question_id"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_wrong_questions_tenant_id_question_reference_id"); - - b.ToTable("wrong_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Announcement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BackgroundColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("background_color"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Link") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("link"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_announcements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_announcements_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_announcements_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "IsActive", "SortOrder") - .HasDatabaseName("ix_announcements_tenant_id_is_active_sort_order"); - - b.ToTable("announcements", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.AuditLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Action") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("action"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("TargetId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_audit_logs"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_audit_logs_actor_user_id"); - - b.HasIndex("TenantId", "CreatedAt") - .HasDatabaseName("ix_audit_logs_tenant_id_created_at"); - - b.HasIndex("TenantId", "TargetType", "TargetId", "CreatedAt") - .HasDatabaseName("ix_audit_logs_tenant_id_target_type_target_id_created_at"); - - b.ToTable("audit_logs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Area") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("area"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Icon") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("ParentCode") - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("parent_code"); - - b.Property("Path") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("path"); - - b.Property("PermissionCode") - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_backend_menus"); - - b.HasAlternateKey("Code") - .HasName("ak_backend_menus_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_backend_menus_code"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_backend_menus_permission_code"); - - b.HasIndex("Area", "ParentCode", "SortOrder") - .HasDatabaseName("ix_backend_menus_area_parent_code_sort_order"); - - b.ToTable("backend_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendPermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Area") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("area"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Module") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("module"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_backend_permissions"); - - b.HasAlternateKey("Code") - .HasName("ak_backend_permissions_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_backend_permissions_code"); - - b.HasIndex("Area", "Module", "SortOrder") - .HasDatabaseName("ix_backend_permissions_area_module_sort_order"); - - b.ToTable("backend_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("JobType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("job_type"); - - b.Property("LastError") - .HasMaxLength(4000) - .HasColumnType("character varying(4000)") - .HasColumnName("last_error"); - - b.Property("LockExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("lock_expires_at"); - - b.Property("LockedBy") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("locked_by"); - - b.Property("MaxRetries") - .HasColumnType("integer") - .HasColumnName("max_retries"); - - b.Property("OutputAssetId") - .HasColumnType("uuid") - .HasColumnName("output_asset_id"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Result") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("result") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RetryCount") - .HasColumnType("integer") - .HasColumnName("retry_count"); - - b.Property("RunAfter") - .HasColumnType("timestamp with time zone") - .HasColumnName("run_after"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_background_jobs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_background_jobs_tenant_id_id"); - - b.HasIndex("TenantId", "OutputAssetId") - .HasDatabaseName("ix_background_jobs_tenant_id_output_asset_id"); - - b.HasIndex("TenantId", "JobType", "Status", "CreatedAt") - .HasDatabaseName("ix_background_jobs_tenant_id_job_type_status_created_at"); - - b.HasIndex("TenantId", "Status", "RunAfter", "CreatedAt") - .HasDatabaseName("ix_background_jobs_tenant_id_status_run_after_created_at"); - - b.ToTable("background_jobs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Badge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("ConditionExtra") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("condition_extra") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ConditionField") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("condition_field"); - - b.Property("ConditionOperator") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("condition_operator"); - - b.Property("ConditionValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)") - .HasColumnName("condition_value"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IconUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon_url"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Level") - .HasColumnType("integer") - .HasColumnName("level"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnlockType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("unlock_type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_badges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_badges_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_badges_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "Category", "IsActive", "SortOrder") - .HasDatabaseName("ix_badges_tenant_id_category_is_active_sort_order"); - - b.ToTable("badges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Banner", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BackgroundColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("background_color"); - - b.Property("BorderColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("border_color"); - - b.Property("ButtonLink") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("button_link"); - - b.Property("ButtonText") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("button_text"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Subtitle") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("subtitle"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_banners"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_banners_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_banners_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_banners_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("banners", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Faq", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Answer") - .HasColumnType("text") - .HasColumnName("answer"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Question") - .HasColumnType("text") - .HasColumnName("question"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_faqs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_faqs_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_faqs_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_faqs_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("faqs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_backend_roles"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_backend_roles_code"); - - b.HasIndex("Status", "Code") - .HasDatabaseName("ix_platform_backend_roles_status_code"); - - b.ToTable("platform_backend_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("MenuCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("menu_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_role_menus"); - - b.HasIndex("MenuCode") - .HasDatabaseName("ix_platform_backend_role_menus_menu_code"); - - b.HasIndex("RoleId", "MenuCode") - .IsUnique() - .HasDatabaseName("ix_platform_backend_role_menus_role_id_menu_code"); - - b.ToTable("platform_backend_role_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("PermissionCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_role_permissions"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_platform_backend_role_permissions_permission_code"); - - b.HasIndex("RoleId", "PermissionCode") - .IsUnique() - .HasDatabaseName("ix_platform_backend_role_permissions_role_id_permission_code"); - - b.ToTable("platform_backend_role_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_user_roles"); - - b.HasIndex("RoleId") - .HasDatabaseName("ix_platform_backend_user_roles_role_id"); - - b.HasIndex("UserId", "RoleId") - .IsUnique() - .HasDatabaseName("ix_platform_backend_user_roles_user_id_role_id"); - - b.ToTable("platform_backend_user_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DataScope") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("data_scope") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_roles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_roles_tenant_id_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_roles_tenant_id_code"); - - b.HasIndex("TenantId", "Status", "Code") - .HasDatabaseName("ix_tenant_backend_roles_tenant_id_status_code"); - - b.ToTable("tenant_backend_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("MenuCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("menu_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_role_menus"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_role_menus_tenant_id_id"); - - b.HasIndex("MenuCode") - .HasDatabaseName("ix_tenant_backend_role_menus_menu_code"); - - b.HasIndex("TenantId", "RoleId", "MenuCode") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_role_menus_tenant_id_role_id_menu_code"); - - b.ToTable("tenant_backend_role_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("PermissionCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_role_permissions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_role_permissions_tenant_id_id"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_tenant_backend_role_permissions_permission_code"); - - b.HasIndex("TenantId", "RoleId", "PermissionCode") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_role_permissions_tenant_id_role_id_permissio~"); - - b.ToTable("tenant_backend_role_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_user_roles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_user_roles_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_backend_user_roles_user_id"); - - b.HasIndex("TenantId", "RoleId") - .HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_role_id"); - - b.HasIndex("TenantId", "UserId", "RoleId") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_user_id_role_id"); - - b.ToTable("tenant_backend_user_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActionLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("action_label"); - - b.Property("ActionPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("action_path"); - - b.Property("AdoptionId") - .HasColumnType("uuid") - .HasColumnName("adoption_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DedupeKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("dedupe_key"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NotificationType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("notification_type"); - - b.Property("ReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("read_at"); - - b.Property("ReadBy") - .HasColumnType("uuid") - .HasColumnName("read_by"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("SourceQuestionBankId") - .HasColumnType("uuid") - .HasColumnName("source_question_bank_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_content_notifications"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_content_notifications_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_content_notifications_created_by"); - - b.HasIndex("ReadBy") - .HasDatabaseName("ix_tenant_content_notifications_read_by"); - - b.HasIndex("TenantId", "SourceQuestionBankId") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_source_question_bank~"); - - b.HasIndex("TenantId", "NotificationType", "DedupeKey") - .IsUnique() - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_notification_type_de~") - .HasFilter("dedupe_key is not null"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_status_created_at"); - - b.HasIndex("TenantId", "AdoptionId", "NotificationType", "Status") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_adoption_id_notifica~"); - - b.ToTable("tenant_content_notifications", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActivePublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("active_public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ActiveTemplateCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("active_template_code"); - - b.Property("ActiveTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("active_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DraftPublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftTemplateCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("draft_template_code"); - - b.Property("DraftTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftUpdatedBy") - .HasColumnType("uuid") - .HasColumnName("draft_updated_by"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublishedBy") - .HasColumnType("uuid") - .HasColumnName("published_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_theme_configs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_theme_configs_tenant_id_id"); - - b.HasIndex("ActiveTemplateCode") - .HasDatabaseName("ix_tenant_theme_configs_active_template_code"); - - b.HasIndex("DraftTemplateCode") - .HasDatabaseName("ix_tenant_theme_configs_draft_template_code"); - - b.HasIndex("DraftUpdatedBy") - .HasDatabaseName("ix_tenant_theme_configs_draft_updated_by"); - - b.HasIndex("PublishedBy") - .HasDatabaseName("ix_tenant_theme_configs_published_by"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_theme_configs_tenant_id"); - - b.ToTable("tenant_theme_configs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("PreviewImageUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_image_url"); - - b.Property("PublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Theme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_theme_templates"); - - b.HasAlternateKey("Code") - .HasName("ak_tenant_theme_templates_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_tenant_theme_templates_code"); - - b.HasIndex("Status", "SortOrder", "Code") - .HasDatabaseName("ix_tenant_theme_templates_status_sort_order_code"); - - b.ToTable("tenant_theme_templates", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserBadge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BadgeId") - .HasColumnType("uuid") - .HasColumnName("badge_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("GrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("granted_at"); - - b.Property("GrantedBy") - .HasColumnType("uuid") - .HasColumnName("granted_by"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_badges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_badges_tenant_id_id"); - - b.HasIndex("GrantedBy") - .HasDatabaseName("ix_user_badges_granted_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_badges_user_id"); - - b.HasIndex("TenantId", "BadgeId") - .HasDatabaseName("ix_user_badges_tenant_id_badge_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_user_badges_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UserId", "BadgeId") - .IsUnique() - .HasDatabaseName("ix_user_badges_tenant_id_user_id_badge_id"); - - b.ToTable("user_badges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserNotification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActionLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("action_label"); - - b.Property("ActionPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("action_path"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DedupeKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("dedupe_key"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NotificationType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("notification_type"); - - b.Property("ReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("read_at"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_notifications"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_notifications_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_user_notifications_created_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_notifications_user_id"); - - b.HasIndex("TenantId", "SourceType", "SourceId") - .HasDatabaseName("ix_user_notifications_tenant_id_source_type_source_id"); - - b.HasIndex("TenantId", "UserId", "NotificationType", "DedupeKey") - .IsUnique() - .HasDatabaseName("ix_user_notifications_tenant_id_user_id_notification_type_dedu~") - .HasFilter("dedupe_key is not null"); - - b.HasIndex("TenantId", "UserId", "Status", "CreatedAt") - .HasDatabaseName("ix_user_notifications_tenant_id_user_id_status_created_at"); - - b.ToTable("user_notifications", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AcknowledgedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("acknowledged_at"); - - b.Property("AcknowledgedBy") - .HasColumnType("uuid") - .HasColumnName("acknowledged_by"); - - b.Property("Action") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("action"); - - b.Property("AuditLogId") - .HasColumnType("uuid") - .HasColumnName("audit_log_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FirstSeenAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("first_seen_at") - .HasDefaultValueSql("now()"); - - b.Property("LastSeenAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_seen_at") - .HasDefaultValueSql("now()"); - - b.Property("ResolutionNote") - .HasColumnType("text") - .HasColumnName("resolution_note"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("ResolvedBy") - .HasColumnType("uuid") - .HasColumnName("resolved_by"); - - b.Property("RuleId") - .HasColumnType("uuid") - .HasColumnName("rule_id"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .HasColumnType("text") - .HasColumnName("summary"); - - b.Property("TargetId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_audit_alerts"); - - b.HasIndex("AcknowledgedBy") - .HasDatabaseName("ix_platform_audit_alerts_acknowledged_by"); - - b.HasIndex("AuditLogId") - .HasDatabaseName("ix_platform_audit_alerts_audit_log_id"); - - b.HasIndex("ResolvedBy") - .HasDatabaseName("ix_platform_audit_alerts_resolved_by"); - - b.HasIndex("RuleId", "AuditLogId") - .IsUnique() - .HasDatabaseName("ix_platform_audit_alerts_rule_id_audit_log_id"); - - b.HasIndex("Status", "Severity", "CreatedAt") - .HasDatabaseName("ix_platform_audit_alerts_status_severity_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_audit_alerts_tenant_id_status_created_at"); - - b.ToTable("platform_audit_alerts", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlertRule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.PrimitiveCollection("ActionPatterns") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("action_patterns") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("Conditions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("conditions") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.PrimitiveCollection("TargetTypes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("target_types") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_audit_alert_rules"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_audit_alert_rules_code"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_platform_audit_alert_rules_tenant_id"); - - b.HasIndex("Enabled", "Severity", "Code") - .HasDatabaseName("ix_platform_audit_alert_rules_enabled_severity_code"); - - b.ToTable("platform_audit_alert_rules", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationChannel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ChannelCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel_code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MinReminderLevel") - .HasColumnType("integer") - .HasColumnName("min_reminder_level"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("provider"); - - b.PrimitiveCollection("ReminderChannels") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("reminder_channels") - .HasDefaultValueSql("array['internal']::text[]"); - - b.PrimitiveCollection("ReminderTypes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("reminder_types") - .HasDefaultValueSql("array['overdue', 'final_notice']::text[]"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.PrimitiveCollection("TenantIds") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("uuid[]") - .HasColumnName("tenant_ids") - .HasDefaultValueSql("'{}'::uuid[]"); - - b.Property("TimeoutSeconds") - .HasColumnType("integer") - .HasColumnName("timeout_seconds"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WebhookUrl") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("webhook_url"); - - b.HasKey("Id") - .HasName("pk_platform_dunning_notification_channels"); - - b.HasIndex("ChannelCode") - .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_channels_channel_code"); - - b.HasIndex("Enabled", "MinReminderLevel", "ChannelCode") - .HasDatabaseName("ix_platform_dunning_notification_channels_enabled_min_reminder~"); - - b.ToTable("platform_dunning_notification_channels", null, t => - { - t.HasCheckConstraint("ck_platform_dunning_channels_level", "min_reminder_level between 1 and 20"); - - t.HasCheckConstraint("ck_platform_dunning_channels_timeout", "timeout_seconds between 1 and 60"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("ChannelId") - .HasColumnType("uuid") - .HasColumnName("channel_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("LastAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_attempt_at"); - - b.Property("LastError") - .HasColumnType("text") - .HasColumnName("last_error"); - - b.Property("LastHttpCode") - .HasColumnType("integer") - .HasColumnName("last_http_code"); - - b.Property("LastResponseSummary") - .HasColumnType("text") - .HasColumnName("last_response_summary"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NextAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_attempt_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("provider"); - - b.Property("ReminderId") - .HasColumnType("uuid") - .HasColumnName("reminder_id"); - - b.Property("RequestPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("request_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ScheduledAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("scheduled_at") - .HasDefaultValueSql("now()"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_dunning_notification_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_platform_dunning_notification_events_tenant_id_id"); - - b.HasIndex("ChannelId") - .HasDatabaseName("ix_platform_dunning_notification_events_channel_id"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_invoice_id"); - - b.HasIndex("TenantId", "ReminderId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_reminder_id"); - - b.HasIndex("InvoiceId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_invoice_id_status_crea~"); - - b.HasIndex("ReminderId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_reminder_id_status_cre~"); - - b.HasIndex("TenantId", "ChannelId", "ReminderId") - .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_channel_id_r~"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_status_creat~"); - - b.HasIndex("Status", "NextAttemptAt", "ScheduledAt", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_status_next_attempt_at~"); - - b.ToTable("platform_dunning_notification_events", null, t => - { - t.HasCheckConstraint("ck_platform_dunning_events_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformSaasPlan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BaseAmountCents") - .HasColumnType("integer") - .HasColumnName("base_amount_cents"); - - b.Property("BillingCycle") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("billing_cycle"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("FeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("IncludedQuotas") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("included_quotas") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OveragePrices") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("overage_prices") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_saas_plans"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_saas_plans_code"); - - b.HasIndex("Status", "SortOrder") - .HasDatabaseName("ix_platform_saas_plans_status_sort_order"); - - b.ToTable("platform_saas_plans", null, t => - { - t.HasCheckConstraint("ck_platform_saas_plans_amount", "base_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("BankAccountMasked") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("bank_account_masked"); - - b.Property("BankName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("bank_name"); - - b.Property("BillingAddress") - .HasColumnType("text") - .HasColumnName("billing_address"); - - b.Property("BillingName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("billing_name"); - - b.Property("ContactEmail") - .HasMaxLength(300) - .HasColumnType("citext") - .HasColumnName("contact_email"); - - b.Property("ContactName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("contact_name"); - - b.Property("ContactPhone") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("contact_phone"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceTitle") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("invoice_title"); - - b.Property("InvoiceType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TaxId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("tax_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_billing_profiles"); - - b.ToTable("tenant_billing_profiles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BalanceCents") - .HasColumnType("integer") - .HasColumnName("balance_cents"); - - b.Property("BillingPeriodEnd") - .HasColumnType("date") - .HasColumnName("billing_period_end"); - - b.Property("BillingPeriodStart") - .HasColumnType("date") - .HasColumnName("billing_period_start"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("DiscountCents") - .HasColumnType("integer") - .HasColumnName("discount_cents"); - - b.Property("DueDate") - .HasColumnType("date") - .HasColumnName("due_date"); - - b.Property("InvoiceNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("invoice_no"); - - b.Property("InvoiceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - - b.Property("IssuedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("issued_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaidCents") - .HasColumnType("integer") - .HasColumnName("paid_cents"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubtotalCents") - .HasColumnType("integer") - .HasColumnName("subtotal_cents"); - - b.Property("TaxCents") - .HasColumnType("integer") - .HasColumnName("tax_cents"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCents") - .HasColumnType("integer") - .HasColumnName("total_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoices"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoices_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoices_created_by"); - - b.HasIndex("TenantId", "InvoiceNo") - .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_invoice_no"); - - b.HasIndex("TenantId", "BillingPeriodStart", "BillingPeriodEnd") - .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_billing_period_start_billing_peri~") - .HasFilter("invoice_type = 'usage_overage' and status <> 'void' and metadata->>'source' = 'usage_overage_auto'"); - - b.HasIndex("TenantId", "Status", "DueDate") - .HasDatabaseName("ix_tenant_invoices_tenant_id_status_due_date"); - - b.ToTable("tenant_invoices", null, t => - { - t.HasCheckConstraint("ck_tenant_invoices_amounts", "subtotal_cents >= 0 and discount_cents >= 0 and tax_cents >= 0 and total_cents >= 0 and paid_cents >= 0 and balance_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoices_period", "billing_period_end is null or billing_period_start is null or billing_period_end >= billing_period_start"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("description"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("ItemRefId") - .HasColumnType("uuid") - .HasColumnName("item_ref_id"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Quantity") - .HasPrecision(12, 2) - .HasColumnType("numeric(12,2)") - .HasColumnName("quantity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitAmountCents") - .HasColumnType("integer") - .HasColumnName("unit_amount_cents"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_items_tenant_id_id"); - - b.HasIndex("InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_invoice_id"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_tenant_id_invoice_id"); - - b.ToTable("tenant_invoice_items", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_items_amounts", "unit_amount_cents >= 0 and amount_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoice_items_quantity", "quantity > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("Method") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("method"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_no"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ReceivedBy") - .HasColumnType("uuid") - .HasColumnName("received_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_payments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_payments_tenant_id_id"); - - b.HasIndex("ReceivedBy") - .HasDatabaseName("ix_tenant_invoice_payments_received_by"); - - b.HasIndex("InvoiceId", "Status") - .HasDatabaseName("ix_tenant_invoice_payments_invoice_id_status"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_invoice_id"); - - b.HasIndex("TenantId", "PaymentNo") - .IsUnique() - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_payment_no"); - - b.ToTable("tenant_invoice_payments", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_payments_amount", "amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AcknowledgedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("acknowledged_at"); - - b.Property("BalanceCentsSnapshot") - .HasColumnType("integer") - .HasColumnName("balance_cents_snapshot"); - - b.Property("Channel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("channel"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DueDate") - .HasColumnType("date") - .HasColumnName("due_date"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("Message") - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ReminderDate") - .HasColumnType("date") - .HasColumnName("reminder_date"); - - b.Property("ReminderLevel") - .HasColumnType("integer") - .HasColumnName("reminder_level"); - - b.Property("ReminderType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("reminder_type"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_reminders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_reminders_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoice_reminders_created_by"); - - b.HasIndex("InvoiceId", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_invoice_id_reminder_date"); - - b.HasIndex("TenantId", "Status", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_status_reminder_date"); - - b.HasIndex("TenantId", "InvoiceId", "ReminderType", "Channel", "ReminderDate") - .IsUnique() - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_invoice_id_reminder_type~"); - - b.ToTable("tenant_invoice_reminders", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_reminders_balance", "balance_cents_snapshot >= 0"); - - t.HasCheckConstraint("ck_tenant_invoice_reminders_level", "reminder_level between 1 and 20"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CurrentVersionId") - .HasColumnType("uuid") - .HasColumnName("current_version_id"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("ExamMarkers") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("exam_markers") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("HasVideoExplanation") - .HasColumnType("boolean") - .HasColumnName("has_video_explanation"); - - b.Property("LegacyCategoryId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_category_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyNodeId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_node_id"); - - b.Property("LegacySubjectId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_subject_id"); - - b.Property("MediaUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("media_url"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("PrimaryCollectionId") - .HasColumnType("uuid") - .HasColumnName("primary_collection_id"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("type"); - - b.Property("TypeLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("type_label"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_questions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_questions_tenant_id_id"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_questions_tenant_id_category_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_questions_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_questions_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_questions_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_questions_tenant_id_node_id"); - - b.HasIndex("TenantId", "PrimaryCollectionId") - .HasDatabaseName("ix_questions_tenant_id_primary_collection_id"); - - b.HasIndex("TenantId", "QuestionBankId") - .HasDatabaseName("ix_questions_tenant_id_question_bank_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_questions_tenant_id_subject_id"); - - b.HasIndex("TenantId", "Id", "CurrentVersionId") - .HasDatabaseName("ix_questions_tenant_id_id_current_version_id"); - - b.ToTable("questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionBank", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_banks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_banks_tenant_id_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_question_banks_tenant_id_region_id"); - - b.ToTable("question_banks", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AnswerText") - .HasColumnType("text") - .HasColumnName("answer_text"); - - b.Property("CodeLang") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("code_lang"); - - b.Property("CodeTemplate") - .HasColumnType("text") - .HasColumnName("code_template"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CorrectOptionIndex") - .HasColumnType("integer") - .HasColumnName("correct_option_index"); - - b.Property("CorrectOptionIndices") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("correct_option_indices") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Explanation") - .HasColumnType("text") - .HasColumnName("explanation"); - - b.Property("Options") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubQuestions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("sub_questions") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("VersionNo") - .HasColumnType("integer") - .HasColumnName("version_no"); - - b.HasKey("Id") - .HasName("pk_question_versions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_versions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "QuestionId", "Id") - .HasName("ak_question_versions_tenant_id_question_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_question_versions_created_by"); - - b.HasIndex("TenantId", "QuestionId", "VersionNo") - .IsUnique() - .HasDatabaseName("ix_question_versions_tenant_id_question_id_version_no"); - - b.ToTable("question_versions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ConsumedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("consumed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("purpose"); - - b.Property("Realm") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("realm"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("security_stamp"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("token_hash"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_challenges"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_auth_challenges_tenant_id"); - - b.HasIndex("TokenHash") - .IsUnique() - .HasDatabaseName("ix_auth_challenges_token_hash"); - - b.HasIndex("UserId", "Purpose", "ExpiresAt") - .HasDatabaseName("ix_auth_challenges_user_id_purpose_expires_at"); - - b.ToTable("auth_challenges", null, t => - { - t.HasCheckConstraint("ck_auth_challenges_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FailureCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("failure_code"); - - b.Property("Identifier") - .HasMaxLength(320) - .HasColumnType("character varying(320)") - .HasColumnName("identifier"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Result") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("result"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_login_events"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_auth_login_events_user_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_auth_login_events_tenant_id_user_id_created_at"); - - b.ToTable("auth_login_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MfaSatisfied") - .HasColumnType("boolean") - .HasColumnName("mfa_satisfied"); - - b.Property("ParentSessionId") - .HasColumnType("uuid") - .HasColumnName("parent_session_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Realm") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("realm"); - - b.Property("ReplacedBySessionId") - .HasColumnType("uuid") - .HasColumnName("replaced_by_session_id"); - - b.Property("RevokedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("revoked_at"); - - b.Property("RevokedReason") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("revoked_reason"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("security_stamp"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TokenFamilyId") - .HasColumnType("uuid") - .HasColumnName("token_family_id"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("token_hash"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_sessions"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_auth_sessions_tenant_id"); - - b.HasIndex("TokenHash") - .IsUnique() - .HasDatabaseName("ix_auth_sessions_token_hash") - .HasAnnotation("Tiku:GlobalUnique", true); - - b.HasIndex("UserId") - .HasDatabaseName("ix_auth_sessions_user_id"); - - b.HasIndex("TokenFamilyId", "RevokedAt") - .HasDatabaseName("ix_auth_sessions_token_family_id_revoked_at"); - - b.HasIndex("Realm", "TenantId", "UserId", "ExpiresAt") - .HasDatabaseName("ix_auth_sessions_realm_tenant_id_user_id_expires_at") - .HasFilter("revoked_at is null"); - - b.ToTable("auth_sessions", null, t => - { - t.HasCheckConstraint("ck_auth_sessions_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsSendRateLimit", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Dimension") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("dimension"); - - b.Property("ScopeHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("scope_hash"); - - b.Property("BucketStart") - .HasColumnType("timestamp with time zone") - .HasColumnName("bucket_start"); - - b.Property("RequestCount") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(0) - .HasColumnName("request_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId", "Dimension", "ScopeHash", "BucketStart") - .HasName("pk_sms_send_rate_limits"); - - b.HasIndex("UpdatedAt") - .HasDatabaseName("ix_sms_send_rate_limits_updated_at"); - - b.ToTable("sms_send_rate_limits", null, t => - { - t.HasCheckConstraint("ck_sms_send_rate_limits_request_count", "request_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsVerificationCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("CodeHash") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("code_hash"); - - b.Property("ConsumedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("consumed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Phone") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("purpose"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_sms_verification_codes"); - - b.HasIndex("TenantId", "Phone", "Purpose") - .IsUnique() - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose") - .HasFilter("consumed_at is null and status in ('pending', 'sent')"); - - b.HasIndex("TenantId", "Phone", "Purpose", "CreatedAt") - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose_created_at") - .HasFilter("consumed_at is null and status in ('pending', 'sent')"); - - b.HasIndex("TenantId", "Phone", "Purpose", "ExpiresAt") - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose_expires_at"); - - b.ToTable("sms_verification_codes", null, t => - { - t.HasCheckConstraint("ck_sms_verification_codes_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.Tenant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BillingStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("billing_status"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegalName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("legal_name"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("mode"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OwnerUserId") - .HasColumnType("uuid") - .HasColumnName("owner_user_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("slug"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenants"); - - b.HasIndex("LegacyId") - .IsUnique() - .HasDatabaseName("ix_tenants_legacy_id"); - - b.HasIndex("Mode") - .IsUnique() - .HasDatabaseName("ix_tenants_mode") - .HasFilter("mode = 'platform_owned'"); - - b.HasIndex("OwnerUserId") - .HasDatabaseName("ix_tenants_owner_user_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_tenants_slug"); - - b.ToTable("tenants", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantBranding", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("BrandName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("brand_name"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FaviconUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("favicon_url"); - - b.Property("LogoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("logo_url"); - - b.Property("OrganizationName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("organization_name"); - - b.Property("PublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ServiceAccountName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("service_account_name"); - - b.Property("ServiceWechat") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("service_wechat"); - - b.Property("ShortName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("short_name"); - - b.Property("Slogan") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("slogan"); - - b.Property("Theme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_branding"); - - b.ToTable("tenant_branding", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClass", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_classes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_classes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_classes_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_classes_updated_by"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_tenant_classes_tenant_id_code") - .HasFilter("code is not null and code <> ''"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_tenant_classes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_tenant_classes_tenant_id_region_id") - .HasFilter("region_id is not null"); - - b.HasIndex("TenantId", "Status", "SortOrder", "CreatedAt") - .HasDatabaseName("ix_tenant_classes_tenant_id_status_sort_order_created_at"); - - b.ToTable("tenant_classes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClassMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClassId") - .HasColumnType("uuid") - .HasColumnName("class_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("JoinedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at") - .HasDefaultValueSql("now()"); - - b.Property("LeftAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("left_at"); - - b.Property("MemberType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("member_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_class_members"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_class_members_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_class_members_user_id"); - - b.HasIndex("TenantId", "ClassId", "Status", "MemberType") - .HasDatabaseName("ix_tenant_class_members_tenant_id_class_id_status_member_type"); - - b.HasIndex("TenantId", "ClassId", "UserId", "MemberType") - .IsUnique() - .HasDatabaseName("ix_tenant_class_members_tenant_id_class_id_user_id_member_type"); - - b.HasIndex("TenantId", "UserId", "Status", "MemberType") - .HasDatabaseName("ix_tenant_class_members_tenant_id_user_id_status_member_type"); - - b.ToTable("tenant_class_members", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantDomain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DnsVerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("dns_verified_at"); - - b.Property("DomainType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("domain_type"); - - b.Property("Host") - .IsRequired() - .HasMaxLength(253) - .HasColumnType("citext") - .HasColumnName("host"); - - b.Property("IsPrimary") - .HasColumnType("boolean") - .HasColumnName("is_primary"); - - b.Property("LastCheckedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_checked_at"); - - b.Property("LastFailureReason") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("last_failure_reason"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TlsReadyAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("tls_ready_at"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VerificationToken") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("verification_token"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_tenant_domains"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_domains_tenant_id_id"); - - b.HasIndex("Host") - .IsUnique() - .HasDatabaseName("ix_tenant_domains_host") - .HasAnnotation("Tiku:GlobalUnique", true); - - b.HasIndex("TenantId", "IsPrimary") - .IsUnique() - .HasDatabaseName("ix_tenant_domains_tenant_id_is_primary") - .HasFilter("is_primary"); - - b.ToTable("tenant_domains", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantExternalProvider", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Capability") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("capability"); - - b.Property("ConfigPublic") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config_public") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("display_name"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_external_providers"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_external_providers_tenant_id_id"); - - b.HasIndex("TenantId", "Capability", "Provider") - .IsUnique() - .HasDatabaseName("ix_tenant_external_providers_tenant_id_capability_provider"); - - b.HasIndex("TenantId", "Capability", "Status", "Priority") - .HasDatabaseName("ix_tenant_external_providers_tenant_id_capability_status_prior~"); - - b.ToTable("tenant_external_providers", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantFrontendConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ConfigVersion") - .IsConcurrencyToken() - .HasColumnType("integer") - .HasColumnName("config_version"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DraftBranding") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_branding") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftFeatures") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_features") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftHomeModules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_home_modules") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("DraftNavigation") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_navigation") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("DraftTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublishedBranding") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_branding") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedFeatures") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_features") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedHomeModules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_home_modules") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("PublishedNavigation") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_navigation") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("PublishedTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SchemaVersion") - .HasColumnType("integer") - .HasColumnName("schema_version"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_frontend_configs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_frontend_configs_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_frontend_configs_tenant_id"); - - b.ToTable("tenant_frontend_configs", null, t => - { - t.HasCheckConstraint("ck_tenant_frontend_configs_config_version", "config_version > 0"); - - t.HasCheckConstraint("ck_tenant_frontend_configs_schema_version", "schema_version > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantMembership", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyRole") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("legacy_role"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("role"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_memberships"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_memberships_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_memberships_user_id"); - - b.HasIndex("TenantId", "UserId", "Role") - .IsUnique() - .HasDatabaseName("ix_tenant_memberships_tenant_id_user_id_role"); - - b.ToTable("tenant_memberships", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EncryptedPayload") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encrypted_payload"); - - b.Property("EncryptionKeyId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("encryption_key_id"); - - b.Property("EncryptionNonce") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encryption_nonce"); - - b.Property("EncryptionTag") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encryption_tag"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)") - .HasColumnName("purpose"); - - b.Property("RotatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rotated_at"); - - b.Property("SecretKey") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)") - .HasColumnName("secret_key"); - - b.Property("SecretRef") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_secrets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_secrets_tenant_id_id"); - - b.HasIndex("TenantId", "SecretRef") - .IsUnique() - .HasDatabaseName("ix_tenant_secrets_tenant_id_secret_ref"); - - b.HasIndex("TenantId", "Purpose", "Provider", "SecretKey") - .IsUnique() - .HasDatabaseName("ix_tenant_secrets_tenant_id_purpose_provider_secret_key"); - - b.HasIndex("TenantId", "Purpose", "Provider", "Status") - .HasDatabaseName("ix_tenant_secrets_tenant_id_purpose_provider_status"); - - b.ToTable("tenant_secrets", null, t => - { - t.HasCheckConstraint("ck_tenant_secrets_encryption_envelope", "octet_length(encrypted_payload) > 0 and octet_length(encryption_nonce) = 12 and octet_length(encryption_tag) = 16 and encryption_key_id <> ''"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSettings", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("AdminFeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("admin_feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublicConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_settings"); - - b.ToTable("tenant_settings", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentFollowup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignedToUserId") - .HasColumnType("uuid") - .HasColumnName("assigned_to_user_id"); - - b.Property("ClassId") - .HasColumnType("uuid") - .HasColumnName("class_id"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CompletedBy") - .HasColumnType("uuid") - .HasColumnName("completed_by"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("DueAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("due_at"); - - b.Property("FollowupType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("followup_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("priority"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_student_followups"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_student_followups_tenant_id_id"); - - b.HasIndex("AssignedToUserId") - .HasDatabaseName("ix_tenant_student_followups_assigned_to_user_id"); - - b.HasIndex("CompletedBy") - .HasDatabaseName("ix_tenant_student_followups_completed_by"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_tenant_student_followups_student_user_id"); - - b.HasIndex("TenantId", "AssignedToUserId", "Status", "DueAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_assigned_to_user_id_stat~") - .HasFilter("assigned_to_user_id is not null"); - - b.HasIndex("TenantId", "ClassId", "Status", "DueAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_class_id_status_due_at") - .HasFilter("class_id is not null"); - - b.HasIndex("TenantId", "StudentUserId", "Status", "DueAt", "CreatedAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_student_user_id_status_d~"); - - b.ToTable("tenant_student_followups", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentNote", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Content") - .IsRequired() - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("IsPinned") - .HasColumnType("boolean") - .HasColumnName("is_pinned"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NoteType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("note_type"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_tenant_student_notes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_student_notes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_student_notes_created_by"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_tenant_student_notes_student_user_id"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_student_notes_updated_by"); - - b.HasIndex("TenantId", "CreatedBy", "CreatedAt") - .HasDatabaseName("ix_tenant_student_notes_tenant_id_created_by_created_at") - .HasFilter("created_by is not null"); - - b.HasIndex("TenantId", "StudentUserId", "IsPinned", "CreatedAt") - .HasDatabaseName("ix_tenant_student_notes_tenant_id_student_user_id_is_pinned_cr~"); - - b.ToTable("tenant_student_notes", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_claims_users_user_id"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_logins_users_user_id"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_tokens_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Category", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_categories_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_categories_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_categories_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Major", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_majors_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_majors_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_majors_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ModuleNode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_module_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_module_nodes_tenant_id_parent_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.QuestionTaxonomyAssignment", b => - { - b.HasOne("Tiku.Domain.Catalog.TaxonomyNode", null) - .WithMany() - .HasForeignKey("TaxonomyOwnerTenantId", "TaxonomyNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_question_taxonomy_assignments_taxonomy_nodes_taxonomy_owner~"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_taxonomy_assignments_questions_tenant_id_question_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Region", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_regions_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.RegionModule", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_region_modules_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_region_modules_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.School", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_schools_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_schools_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_schools_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineField", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_scoreline_fields_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_fields_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_scoreline_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Subject", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subjects_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.TaxonomyNode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_taxonomy_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.TaxonomyNode", null) - .WithMany() - .HasForeignKey("ParentOwnerTenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_taxonomy_nodes_taxonomy_nodes_parent_owner_tenant_id_parent~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.ActivationCode", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AgentUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_users_agent_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activation_codes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UsedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_users_used_by"); - - b.HasOne("Tiku.Domain.Commerce.CodeBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_code_batches_tenant_id_batch_id"); - - b.HasOne("Tiku.Domain.Commerce.CouponRedemption", null) - .WithMany() - .HasForeignKey("TenantId", "CouponRedemptionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_coupon_redemptions_tenant_id_coupon_redemp~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "UsedRegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_regions_tenant_id_used_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CodeBatch", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_code_batches_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucher", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_adjustment_vouchers_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_reconciliation_batche~"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationIssue", null) - .WithMany() - .HasForeignKey("TenantId", "IssueId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_reconciliation_issues~"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_reconciliation_items_~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_refund_requests_tenan~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucherEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_voucher_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_adjustment_voucher_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceAdjustmentVoucher", null) - .WithMany() - .HasForeignKey("TenantId", "VoucherId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_adjustment_voucher_events_commerce_adjustment_vouc~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationBatch", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_batches_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_batches_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssue", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedTo") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_assigned_to"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ResolvedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_resolved_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_reconciliation_batc~"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_reconciliation_item~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_refund_requests_ten~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssueEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issue_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issue_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationIssue", null) - .WithMany() - .HasForeignKey("TenantId", "IssueId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issue_events_commerce_reconciliatio~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_items_commerce_reconciliation_batch~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_commerce_refund_requests_tena~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_events_commerce_refund_requests_tenant_id_r~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundRequest", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ProcessedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_processed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("RequestedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_requested_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_requests_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_requests_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_refund_requests_payments_tenant_id_payment_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Coupon", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_coupons_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_coupons_svip_plans_tenant_id_plan_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CouponRedemption", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_coupon_redemptions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.Coupon", null) - .WithMany() - .HasForeignKey("TenantId", "CouponId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_coupons_tenant_id_coupon_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_svip_plans_tenant_id_plan_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Entitlement", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("RevokedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_entitlements_users_revoked_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entitlements_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entitlements_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Order", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_orders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_orders_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_orders_svip_plans_tenant_id_plan_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_orders_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.OrderItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_order_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_order_items_orders_tenant_id_order_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Payment", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payments_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payments_orders_tenant_id_order_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PaymentEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_payment_events_payments_tenant_id_payment_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityClaim", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.PointActivityTask", null) - .WithMany() - .HasForeignKey("TenantId", "TaskId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_point_activity_tasks_tenant_id_task_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityTask", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_tasks_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_point_exchange_items_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeOrder", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.PointExchangeItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_point_exchange_items_tenant_id_item_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Product", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_products_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_products_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.SvipPlan", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_svip_plans_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_svip_plans_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_subscriptions_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_usage_records_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_ai_recommendation_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_ai_recommendation_reports_users_user_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_ai_recommendation_reports_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AppAsset", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_app_assets_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAsset", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_assets_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_updated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("VerifiedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_verified_by"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetAccessEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_asset_access_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_asset_access_events_users_user_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_asset_access_events_content_assets_tenant_id_asset_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetSecurityScanEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_asset_security_scan_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_asset_security_scan_events_content_assets_tenant_id~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentEntry", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_entries_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_entries_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_entries_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_entries_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportIssue", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_content_import_issues_content_import_items_tenant_id_item_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportJob", null) - .WithMany() - .HasForeignKey("TenantId", "JobId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_issues_content_import_jobs_tenant_id_job_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportJob", null) - .WithMany() - .HasForeignKey("TenantId", "JobId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_items_content_import_jobs_tenant_id_job_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportJob", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_import_jobs_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_jobs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "TargetCategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_categories_tenant_id_target_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "TargetContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_content_nodes_tenant_id_target_content_~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "TargetQuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_question_banks_tenant_id_target_questio~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "TargetRegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_regions_tenant_id_target_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "TargetSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_subjects_tenant_id_target_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentNode", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_nodes_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_nodes_users_updated_by"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_nodes_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_content_nodes_content_nodes_tenant_id_parent_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_nodes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookChapter", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_chapters_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_chapters_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_chapters_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.HandbookSubject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_handbook_chapters_handbook_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookEntry", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_entries_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.HandbookChapter", null) - .WithMany() - .HasForeignKey("TenantId", "ChapterId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_handbook_entries_handbook_chapters_tenant_id_chapter_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_entries_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_entries_content_entries_tenant_id_entry_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookSubject", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_subjects_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ImageAsset", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_images_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.PracticeBlueprint", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_blueprints_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_blueprints_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_blueprints_users_updated_by"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_question_collections_tenant_id_collecti~"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_content_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollection", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_collections_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collections_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_collections_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_content_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_question_banks_tenant_id_question_bank~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollectionItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_question_collection_items_questions_question_owner_tenant_i~"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_question_collections_tenant_id_co~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_tenant_question_references_tenant~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionTypeGroup", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_type_groups_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_type_groups_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionVideo", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_videos_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_videos_questions_tenant_id_question_id"); - - b.HasOne("Tiku.Domain.Content.VideoExplanation", null) - .WithMany() - .HasForeignKey("TenantId", "VideoId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_videos_video_explanations_tenant_id_video_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.SubjectShare", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SourceSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_subjects_tenant_id_source_subject_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "TargetSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_subjects_tenant_id_target_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionBankPreference", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_bank_preferences_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_question_bank_preferences_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_bank_preferences_users_updated_by"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("QuestionBankOwnerTenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_question_bank_preferences_question_banks_question_ba~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionReference", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_references_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_question_references_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_question_references_questions_question_owner_tenant_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordFavorite", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_users_user_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyWord", null) - .WithMany() - .HasForeignKey("TenantId", "WordId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_vocabulary_words_tenant_id_word_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordProgress", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_users_user_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyWord", null) - .WithMany() - .HasForeignKey("TenantId", "WordId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_vocabulary_words_tenant_id_word_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoExplanation", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_explanations_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_video_explanations_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoPlaybackProgress", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_playback_progress_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_playback_progress_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_video_playback_progress_questions_tenant_id_question_id"); - - b.HasOne("Tiku.Domain.Content.VideoExplanation", null) - .WithMany() - .HasForeignKey("TenantId", "VideoId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_playback_progress_video_explanations_tenant_id_video_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyUnit", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_vocabulary_units_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyWord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_vocabulary_words_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyUnit", null) - .WithMany() - .HasForeignKey("TenantId", "UnitId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_vocabulary_units_tenant_id_unit_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlement", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("GeneratedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_generated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("PaidBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_paid_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlements_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlements_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementExportEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ExportedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_export_events_users_exported_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_export_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_export_events_commission_settlements_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementItem", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_items_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_items_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_items_commission_settlements_tenant_i~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementProof", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("SubmittedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_users_submitted_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_proofs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_content_assets_tenant_id_asset~"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_proofs_commission_settlements_tenant_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmConfig", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_config_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookLog", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_webhook_log_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookQueueItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_webhook_queue_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralCode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_codes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_codes_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralLead", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_assigned_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedToUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_assigned_to_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_leads_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_leads_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.ReferralTrack", null) - .WithMany() - .HasForeignKey("TenantId", "FirstTrackId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_referral_tracks_tenant_id_first_track_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralQrcode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_qrcodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_qrcodes_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTeamEdge", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("LeaderUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_team_edges_users_leader_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("MemberUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_team_edges_users_member_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_team_edges_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTrack", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("TargetUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_users_target_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_tracks_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.ReferralLead", null) - .WithMany() - .HasForeignKey("TenantId", "LeadId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_referral_leads_tenant_id_lead_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.TenantCommissionSetting", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_commission_settings_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_commission_settings_users_updated_by"); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.StudentProfile", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_student_profiles_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_student_profiles_users_user_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "SelectedMajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_majors_tenant_id_selected_major_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SelectedSchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_schools_tenant_id_selected_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.UserIdentity", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_identities_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportIssue", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_import_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Import.PocketBaseImportRun", null) - .WithMany() - .HasForeignKey("TenantId", "RunId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_pb_import_issues_pb_import_runs_tenant_id_run_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportRun", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_import_runs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseRawRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_raw_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Import.PocketBaseImportRun", null) - .WithMany() - .HasForeignKey("TenantId", "RunId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_raw_records_pb_import_runs_tenant_id_run_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.AnswerRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_answer_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_answer_records_users_user_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSessionQuestion", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId", "SessionQuestionId") - .HasPrincipalKey("TenantId", "PracticeSessionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_answer_records_practice_session_questions_tenant_id_practic~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "UserId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "UserId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_answer_records_practice_sessions_tenant_id_user_id_practice~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.DashboardDailyStat", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_dashboard_daily_stats_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_dashboard_daily_stats_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ExamDate", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_exam_dates_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_exam_dates_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_exam_dates_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.FavoriteQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_favorite_questions_questions_question_owner_tenant_id_quest~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_tenant_question_references_tenant_id_que~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeAccessEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_access_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_access_events_users_user_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_access_events_practice_sessions_tenant_id_practice~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeDailyUsage", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_daily_usage_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_daily_usage_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSession", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_sessions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_sessions_users_user_id"); - - b.HasOne("Tiku.Domain.Content.PracticeBlueprint", null) - .WithMany() - .HasForeignKey("TenantId", "BlueprintId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_practice_blueprints_tenant_id_blueprint_id"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_question_collections_tenant_id_collection~"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_content_entries_tenant_id_entry_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_practice_sessions_tenant_id_prac~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionVersion", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId", "QuestionVersionId") - .HasPrincipalKey("TenantId", "QuestionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_question_versions_question_owner~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_tenant_question_references_tenan~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReport", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_users_user_id"); - - b.HasOne("Tiku.Domain.Content.PracticeBlueprint", null) - .WithMany() - .HasForeignKey("TenantId", "BlueprintId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_session_reports_practice_blueprints_tenant_id_blue~"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_session_reports_question_collections_tenant_id_col~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_practice_sessions_tenant_id_practi~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReportSection", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_practice_sessions_tenant_i~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSessionReport", null) - .WithMany() - .HasForeignKey("TenantId", "ReportId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_practice_session_reports_t~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RecentPractice", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_recent_practices_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_recent_practices_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.Report", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("HandledBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_reports_users_handled_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_reports_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_reports_questions_tenant_id_question_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ReportStatusEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_report_status_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_report_status_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.Report", null) - .WithMany() - .HasForeignKey("TenantId", "ReportId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_report_status_events_reports_tenant_id_report_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RevenueDailyStat", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_revenue_daily_stats_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_revenue_daily_stats_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.UserScoreEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_score_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_score_events_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.WrongQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_wrong_questions_questions_question_owner_tenant_id_question~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_tenant_question_references_tenant_id_questi~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Announcement", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_announcements_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.AuditLog", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_audit_logs_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_audit_logs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_backend_menus_backend_permissions_permission_code"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_background_jobs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "OutputAssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_background_jobs_content_assets_tenant_id_output_asset_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Badge", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Banner", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_banners_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_banners_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Faq", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_faqs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_faqs_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendMenu", null) - .WithMany() - .HasForeignKey("MenuCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_menus_backend_menus_menu_code"); - - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_menus_platform_backend_roles_role_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_permissions_backend_permissions_permi~"); - - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_permissions_platform_backend_roles_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b => - { - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_user_roles_platform_backend_roles_role_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_user_roles_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_roles_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendMenu", null) - .WithMany() - .HasForeignKey("MenuCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_menus_backend_menus_menu_code"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_menus_tenant_backend_roles_tenant_id_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_permissions_backend_permissions_permiss~"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_permissions_tenant_backend_roles_tenant~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_user_roles_users_user_id"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_user_roles_tenant_backend_roles_tenant_id_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_content_notifications_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReadBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_content_notifications_users_read_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_content_notifications_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "SourceQuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_tenant_content_notifications_question_banks_tenant_id_sourc~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeConfig", b => - { - b.HasOne("Tiku.Domain.Operations.TenantThemeTemplate", null) - .WithMany() - .HasForeignKey("ActiveTemplateCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_tenant_theme_templates_active_template~"); - - b.HasOne("Tiku.Domain.Operations.TenantThemeTemplate", null) - .WithMany() - .HasForeignKey("DraftTemplateCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_tenant_theme_templates_draft_template_~"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("DraftUpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_users_draft_updated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("PublishedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_users_published_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_theme_configs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserBadge", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("GrantedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_user_badges_users_granted_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_badges_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_user_badges_users_user_id"); - - b.HasOne("Tiku.Domain.Operations.Badge", null) - .WithMany() - .HasForeignKey("TenantId", "BadgeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_user_badges_badges_tenant_id_badge_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserNotification", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_user_notifications_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_notifications_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_notifications_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AcknowledgedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_platform_audit_alerts_users_acknowledged_by"); - - b.HasOne("Tiku.Domain.Operations.AuditLog", null) - .WithMany() - .HasForeignKey("AuditLogId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_audit_alerts_audit_logs_audit_log_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ResolvedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_platform_audit_alerts_users_resolved_by"); - - b.HasOne("Tiku.Domain.Platform.PlatformAuditAlertRule", null) - .WithMany() - .HasForeignKey("RuleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_audit_alerts_platform_audit_alert_rules_rule_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_platform_audit_alerts_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlertRule", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_platform_audit_alert_rules_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => - { - b.HasOne("Tiku.Domain.Platform.PlatformDunningNotificationChannel", null) - .WithMany() - .HasForeignKey("ChannelId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_platform_dunning_notif~"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoices_tenant~"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoiceReminder", null) - .WithMany() - .HasForeignKey("TenantId", "ReminderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoice_reminde~"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Platform.TenantBillingProfile", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_billing_profiles_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoices_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoices_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReceivedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_payments_users_received_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_reminders_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenant_invoices_tenant_id_invoice_~"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "PrimaryCollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_collections_tenant_id_primary_collection~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_banks_tenant_id_question_bank_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_subjects_tenant_id_subject_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionVersion", null) - .WithMany() - .HasForeignKey("TenantId", "Id", "CurrentVersionId") - .HasPrincipalKey("TenantId", "QuestionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_versions_tenant_id_id_current_version_id"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionBank", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_banks_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_banks_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionVersion", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_versions_users_created_by"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_versions_questions_tenant_id_question_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_auth_challenges_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_auth_login_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_auth_login_events_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthSession", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_auth_sessions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsSendRateLimit", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sms_send_rate_limits_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsVerificationCode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sms_verification_codes_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.Tenant", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("OwnerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenants_users_owner_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantBranding", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Tenancy.TenantBranding", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_branding_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClass", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_classes_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_classes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_classes_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_tenant_classes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClassMember", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_users_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.TenantClass", null) - .WithMany() - .HasForeignKey("TenantId", "ClassId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_tenant_classes_tenant_id_class_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantDomain", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_domains_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantExternalProvider", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_external_providers_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantFrontendConfig", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_frontend_configs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantMembership", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_memberships_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_memberships_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_secrets_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSettings", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Tenancy.TenantSettings", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_settings_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentFollowup", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedToUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_users_assigned_to_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CompletedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_users_completed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_followups_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_followups_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Tenancy.TenantClass", null) - .WithMany() - .HasForeignKey("TenantId", "ClassId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_tenant_classes_tenant_id_class_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentNote", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_notes_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_notes_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_notes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_notes_users_updated_by"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260728070357_AddVideoPlaybackProgress.cs b/Tiku.Infrastructure/Persistence/Migrations/20260728070357_AddVideoPlaybackProgress.cs deleted file mode 100644 index 023afa3..0000000 --- a/Tiku.Infrastructure/Persistence/Migrations/20260728070357_AddVideoPlaybackProgress.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Text.Json; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Tiku.Infrastructure.Persistence.Migrations -{ - /// - public partial class AddVideoPlaybackProgress : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "video_playback_progress", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - user_id = table.Column(type: "uuid", nullable: false), - video_id = table.Column(type: "uuid", nullable: false), - question_id = table.Column(type: "uuid", nullable: true), - position_seconds = table.Column(type: "integer", nullable: false), - duration_seconds = table.Column(type: "integer", nullable: true), - watched_seconds = table.Column(type: "integer", nullable: false), - is_completed = table.Column(type: "boolean", nullable: false), - completed_at = table.Column(type: "timestamp with time zone", nullable: true), - last_played_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), - play_count = table.Column(type: "integer", nullable: false), - metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), - tenant_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), - updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("pk_video_playback_progress", x => x.id); - table.UniqueConstraint("ak_video_playback_progress_tenant_id_id", x => new { x.tenant_id, x.id }); - table.CheckConstraint("ck_video_playback_progress_duration", "duration_seconds is null or duration_seconds >= 0"); - table.CheckConstraint("ck_video_playback_progress_play_count", "play_count >= 0"); - table.CheckConstraint("ck_video_playback_progress_position", "position_seconds >= 0"); - table.CheckConstraint("ck_video_playback_progress_watched", "watched_seconds >= 0"); - table.ForeignKey( - name: "fk_video_playback_progress_questions_tenant_id_question_id", - columns: x => new { x.tenant_id, x.question_id }, - principalTable: "questions", - principalColumns: new[] { "tenant_id", "id" }, - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_video_playback_progress_tenants_tenant_id", - column: x => x.tenant_id, - principalTable: "tenants", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_video_playback_progress_users_user_id", - column: x => x.user_id, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_video_playback_progress_video_explanations_tenant_id_video_~", - columns: x => new { x.tenant_id, x.video_id }, - principalTable: "video_explanations", - principalColumns: new[] { "tenant_id", "id" }, - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_video_playback_progress_tenant_id_question_id", - table: "video_playback_progress", - columns: new[] { "tenant_id", "question_id" }); - - migrationBuilder.CreateIndex( - name: "ix_video_playback_progress_tenant_id_user_id_last_played_at", - table: "video_playback_progress", - columns: new[] { "tenant_id", "user_id", "last_played_at" }); - - migrationBuilder.CreateIndex( - name: "ix_video_playback_progress_tenant_id_user_id_video_id", - table: "video_playback_progress", - columns: new[] { "tenant_id", "user_id", "video_id" }, - unique: true, - filter: "question_id is null"); - - migrationBuilder.CreateIndex( - name: "ix_video_playback_progress_tenant_id_user_id_video_id_question~", - table: "video_playback_progress", - columns: new[] { "tenant_id", "user_id", "video_id", "question_id" }, - unique: true, - filter: "question_id is not null"); - - migrationBuilder.CreateIndex( - name: "ix_video_playback_progress_tenant_id_video_id", - table: "video_playback_progress", - columns: new[] { "tenant_id", "video_id" }); - - migrationBuilder.CreateIndex( - name: "ix_video_playback_progress_user_id", - table: "video_playback_progress", - column: "user_id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "video_playback_progress"); - } - } -} diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260728093337_RemoveMfaAuthentication.Designer.cs b/Tiku.Infrastructure/Persistence/Migrations/20260728093337_RemoveMfaAuthentication.Designer.cs deleted file mode 100644 index c6a4ba7..0000000 --- a/Tiku.Infrastructure/Persistence/Migrations/20260728093337_RemoveMfaAuthentication.Designer.cs +++ /dev/null @@ -1,18099 +0,0 @@ -// -using System; -using System.Text.Json; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Tiku.Infrastructure.Persistence; - -#nullable disable - -namespace Tiku.Infrastructure.Persistence.Migrations -{ - [DbContext(typeof(TikuDbContext))] - [Migration("20260728093337_RemoveMfaAuthentication")] - partial class RemoveMfaAuthentication - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.10") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "citext"); - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "ltree"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FriendlyName") - .HasColumnType("text") - .HasColumnName("friendly_name"); - - b.Property("Xml") - .HasColumnType("text") - .HasColumnName("xml"); - - b.HasKey("Id") - .HasName("pk_data_protection_keys"); - - b.ToTable("data_protection_keys", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text") - .HasColumnName("claim_type"); - - b.Property("ClaimValue") - .HasColumnType("text") - .HasColumnName("claim_value"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_claims"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_claims_user_id"); - - b.ToTable("user_claims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text") - .HasColumnName("login_provider"); - - b.Property("ProviderKey") - .HasColumnType("text") - .HasColumnName("provider_key"); - - b.Property("ProviderDisplayName") - .HasColumnType("text") - .HasColumnName("provider_display_name"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("LoginProvider", "ProviderKey") - .HasName("pk_user_logins"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_logins_user_id"); - - b.ToTable("user_logins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("LoginProvider") - .HasColumnType("text") - .HasColumnName("login_provider"); - - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); - - b.Property("Value") - .HasColumnType("text") - .HasColumnName("value"); - - b.HasKey("UserId", "LoginProvider", "Name") - .HasName("pk_user_tokens"); - - b.ToTable("user_tokens", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CategoryType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("category_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("SvipQuestionLimit") - .HasColumnType("integer") - .HasColumnName("svip_question_limit"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_categories"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_categories_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_categories_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_categories_tenant_id_node_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_categories_tenant_id_subject_id"); - - b.ToTable("categories", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Major", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StudyTips") - .HasColumnType("text") - .HasColumnName("study_tips"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_majors"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_majors_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_majors_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_majors_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_majors_tenant_id_school_id"); - - b.ToTable("majors", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ModuleNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyModuleId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_module_id"); - - b.Property("LegacyParentId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_parent_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("Path") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("path"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_module_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_module_nodes_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_module_nodes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_module_nodes_tenant_id_module_id"); - - b.HasIndex("TenantId", "ParentId") - .HasDatabaseName("ix_module_nodes_tenant_id_parent_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_module_nodes_tenant_id_region_id"); - - b.ToTable("module_nodes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.QuestionTaxonomyAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsPrimary") - .HasColumnType("boolean") - .HasColumnName("is_primary"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("TaxonomyNodeId") - .HasColumnType("uuid") - .HasColumnName("taxonomy_node_id"); - - b.Property("TaxonomyOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("taxonomy_owner_tenant_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_question_taxonomy_assignments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_taxonomy_assignments_tenant_id_id"); - - b.HasIndex("TaxonomyOwnerTenantId", "TaxonomyNodeId") - .HasDatabaseName("ix_question_taxonomy_assignments_taxonomy_owner_tenant_id_taxo~"); - - b.HasIndex("TenantId", "QuestionId", "TaxonomyOwnerTenantId", "TaxonomyNodeId") - .IsUnique() - .HasDatabaseName("ix_question_taxonomy_assignments_tenant_id_question_id_taxonom~"); - - b.ToTable("question_taxonomy_assignments", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Region", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("code"); - - b.Property("Config") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FullName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("full_name"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsHot") - .HasColumnType("boolean") - .HasColumnName("is_hot"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Pinyin") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("pinyin"); - - b.Property("ShortName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("short_name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_regions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_regions_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_regions_tenant_id_legacy_id"); - - b.ToTable("regions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.RegionModule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsPrimarySchoolModule") - .HasColumnType("boolean") - .HasColumnName("is_primary_school_module"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Route") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("route"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TextColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("text_color"); - - b.Property("Type") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_region_modules"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_region_modules_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_region_modules_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_region_modules_tenant_id_region_id"); - - b.ToTable("region_modules", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.School", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("ProfessionalExamDate") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("professional_exam_date"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_schools"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_schools_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_schools_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_schools_tenant_id_module_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_schools_tenant_id_region_id"); - - b.ToTable("schools", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineField", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("FieldKey") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("field_key"); - - b.Property("FieldName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("field_name"); - - b.Property("FieldType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("field_type"); - - b.Property("IsFilter") - .HasColumnType("boolean") - .HasColumnName("is_filter"); - - b.Property("IsRequired") - .HasColumnType("boolean") - .HasColumnName("is_required"); - - b.Property("IsTrend") - .HasColumnType("boolean") - .HasColumnName("is_trend"); - - b.Property("IsVisible") - .HasColumnType("boolean") - .HasColumnName("is_visible"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Options") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Placeholder") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("placeholder"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Unit") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("unit"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_scoreline_fields"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_scoreline_fields_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_scoreline_fields_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "FieldKey") - .IsUnique() - .HasDatabaseName("ix_scoreline_fields_tenant_id_region_id_field_key"); - - b.HasIndex("TenantId", "RegionId", "IsFilter", "SortOrder") - .HasDatabaseName("ix_scoreline_fields_tenant_id_region_id_is_filter_sort_order"); - - b.ToTable("scoreline_fields", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FieldValues") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("field_values") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("major_name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SchoolName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("school_name"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Year") - .HasColumnType("integer") - .HasColumnName("year"); - - b.HasKey("Id") - .HasName("pk_scoreline_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_scoreline_records_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_scoreline_records_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_scoreline_records_tenant_id_major_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_scoreline_records_tenant_id_school_id"); - - b.HasIndex("TenantId", "Year") - .HasDatabaseName("ix_scoreline_records_tenant_id_year"); - - b.HasIndex("TenantId", "RegionId", "SchoolId", "MajorId", "Year") - .HasDatabaseName("ix_scoreline_records_tenant_id_region_id_school_id_major_id_ye~"); - - b.ToTable("scoreline_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Subject", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorLegacyIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("major_legacy_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_subjects"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_subjects_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_subjects_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_subjects_tenant_id_major_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_subjects_tenant_id_module_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_subjects_tenant_id_node_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_subjects_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_subjects_tenant_id_school_id"); - - b.ToTable("subjects", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.TaxonomyNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Depth") - .HasColumnType("integer") - .HasColumnName("depth"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("node_type"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("ParentOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("parent_owner_tenant_id"); - - b.Property("Path") - .HasColumnType("ltree") - .HasColumnName("path"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_taxonomy_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_taxonomy_nodes_tenant_id_id"); - - b.HasIndex("ParentOwnerTenantId", "ParentId") - .HasDatabaseName("ix_taxonomy_nodes_parent_owner_tenant_id_parent_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_taxonomy_nodes_tenant_id_code"); - - b.ToTable("taxonomy_nodes", null, t => - { - t.HasCheckConstraint("ck_taxonomy_nodes_parent_pair", "(parent_owner_tenant_id is null) = (parent_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.ActivationCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AgentUserId") - .HasColumnType("uuid") - .HasColumnName("agent_user_id"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CouponCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("coupon_code"); - - b.Property("CouponRedemptionId") - .HasColumnType("uuid") - .HasColumnName("coupon_redemption_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("IsUsed") - .HasColumnType("boolean") - .HasColumnName("is_used"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("SoldTo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("sold_to"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitPriceCents") - .HasColumnType("integer") - .HasColumnName("unit_price_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("used_at"); - - b.Property("UsedBy") - .HasColumnType("uuid") - .HasColumnName("used_by"); - - b.Property("UsedRegionId") - .HasColumnType("uuid") - .HasColumnName("used_region_id"); - - b.HasKey("Id") - .HasName("pk_activation_codes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_activation_codes_tenant_id_id"); - - b.HasIndex("AgentUserId") - .HasDatabaseName("ix_activation_codes_agent_user_id"); - - b.HasIndex("UsedBy") - .HasDatabaseName("ix_activation_codes_used_by"); - - b.HasIndex("TenantId", "BatchId") - .HasDatabaseName("ix_activation_codes_tenant_id_batch_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_activation_codes_tenant_id_code"); - - b.HasIndex("TenantId", "CouponRedemptionId") - .HasDatabaseName("ix_activation_codes_tenant_id_coupon_redemption_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_activation_codes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UsedRegionId") - .HasDatabaseName("ix_activation_codes_tenant_id_used_region_id"); - - b.ToTable("activation_codes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CodeBatch", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CampaignName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("campaign_name"); - - b.Property("Channel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel"); - - b.Property("CommissionRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("commission_rate"); - - b.Property("CostPriceCents") - .HasColumnType("integer") - .HasColumnName("cost_price_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("DefaultUnitPriceCents") - .HasColumnType("integer") - .HasColumnName("default_unit_price_cents"); - - b.Property("IssuedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("issued_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_code_batches"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_code_batches_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_code_batches_tenant_id_legacy_id"); - - b.ToTable("code_batches", null, t => - { - t.HasCheckConstraint("ck_code_batches_amounts", "default_unit_price_cents >= 0 and cost_price_cents >= 0"); - - t.HasCheckConstraint("ck_code_batches_counts", "total_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("ClosedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("closed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("Direction") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("direction"); - - b.Property("IssueId") - .HasColumnType("uuid") - .HasColumnName("issue_id"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProofAssetKey") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("proof_asset_key"); - - b.Property("Reason") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("reason"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VoucherNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("voucher_no"); - - b.HasKey("Id") - .HasName("pk_commerce_adjustment_vouchers"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_adjustment_vouchers_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_adjustment_vouchers_created_by"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commerce_adjustment_vouchers_reviewed_by"); - - b.HasIndex("TenantId", "BatchId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_batch_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_item_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_order_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "VoucherNo") - .IsUnique() - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_voucher_no"); - - b.HasIndex("TenantId", "IssueId", "CreatedAt") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_issue_id_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_status_created_at"); - - b.ToTable("commerce_adjustment_vouchers", null, t => - { - t.HasCheckConstraint("ck_commerce_adjustment_vouchers_amount", "amount_cents > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucherEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("Note") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.Property("VoucherId") - .HasColumnType("uuid") - .HasColumnName("voucher_id"); - - b.HasKey("Id") - .HasName("pk_commerce_adjustment_voucher_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_adjustment_voucher_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_adjustment_voucher_events_actor_user_id"); - - b.HasIndex("TenantId", "VoucherId", "CreatedAt") - .HasDatabaseName("ix_commerce_adjustment_voucher_events_tenant_id_voucher_id_cre~"); - - b.ToTable("commerce_adjustment_voucher_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationBatch", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BillDate") - .HasColumnType("date") - .HasColumnName("bill_date"); - - b.Property("BillType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("bill_type"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DuplicateCount") - .HasColumnType("integer") - .HasColumnName("duplicate_count"); - - b.Property("Error") - .HasColumnType("text") - .HasColumnName("error"); - - b.Property("FeeCents") - .HasColumnType("integer") - .HasColumnName("fee_cents"); - - b.Property("IgnoredCount") - .HasColumnType("integer") - .HasColumnName("ignored_count"); - - b.Property("MatchedCount") - .HasColumnType("integer") - .HasColumnName("matched_count"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MismatchCount") - .HasColumnType("integer") - .HasColumnName("mismatch_count"); - - b.Property("MissingLocalCount") - .HasColumnType("integer") - .HasColumnName("missing_local_count"); - - b.Property("MissingProviderCount") - .HasColumnType("integer") - .HasColumnName("missing_provider_count"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source"); - - b.Property("SourceHash") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("source_hash"); - - b.Property("SourceName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("source_name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_batches"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_batches_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_reconciliation_batches_created_by"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_batches_tenant_id_status_created_at"); - - b.HasIndex("TenantId", "Provider", "BillDate", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_batches_tenant_id_provider_bill_dat~"); - - b.ToTable("commerce_reconciliation_batches", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_batches_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commerce_reconciliation_batches_counts", "total_count >= 0 and matched_count >= 0 and mismatch_count >= 0 and missing_local_count >= 0 and missing_provider_count >= 0 and duplicate_count >= 0 and ignored_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("AssignedTo") - .HasColumnType("uuid") - .HasColumnName("assigned_to"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DueAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("due_at"); - - b.Property("IssueCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("IssueNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_no"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("MatchStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("match_status"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("OrderNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("RefundNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("ResolutionNote") - .HasColumnType("text") - .HasColumnName("resolution_note"); - - b.Property("ResolutionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("resolution_type"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("ResolvedBy") - .HasColumnType("uuid") - .HasColumnName("resolved_by"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("summary"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TransactionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("transaction_type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_issues_tenant_id_id"); - - b.HasIndex("AssignedTo") - .HasDatabaseName("ix_commerce_reconciliation_issues_assigned_to"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_reconciliation_issues_created_by"); - - b.HasIndex("ResolvedBy") - .HasDatabaseName("ix_commerce_reconciliation_issues_resolved_by"); - - b.HasIndex("TenantId", "IssueNo") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_issue_no"); - - b.HasIndex("TenantId", "ItemId") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_item_id") - .HasFilter("item_id is not null and status in ('open', 'investigating', 'escalated')"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_order_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "OrderNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_order_no_created_at"); - - b.HasIndex("TenantId", "PaymentId", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_payment_id_created~"); - - b.HasIndex("TenantId", "AssignedTo", "Status", "DueAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_assigned_to_status~"); - - b.HasIndex("TenantId", "BatchId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_batch_id_status_cr~"); - - b.HasIndex("TenantId", "Status", "Severity", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_status_severity_cr~"); - - b.ToTable("commerce_reconciliation_issues", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_issues_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssueEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("IssueId") - .HasColumnType("uuid") - .HasColumnName("issue_id"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_issue_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_issue_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_reconciliation_issue_events_actor_user_id"); - - b.HasIndex("TenantId", "IssueId", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issue_events_tenant_id_issue_id_cre~"); - - b.ToTable("commerce_reconciliation_issue_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FeeCents") - .HasColumnType("integer") - .HasColumnName("fee_cents"); - - b.Property("IssueCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("LocalStatus") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("local_status"); - - b.Property("MatchStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("match_status"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("OrderNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("ProviderStatus") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider_status"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("RefundNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("RefundedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("refunded_at"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TransactionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("transaction_type"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_items_tenant_id_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_order_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "BatchId", "RowNo") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_batch_id_row_no"); - - b.HasIndex("TenantId", "OrderNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_order_no_created_at"); - - b.HasIndex("TenantId", "BatchId", "MatchStatus", "RowNo") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_batch_id_match_stat~"); - - b.HasIndex("TenantId", "Provider", "ProviderTradeNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_provider_provider_t~"); - - b.HasIndex("TenantId", "RefundNo", "ProviderRefundNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_refund_no_provider_~"); - - b.ToTable("commerce_reconciliation_items", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_items_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commerce_reconciliation_items_row", "row_no > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_commerce_refund_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_refund_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_refund_events_actor_user_id"); - - b.HasIndex("TenantId", "RefundRequestId", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_events_tenant_id_refund_request_id_created_~"); - - b.ToTable("commerce_refund_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundRequest", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CancelledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("cancelled_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementAction") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("entitlement_action"); - - b.Property("FailedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("failed_at"); - - b.Property("FailureReason") - .HasColumnType("text") - .HasColumnName("failure_reason"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProcessedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("processed_at"); - - b.Property("ProcessedBy") - .HasColumnType("uuid") - .HasColumnName("processed_by"); - - b.Property("Provider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("Reason") - .HasColumnType("text") - .HasColumnName("reason"); - - b.Property("RefundNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RequestedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("requested_at") - .HasDefaultValueSql("now()"); - - b.Property("RequestedBy") - .HasColumnType("uuid") - .HasColumnName("requested_by"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SucceededAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("succeeded_at"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_refund_requests"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_refund_requests_tenant_id_id"); - - b.HasIndex("ProcessedBy") - .HasDatabaseName("ix_commerce_refund_requests_processed_by"); - - b.HasIndex("RequestedBy") - .HasDatabaseName("ix_commerce_refund_requests_requested_by"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commerce_refund_requests_reviewed_by"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundNo") - .IsUnique() - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_refund_no"); - - b.HasIndex("TenantId", "OrderId", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_order_id_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_status_created_at"); - - b.ToTable("commerce_refund_requests", null, t => - { - t.HasCheckConstraint("ck_commerce_refund_requests_amount", "amount_cents > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Coupon", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DiscountType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("discount_type"); - - b.Property("DiscountValue") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("discount_value"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MaxUses") - .HasColumnType("integer") - .HasColumnName("max_uses"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("Source") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property("ValidFrom") - .HasColumnType("timestamp with time zone") - .HasColumnName("valid_from"); - - b.Property("ValidTo") - .HasColumnType("timestamp with time zone") - .HasColumnName("valid_to"); - - b.HasKey("Id") - .HasName("pk_coupons"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_coupons_tenant_id_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_coupons_tenant_id_code"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_coupons_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_coupons_tenant_id_plan_id"); - - b.ToTable("coupons", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CouponRedemption", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClaimedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("claimed_at"); - - b.Property("CouponCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("coupon_code"); - - b.Property("CouponId") - .HasColumnType("uuid") - .HasColumnName("coupon_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DiscountAppliedCents") - .HasColumnType("integer") - .HasColumnName("discount_applied_cents"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("Source") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("used_at"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_coupon_redemptions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_coupon_redemptions_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_coupon_redemptions_user_id"); - - b.HasIndex("TenantId", "CouponId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_coupon_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_coupon_redemptions_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_plan_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_region_id"); - - b.HasIndex("TenantId", "OrderId", "UserId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_order_id_user_id") - .HasFilter("order_id is not null"); - - b.HasIndex("TenantId", "UserId", "CouponId") - .IsUnique() - .HasDatabaseName("ix_coupon_redemptions_tenant_id_user_id_coupon_id") - .HasFilter("coupon_id is not null"); - - b.ToTable("coupon_redemptions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Entitlement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementType") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("entitlement_type"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("LegacySourceId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_source_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RevokedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("revoked_at"); - - b.Property("RevokedBy") - .HasColumnType("uuid") - .HasColumnName("revoked_by"); - - b.Property("RevokedReason") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("revoked_reason"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("StartsAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at") - .HasDefaultValueSql("now()"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_entitlements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_entitlements_tenant_id_id"); - - b.HasIndex("RevokedBy") - .HasDatabaseName("ix_entitlements_revoked_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_entitlements_user_id"); - - b.HasIndex("TenantId", "UserId", "Status", "ExpiresAt") - .HasDatabaseName("ix_entitlements_tenant_id_user_id_status_expires_at"); - - b.ToTable("entitlements", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyPlanId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_plan_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("LegacyUserId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_user_id"); - - b.Property("OrderNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PayMethod") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("pay_method"); - - b.Property("PayProvider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("pay_provider"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("ProductName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("product_name"); - - b.Property("ProductType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("product_type"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefundedAmountCents") - .HasColumnType("integer") - .HasColumnName("refunded_amount_cents"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("trade_no"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_orders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_orders_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_orders_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_orders_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderNo") - .IsUnique() - .HasDatabaseName("ix_orders_tenant_id_order_no"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_orders_tenant_id_plan_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_orders_tenant_id_region_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_orders_tenant_id_user_id_created_at"); - - b.ToTable("orders", null, t => - { - t.HasCheckConstraint("ck_orders_amount", "amount_cents >= 0"); - - t.HasCheckConstraint("ck_orders_refunded_amount", "refunded_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("item_type"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("Quantity") - .HasColumnType("integer") - .HasColumnName("quantity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalAmountCents") - .HasColumnType("integer") - .HasColumnName("total_amount_cents"); - - b.Property("UnitAmountCents") - .HasColumnType("integer") - .HasColumnName("unit_amount_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_order_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_order_items_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_order_items_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_order_items_tenant_id_order_id"); - - b.ToTable("order_items", null, t => - { - t.HasCheckConstraint("ck_order_items_amounts", "unit_amount_cents >= 0 and total_amount_cents >= 0"); - - t.HasCheckConstraint("ck_order_items_quantity", "quantity > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Payment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyOrderId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_order_id"); - - b.Property("Method") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("method"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefundedAmountCents") - .HasColumnType("integer") - .HasColumnName("refunded_amount_cents"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_payments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_payments_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_payments_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderId", "UpdatedAt") - .HasDatabaseName("ix_payments_tenant_id_order_id_updated_at"); - - b.HasIndex("TenantId", "Provider", "ProviderTradeNo") - .IsUnique() - .HasDatabaseName("ix_payments_tenant_id_provider_provider_trade_no") - .HasFilter("provider_trade_no is not null"); - - b.ToTable("payments", null, t => - { - t.HasCheckConstraint("ck_payments_amount", "amount_cents >= 0"); - - t.HasCheckConstraint("ck_payments_refunded_amount", "refunded_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PaymentEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Error") - .HasColumnType("text") - .HasColumnName("error"); - - b.Property("EventId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("event_id"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProcessedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("processed_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("SignatureValid") - .HasColumnType("boolean") - .HasColumnName("signature_valid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_payment_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_payment_events_tenant_id_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_payment_events_tenant_id_payment_id"); - - b.HasIndex("TenantId", "Provider", "EventId") - .IsUnique() - .HasDatabaseName("ix_payment_events_tenant_id_provider_event_id"); - - b.ToTable("payment_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClaimedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("claimed_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TaskId") - .HasColumnType("uuid") - .HasColumnName("task_id"); - - b.Property("TaskKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("task_key"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_point_activity_claims"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_activity_claims_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_point_activity_claims_user_id"); - - b.HasIndex("TenantId", "TaskId") - .HasDatabaseName("ix_point_activity_claims_tenant_id_task_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_point_activity_claims_tenant_id_user_id_created_at"); - - b.HasIndex("TenantId", "UserId", "TaskId", "SourceType", "SourceId") - .IsUnique() - .HasDatabaseName("ix_point_activity_claims_tenant_id_user_id_task_id_source_type~") - .HasFilter("source_type is not null and source_id is not null"); - - b.ToTable("point_activity_claims", null, t => - { - t.HasCheckConstraint("ck_point_activity_claims_points", "points > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityTask", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("EndsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ends_at"); - - b.Property("MaxClaimsPerUser") - .HasColumnType("integer") - .HasColumnName("max_claims_per_user"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("Rules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TaskKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("task_key"); - - b.Property("TaskType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("task_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_point_activity_tasks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_activity_tasks_tenant_id_id"); - - b.HasIndex("TenantId", "TaskKey") - .IsUnique() - .HasDatabaseName("ix_point_activity_tasks_tenant_id_task_key"); - - b.HasIndex("TenantId", "Status", "SortOrder") - .HasDatabaseName("ix_point_activity_tasks_tenant_id_status_sort_order"); - - b.ToTable("point_activity_tasks", null, t => - { - t.HasCheckConstraint("ck_point_activity_tasks_max_claims", "max_claims_per_user > 0"); - - t.HasCheckConstraint("ck_point_activity_tasks_points", "points > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("EndsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ends_at"); - - b.Property("FulfillmentPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("fulfillment_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ItemKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("item_key"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("PointsCost") - .HasColumnType("integer") - .HasColumnName("points_cost"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Stock") - .HasColumnType("integer") - .HasColumnName("stock"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_point_exchange_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_exchange_items_tenant_id_id"); - - b.HasIndex("TenantId", "ItemKey") - .IsUnique() - .HasDatabaseName("ix_point_exchange_items_tenant_id_item_key"); - - b.HasIndex("TenantId", "RegionId", "Status", "SortOrder") - .HasDatabaseName("ix_point_exchange_items_tenant_id_region_id_status_sort_order"); - - b.ToTable("point_exchange_items", null, t => - { - t.HasCheckConstraint("ck_point_exchange_items_days", "days is null or days >= 0"); - - t.HasCheckConstraint("ck_point_exchange_items_points_cost", "points_cost > 0"); - - t.HasCheckConstraint("ck_point_exchange_items_stock", "stock is null or stock >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeOrder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CancelledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("cancelled_at"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FulfillmentSnapshot") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("fulfillment_snapshot") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("ItemName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("item_name"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("OrderedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("ordered_at") - .HasDefaultValueSql("now()"); - - b.Property("PointsCost") - .HasColumnType("integer") - .HasColumnName("points_cost"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_point_exchange_orders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_exchange_orders_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_point_exchange_orders_user_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_point_exchange_orders_tenant_id_item_id"); - - b.HasIndex("TenantId", "OrderNo") - .IsUnique() - .HasDatabaseName("ix_point_exchange_orders_tenant_id_order_no"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_point_exchange_orders_tenant_id_user_id_created_at"); - - b.ToTable("point_exchange_orders", null, t => - { - t.HasCheckConstraint("ck_point_exchange_orders_points_cost", "points_cost > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Cover") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cover"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DetailImages") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("detail_images") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true) - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyPriceText") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_price_text"); - - b.Property("Link") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("link"); - - b.Property("PreviewIframe") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_iframe"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_products"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_products_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_products_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "Type", "IsActive", "SortOrder") - .HasDatabaseName("ix_products_tenant_id_region_id_type_is_active_sort_order"); - - b.ToTable("products", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.SvipPlan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Badge") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("badge"); - - b.Property("CouponOnly") - .HasColumnType("boolean") - .HasColumnName("coupon_only"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OriginalPriceCents") - .HasColumnType("integer") - .HasColumnName("original_price_cents"); - - b.Property("PerDayLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("per_day_label"); - - b.Property("PriceCents") - .HasColumnType("integer") - .HasColumnName("price_cents"); - - b.Property("Recommended") - .HasColumnType("boolean") - .HasColumnName("recommended"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VpEnabled") - .HasColumnType("boolean") - .HasColumnName("vp_enabled"); - - b.Property("VpProductId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("vp_product_id"); - - b.HasKey("Id") - .HasName("pk_svip_plans"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_svip_plans_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_svip_plans_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_svip_plans_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("svip_plans", null, t => - { - t.HasCheckConstraint("ck_svip_plans_days", "days >= 0"); - - t.HasCheckConstraint("ck_svip_plans_price", "price_cents >= 0 and (original_price_cents is null or original_price_cents >= 0)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BillingCycle") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("billing_cycle"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PlanCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("plan_code"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_subscriptions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_subscriptions_tenant_id_id"); - - b.HasIndex("TenantId", "Status", "ExpiresAt") - .HasDatabaseName("ix_tenant_subscriptions_tenant_id_status_expires_at"); - - b.ToTable("tenant_subscriptions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MetricKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("metric_key"); - - b.Property("MetricValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)") - .HasColumnName("metric_value"); - - b.Property("PeriodEnd") - .HasColumnType("date") - .HasColumnName("period_end"); - - b.Property("PeriodStart") - .HasColumnType("date") - .HasColumnName("period_start"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_usage_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_usage_records_tenant_id_id"); - - b.HasIndex("TenantId", "MetricKey", "PeriodStart", "PeriodEnd") - .HasDatabaseName("ix_tenant_usage_records_tenant_id_metric_key_period_start_peri~"); - - b.ToTable("tenant_usage_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContextPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("context_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("GeneratedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("generated_at"); - - b.Property("InputPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("input_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Model") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("model"); - - b.Property("PromptVersion") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("prompt_version"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("ResultPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("result_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_ai_recommendation_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_ai_recommendation_reports_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_ai_recommendation_reports_user_id"); - - b.HasIndex("TenantId", "RegionId", "CreatedAt") - .HasDatabaseName("ix_ai_recommendation_reports_tenant_id_region_id_created_at"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_ai_recommendation_reports_tenant_id_user_id_created_at"); - - b.ToTable("ai_recommendation_reports", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AppAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssetKey") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("asset_key"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_app_assets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_app_assets_tenant_id_id"); - - b.HasIndex("TenantId", "AssetKey") - .IsUnique() - .HasDatabaseName("ix_app_assets_tenant_id_asset_key"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_app_assets_tenant_id_legacy_id"); - - b.ToTable("app_assets", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssetKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("asset_key"); - - b.Property("AssetType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("asset_type"); - - b.Property("Bucket") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("bucket"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("CdnUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cdn_url"); - - b.Property("ChecksumSha256") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("checksum_sha256"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("DownloadCount") - .HasColumnType("integer") - .HasColumnName("download_count"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("FileSizeBytes") - .HasColumnType("bigint") - .HasColumnName("file_size_bytes"); - - b.Property("IsPublic") - .HasColumnType("boolean") - .HasColumnName("is_public"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MimeType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("mime_type"); - - b.Property("ObjectKey") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("object_key"); - - b.Property("PreviewObjectKey") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("preview_object_key"); - - b.Property("PreviewStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("preview_status"); - - b.Property("PreviewUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_url"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SecurityFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("security_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SecurityScanProvider") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("security_scan_provider"); - - b.Property("SecurityScanStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("not_required") - .HasColumnName("security_scan_status"); - - b.Property("SecurityScanSummary") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("security_scan_summary") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SecurityScannedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("security_scanned_at"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StorageProvider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_provider"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("UploadStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("upload_status"); - - b.Property("VerificationDetails") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("verification_details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.Property("VerifiedBy") - .HasColumnType("uuid") - .HasColumnName("verified_by"); - - b.Property("VerifiedChecksumSha256") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("verified_checksum_sha256"); - - b.Property("VerifiedSizeBytes") - .HasColumnType("bigint") - .HasColumnName("verified_size_bytes"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_assets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_assets_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_assets_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_assets_updated_by"); - - b.HasIndex("VerifiedBy") - .HasDatabaseName("ix_content_assets_verified_by"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_content_assets_tenant_id_category_id"); - - b.HasIndex("TenantId", "ChecksumSha256") - .HasDatabaseName("ix_content_assets_tenant_id_checksum_sha256") - .HasFilter("checksum_sha256 is not null"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_content_assets_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_assets_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PreviewStatus") - .HasDatabaseName("ix_content_assets_tenant_id_preview_status") - .HasFilter("preview_status <> 'none'"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_content_assets_tenant_id_region_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_content_assets_tenant_id_subject_id"); - - b.HasIndex("TenantId", "StorageProvider", "Bucket", "ObjectKey") - .HasDatabaseName("ix_content_assets_tenant_id_storage_provider_bucket_object_key"); - - b.HasIndex("TenantId", "UploadStatus", "Status", "UpdatedAt") - .HasDatabaseName("ix_content_assets_tenant_id_upload_status_status_updated_at"); - - b.HasIndex("TenantId", "SecurityScanStatus", "UploadStatus", "Status", "UpdatedAt") - .HasDatabaseName("ix_content_assets_tenant_id_security_scan_status_upload_status~"); - - b.HasIndex("TenantId", "Status", "Visibility", "AssetType", "RegionId", "SubjectId", "CategoryId", "SortOrder") - .HasDatabaseName("ix_content_assets_tenant_id_status_visibility_asset_type_regio~"); - - b.ToTable("content_assets", null, t => - { - t.HasCheckConstraint("ck_content_assets_download_count", "download_count >= 0"); - - t.HasCheckConstraint("ck_content_assets_file_size", "file_size_bytes is null or file_size_bytes >= 0"); - - t.HasCheckConstraint("ck_content_assets_verified_checksum", "verified_checksum_sha256 is null or verified_checksum_sha256 ~ '^[a-f0-9]{64}$'"); - - t.HasCheckConstraint("ck_content_assets_verified_size", "verified_size_bytes is null or verified_size_bytes >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetAccessEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("access_type"); - - b.Property("ActorRole") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("actor_role"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("AssetType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("asset_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DenyCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("deny_code"); - - b.Property("Disposition") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("disposition"); - - b.Property("ExpiresInSeconds") - .HasColumnType("integer") - .HasColumnName("expires_in_seconds"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Result") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("result"); - - b.Property("SignatureMode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("signature_mode"); - - b.Property("StorageProvider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("storage_provider"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("Visibility") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_asset_access_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_asset_access_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_content_asset_access_events_user_id"); - - b.HasIndex("TenantId", "AssetId", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_asset_id_created_at"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_user_id_created_at") - .HasFilter("user_id is not null"); - - b.HasIndex("TenantId", "AccessType", "Result", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_access_type_result_cr~"); - - b.ToTable("content_asset_access_events", null, t => - { - t.HasCheckConstraint("ck_content_asset_access_events_expiry", "expires_in_seconds is null or expires_in_seconds > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetSecurityScanEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.PrimitiveCollection("IssueCodes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("issue_codes") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RiskLevel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("risk_level"); - - b.Property("ScanStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scan_status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_content_asset_security_scan_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_asset_security_scan_events_tenant_id_id"); - - b.HasIndex("TenantId", "AssetId", "CreatedAt") - .HasDatabaseName("ix_content_asset_security_scan_events_tenant_id_asset_id_creat~"); - - b.HasIndex("TenantId", "ScanStatus", "RiskLevel", "CreatedAt") - .HasDatabaseName("ix_content_asset_security_scan_events_tenant_id_scan_status_ri~"); - - b.ToTable("content_asset_security_scan_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("description"); - - b.Property("EntryKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("entry_key"); - - b.Property("EntryType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("entry_type"); - - b.Property("Icon") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LayoutConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("layout_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Route") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("route"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_entries"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_entries_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_entries_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_entries_updated_by"); - - b.HasIndex("TenantId", "EntryKey") - .IsUnique() - .HasDatabaseName("ix_content_entries_tenant_id_entry_key"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_entries_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "EntryType", "IsActive", "SortOrder") - .HasDatabaseName("ix_content_entries_tenant_id_region_id_entry_type_is_active_so~"); - - b.ToTable("content_entries", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FieldPath") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("field_path"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("JobId") - .HasColumnType("uuid") - .HasColumnName("job_id"); - - b.Property("Message") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("message"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_content_import_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_issues_tenant_id_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_content_import_issues_tenant_id_item_id"); - - b.HasIndex("TenantId", "JobId", "Severity", "RowNo") - .HasDatabaseName("ix_content_import_issues_tenant_id_job_id_severity_row_no"); - - b.ToTable("content_import_issues", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("content_hash"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExternalId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("external_id"); - - b.Property("IssuesCount") - .HasColumnType("integer") - .HasColumnName("issues_count"); - - b.Property("JobId") - .HasColumnType("uuid") - .HasColumnName("job_id"); - - b.Property("NormalizedPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("normalized_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("SourcePayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("source_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TargetId") - .HasColumnType("uuid") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_content_import_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_items_tenant_id_id"); - - b.HasIndex("TenantId", "JobId", "RowNo") - .IsUnique() - .HasDatabaseName("ix_content_import_items_tenant_id_job_id_row_no"); - - b.HasIndex("TenantId", "JobId", "Status", "RowNo") - .HasDatabaseName("ix_content_import_items_tenant_id_job_id_status_row_no"); - - b.ToTable("content_import_items", null, t => - { - t.HasCheckConstraint("ck_content_import_items_issues_count", "issues_count >= 0"); - - t.HasCheckConstraint("ck_content_import_items_row_no", "row_no >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DryRun") - .HasColumnType("boolean") - .HasColumnName("dry_run"); - - b.Property("ErrorCount") - .HasColumnType("integer") - .HasColumnName("error_count"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("ImportType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("import_type"); - - b.Property("InsertedCount") - .HasColumnType("integer") - .HasColumnName("inserted_count"); - - b.Property("NormalizedPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("normalized_payload") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SkippedCount") - .HasColumnType("integer") - .HasColumnName("skipped_count"); - - b.Property("SourceFormat") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_format"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SourceName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("source_name"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("summary") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TargetCategoryId") - .HasColumnType("uuid") - .HasColumnName("target_category_id"); - - b.Property("TargetContentNodeId") - .HasColumnType("uuid") - .HasColumnName("target_content_node_id"); - - b.Property("TargetQuestionBankId") - .HasColumnType("uuid") - .HasColumnName("target_question_bank_id"); - - b.Property("TargetRegionId") - .HasColumnType("uuid") - .HasColumnName("target_region_id"); - - b.Property("TargetSubjectId") - .HasColumnType("uuid") - .HasColumnName("target_subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedCount") - .HasColumnType("integer") - .HasColumnName("updated_count"); - - b.Property("ValidCount") - .HasColumnType("integer") - .HasColumnName("valid_count"); - - b.Property("WarningCount") - .HasColumnType("integer") - .HasColumnName("warning_count"); - - b.HasKey("Id") - .HasName("pk_content_import_jobs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_jobs_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_import_jobs_created_by"); - - b.HasIndex("TenantId", "TargetCategoryId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_category_id"); - - b.HasIndex("TenantId", "TargetContentNodeId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_content_node_id"); - - b.HasIndex("TenantId", "TargetQuestionBankId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_question_bank_id"); - - b.HasIndex("TenantId", "TargetRegionId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_region_id"); - - b.HasIndex("TenantId", "TargetSubjectId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_subject_id"); - - b.HasIndex("TenantId", "ImportType", "Status", "CreatedAt") - .HasDatabaseName("ix_content_import_jobs_tenant_id_import_type_status_created_at"); - - b.ToTable("content_import_jobs", null, t => - { - t.HasCheckConstraint("ck_content_import_jobs_counts", "total_count >= 0 and valid_count >= 0 and error_count >= 0 and warning_count >= 0 and inserted_count >= 0 and updated_count >= 0 and skipped_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Depth") - .HasColumnType("integer") - .HasColumnName("depth"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsLeaf") - .HasColumnType("boolean") - .HasColumnName("is_leaf"); - - b.Property("IsSelectable") - .HasColumnType("boolean") - .HasColumnName("is_selectable"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MarkerConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("marker_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MarkerType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("marker_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("node_key"); - - b.Property("NodeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("node_type"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("Path") - .HasColumnType("ltree") - .HasColumnName("path"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_content_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_nodes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_nodes_created_by"); - - b.HasIndex("Path") - .HasDatabaseName("ix_content_nodes_path"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Path"), "gist"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_nodes_updated_by"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_nodes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MarkerType") - .HasDatabaseName("ix_content_nodes_tenant_id_marker_type") - .HasFilter("marker_type is not null"); - - b.HasIndex("TenantId", "ParentId") - .HasDatabaseName("ix_content_nodes_tenant_id_parent_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_content_nodes_tenant_id_region_id"); - - b.HasIndex("TenantId", "EntryId", "NodeKey") - .IsUnique() - .HasDatabaseName("ix_content_nodes_tenant_id_entry_id_node_key"); - - b.HasIndex("TenantId", "EntryId", "ParentId", "SortOrder") - .HasDatabaseName("ix_content_nodes_tenant_id_entry_id_parent_id_sort_order"); - - b.ToTable("content_nodes", null, t => - { - t.HasCheckConstraint("ck_content_nodes_depth", "depth >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookChapter", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_chapters"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_chapters_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_chapters_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_chapters_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_handbook_chapters_tenant_id_subject_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_chapters_tenant_id_entry_id_content_node_id_subjec~"); - - b.ToTable("handbook_chapters", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ChapterId") - .HasColumnType("uuid") - .HasColumnName("chapter_id"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("Summary") - .HasColumnType("text") - .HasColumnName("summary"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_entries"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_entries_tenant_id_id"); - - b.HasIndex("TenantId", "ChapterId") - .HasDatabaseName("ix_handbook_entries_tenant_id_chapter_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_entries_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_entries_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "ChapterId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_entries_tenant_id_entry_id_content_node_id_chapter~"); - - b.ToTable("handbook_entries", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookSubject", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorLegacyIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("major_legacy_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_subjects"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_subjects_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_subjects_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_major_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_school_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_subjects_tenant_id_entry_id_content_node_id_region~"); - - b.ToTable("handbook_subjects", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ImageAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("CdnUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cdn_url"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("IsPublic") - .HasColumnType("boolean") - .HasColumnName("is_public"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_images"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_images_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_images_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "Category", "IsPublic") - .HasDatabaseName("ix_images_tenant_id_category_is_public"); - - b.ToTable("images", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.PracticeBlueprint", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssemblyType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assembly_type"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("mode"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("PassScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("pass_score"); - - b.Property("QuestionLimit") - .HasColumnType("integer") - .HasColumnName("question_limit"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Rules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Sections") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("sections") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_practice_blueprints"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_blueprints_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_practice_blueprints_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_practice_blueprints_updated_by"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_collection_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_practice_blueprints_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_region_id"); - - b.HasIndex("TenantId", "NodeId", "CollectionId", "Mode", "Status", "SortOrder") - .HasDatabaseName("ix_practice_blueprints_tenant_id_node_id_collection_id_mode_st~"); - - b.ToTable("practice_blueprints", null, t => - { - t.HasCheckConstraint("ck_practice_blueprints_duration", "duration_minutes is null or duration_minutes > 0"); - - t.HasCheckConstraint("ck_practice_blueprints_question_limit", "question_limit is null or question_limit > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("CollectionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("collection_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Filters") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("filters") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_question_collections"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_collections_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_question_collections_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_question_collections_updated_by"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_question_collections_tenant_id_category_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_question_collections_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_collections_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "QuestionBankId") - .HasDatabaseName("ix_question_collections_tenant_id_question_bank_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_question_collections_tenant_id_region_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_question_collections_tenant_id_subject_id"); - - b.HasIndex("TenantId", "NodeId", "Status", "SortOrder") - .HasDatabaseName("ix_question_collections_tenant_id_node_id_status_sort_order"); - - b.ToTable("question_collections", null, t => - { - t.HasCheckConstraint("ck_question_collections_duration", "duration_minutes is null or duration_minutes > 0"); - - t.HasCheckConstraint("ck_question_collections_question_count", "question_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollectionItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("Required") - .HasColumnType("boolean") - .HasColumnName("required"); - - b.Property("Score") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("score"); - - b.Property("SectionKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("section_key"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_collection_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_collection_items_tenant_id_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_question_collection_items_question_owner_tenant_id_question~"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_question_collection_items_tenant_id_question_reference_id"); - - b.HasIndex("TenantId", "CollectionId", "QuestionReferenceId") - .IsUnique() - .HasDatabaseName("ix_question_collection_items_tenant_id_collection_id_question_~"); - - b.HasIndex("TenantId", "CollectionId", "SectionKey", "SortOrder") - .HasDatabaseName("ix_question_collection_items_tenant_id_collection_id_section_k~"); - - b.ToTable("question_collection_items", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionTypeGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("display_name"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Types") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("types") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_type_groups"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_type_groups_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_type_groups_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_question_type_groups_tenant_id_subject_id_is_active_sort_or~"); - - b.ToTable("question_type_groups", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionVideo", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyQuestionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_question_id"); - - b.Property("LegacyVideoId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_video_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VideoId") - .HasColumnType("uuid") - .HasColumnName("video_id"); - - b.Property("VideoType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("video_type"); - - b.HasKey("Id") - .HasName("pk_question_videos"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_videos_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_videos_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "VideoId") - .HasDatabaseName("ix_question_videos_tenant_id_video_id"); - - b.HasIndex("TenantId", "QuestionId", "VideoId", "VideoType") - .IsUnique() - .HasDatabaseName("ix_question_videos_tenant_id_question_id_video_id_video_type"); - - b.ToTable("question_videos", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.SubjectShare", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("SourceSubjectId") - .HasColumnType("uuid") - .HasColumnName("source_subject_id"); - - b.Property("TargetSubjectId") - .HasColumnType("uuid") - .HasColumnName("target_subject_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Id") - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacySourceSubjectId") - .HasColumnType("text") - .HasColumnName("legacy_source_subject_id"); - - b.Property("LegacyTargetSubjectId") - .HasColumnType("text") - .HasColumnName("legacy_target_subject_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("TenantId", "SourceSubjectId", "TargetSubjectId") - .HasName("pk_subject_shares"); - - b.HasIndex("TenantId", "TargetSubjectId") - .HasDatabaseName("ix_subject_shares_tenant_id_target_subject_id"); - - b.ToTable("subject_shares", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionBankPreference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Alias") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("alias"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("IsVisible") - .HasColumnType("boolean") - .HasColumnName("is_visible"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NavigationLocation") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("navigation_location"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("QuestionBankOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_bank_owner_tenant_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_question_bank_preferences"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_question_bank_preferences_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_question_bank_preferences_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_question_bank_preferences_updated_by"); - - b.HasIndex("QuestionBankOwnerTenantId", "QuestionBankId") - .HasDatabaseName("ix_tenant_question_bank_preferences_question_bank_owner_tenant~"); - - b.HasIndex("TenantId", "QuestionBankOwnerTenantId", "QuestionBankId") - .IsUnique() - .HasDatabaseName("ix_tenant_question_bank_preferences_tenant_id_question_bank_ow~"); - - b.ToTable("tenant_question_bank_preferences", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionReference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_question_references"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_question_references_tenant_id_id"); - - b.HasAlternateKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasName("ak_tenant_question_references_tenant_id_question_owner_tenant_~"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_question_references_created_by"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_tenant_question_references_question_owner_tenant_id_questio~"); - - b.ToTable("tenant_question_references", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordFavorite", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FavoritedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("favorited_at"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacyUserId") - .HasColumnType("text") - .HasColumnName("legacy_user_id"); - - b.Property("LegacyWordId") - .HasColumnType("text") - .HasColumnName("legacy_word_id"); - - b.Property("Note") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WordId") - .HasColumnType("uuid") - .HasColumnName("word_id"); - - b.HasKey("Id") - .HasName("pk_user_word_favorites"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_word_favorites_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_word_favorites_user_id"); - - b.HasIndex("TenantId", "WordId") - .HasDatabaseName("ix_user_word_favorites_tenant_id_word_id"); - - b.HasIndex("TenantId", "UserId", "WordId") - .IsUnique() - .HasDatabaseName("ix_user_word_favorites_tenant_id_user_id_word_id"); - - b.ToTable("user_word_favorites", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordProgress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CorrectStreak") - .HasColumnType("integer") - .HasColumnName("correct_streak"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DueLevel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("due_level"); - - b.Property("EaseFactor") - .ValueGeneratedOnAdd() - .HasPrecision(4, 2) - .HasColumnType("numeric(4,2)") - .HasDefaultValue(2.50m) - .HasColumnName("ease_factor"); - - b.Property("LastResult") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("last_result"); - - b.Property("LastReviewAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_review_at"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacyUserId") - .HasColumnType("text") - .HasColumnName("legacy_user_id"); - - b.Property("LegacyWordId") - .HasColumnType("text") - .HasColumnName("legacy_word_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NextReviewAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_review_at"); - - b.Property("ReviewCount") - .HasColumnType("integer") - .HasColumnName("review_count"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WordId") - .HasColumnType("uuid") - .HasColumnName("word_id"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.HasKey("Id") - .HasName("pk_user_word_progress"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_word_progress_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_word_progress_user_id"); - - b.HasIndex("TenantId", "WordId") - .HasDatabaseName("ix_user_word_progress_tenant_id_word_id"); - - b.HasIndex("TenantId", "UserId", "WordId") - .IsUnique() - .HasDatabaseName("ix_user_word_progress_tenant_id_user_id_word_id"); - - b.HasIndex("TenantId", "UserId", "NextReviewAt", "Status", "DueLevel") - .HasDatabaseName("ix_user_word_progress_tenant_id_user_id_next_review_at_status_~"); - - b.ToTable("user_word_progress", null, t => - { - t.HasCheckConstraint("ck_user_word_progress_correct_streak", "correct_streak >= 0"); - - t.HasCheckConstraint("ck_user_word_progress_ease_factor", "ease_factor >= 1.30 and ease_factor <= 3.00"); - - t.HasCheckConstraint("ck_user_word_progress_review_count", "review_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoExplanation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("DurationSeconds") - .HasColumnType("integer") - .HasColumnName("duration_seconds"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsGeneral") - .HasColumnType("boolean") - .HasColumnName("is_general"); - - b.Property("KnowledgeTags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("knowledge_tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacySubjectId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_subject_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ThumbnailUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("thumbnail_url"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VideoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("video_url"); - - b.HasKey("Id") - .HasName("pk_video_explanations"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_video_explanations_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_video_explanations_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SourceHash") - .HasDatabaseName("ix_video_explanations_tenant_id_source_hash") - .HasFilter("source_hash is not null"); - - b.HasIndex("TenantId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_video_explanations_tenant_id_subject_id_is_active_sort_order"); - - b.ToTable("video_explanations", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoPlaybackProgress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DurationSeconds") - .HasColumnType("integer") - .HasColumnName("duration_seconds"); - - b.Property("IsCompleted") - .HasColumnType("boolean") - .HasColumnName("is_completed"); - - b.Property("LastPlayedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_played_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PlayCount") - .HasColumnType("integer") - .HasColumnName("play_count"); - - b.Property("PositionSeconds") - .HasColumnType("integer") - .HasColumnName("position_seconds"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("VideoId") - .HasColumnType("uuid") - .HasColumnName("video_id"); - - b.Property("WatchedSeconds") - .HasColumnType("integer") - .HasColumnName("watched_seconds"); - - b.HasKey("Id") - .HasName("pk_video_playback_progress"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_video_playback_progress_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_video_playback_progress_user_id"); - - b.HasIndex("TenantId", "QuestionId") - .HasDatabaseName("ix_video_playback_progress_tenant_id_question_id"); - - b.HasIndex("TenantId", "VideoId") - .HasDatabaseName("ix_video_playback_progress_tenant_id_video_id"); - - b.HasIndex("TenantId", "UserId", "LastPlayedAt") - .HasDatabaseName("ix_video_playback_progress_tenant_id_user_id_last_played_at"); - - b.HasIndex("TenantId", "UserId", "VideoId") - .IsUnique() - .HasDatabaseName("ix_video_playback_progress_tenant_id_user_id_video_id") - .HasFilter("question_id is null"); - - b.HasIndex("TenantId", "UserId", "VideoId", "QuestionId") - .IsUnique() - .HasDatabaseName("ix_video_playback_progress_tenant_id_user_id_video_id_question~") - .HasFilter("question_id is not null"); - - b.ToTable("video_playback_progress", null, t => - { - t.HasCheckConstraint("ck_video_playback_progress_duration", "duration_seconds is null or duration_seconds >= 0"); - - t.HasCheckConstraint("ck_video_playback_progress_play_count", "play_count >= 0"); - - t.HasCheckConstraint("ck_video_playback_progress_position", "position_seconds >= 0"); - - t.HasCheckConstraint("ck_video_playback_progress_watched", "watched_seconds >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyUnit", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WordCount") - .HasColumnType("integer") - .HasColumnName("word_count"); - - b.HasKey("Id") - .HasName("pk_vocabulary_units"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_vocabulary_units_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_vocabulary_units_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_vocabulary_units_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_vocabulary_units_tenant_id_region_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_vocabulary_units_tenant_id_entry_id_content_node_id_region_~"); - - b.ToTable("vocabulary_units", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyWord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Example") - .HasColumnType("text") - .HasColumnName("example"); - - b.Property("ExampleTranslation") - .HasColumnType("text") - .HasColumnName("example_translation"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Meaning") - .HasColumnType("text") - .HasColumnName("meaning"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Phonetic") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("phonetic"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitId") - .HasColumnType("uuid") - .HasColumnName("unit_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Word") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("word"); - - b.HasKey("Id") - .HasName("pk_vocabulary_words"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_vocabulary_words_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_vocabulary_words_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_vocabulary_words_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UnitId", "IsActive", "SortOrder", "CreatedAt") - .HasDatabaseName("ix_vocabulary_words_tenant_id_unit_id_is_active_sort_order_cre~"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "UnitId", "IsActive", "SortOrder") - .HasDatabaseName("ix_vocabulary_words_tenant_id_entry_id_content_node_id_unit_id~"); - - b.ToTable("vocabulary_words", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CommissionAmountCents") - .HasColumnType("integer") - .HasColumnName("commission_amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DefaultRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("default_rate"); - - b.Property("EffectiveRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("effective_rate"); - - b.Property("GeneratedBy") - .HasColumnType("uuid") - .HasColumnName("generated_by"); - - b.Property("GrossAmountCents") - .HasColumnType("integer") - .HasColumnName("gross_amount_cents"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaidBy") - .HasColumnType("uuid") - .HasColumnName("paid_by"); - - b.Property("PaidUserCount") - .HasColumnType("integer") - .HasColumnName("paid_user_count"); - - b.Property("PaymentAccount") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("payment_account"); - - b.Property("PaymentMethod") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_method"); - - b.Property("PeriodEnd") - .HasColumnType("date") - .HasColumnName("period_end"); - - b.Property("PeriodStart") - .HasColumnType("date") - .HasColumnName("period_start"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("SettlementNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("settlement_no"); - - b.Property("SourceCount") - .HasColumnType("integer") - .HasColumnName("source_count"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlements_tenant_id_id"); - - b.HasIndex("GeneratedBy") - .HasDatabaseName("ix_commission_settlements_generated_by"); - - b.HasIndex("PaidBy") - .HasDatabaseName("ix_commission_settlements_paid_by"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_commission_settlements_referrer_user_id"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commission_settlements_reviewed_by"); - - b.HasIndex("TenantId", "SettlementNo") - .IsUnique() - .HasDatabaseName("ix_commission_settlements_tenant_id_settlement_no"); - - b.HasIndex("TenantId", "ReferrerUserId", "PeriodStart") - .HasDatabaseName("ix_commission_settlements_referrer_start"); - - b.HasIndex("TenantId", "ReferrerUserId", "PeriodStart", "PeriodEnd") - .IsUnique() - .HasDatabaseName("ux_commission_settlements_referrer_period"); - - b.ToTable("commission_settlements", null, t => - { - t.HasCheckConstraint("ck_commission_settlements_amounts", "gross_amount_cents >= 0 and commission_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commission_settlements_counts", "source_count >= 0 and paid_user_count >= 0"); - - t.HasCheckConstraint("ck_commission_settlements_period", "period_end >= period_start"); - - t.HasCheckConstraint("ck_commission_settlements_rates", "default_rate >= 0 and default_rate <= 1 and (effective_rate is null or (effective_rate >= 0 and effective_rate <= 1))"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementExportEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentSha256") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("content_sha256"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExportFormat") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasColumnName("export_format"); - - b.Property("ExportedBy") - .HasColumnType("uuid") - .HasColumnName("exported_by"); - - b.Property("Filename") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("filename"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RowCount") - .HasColumnType("integer") - .HasColumnName("row_count"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_export_events"); - - b.HasIndex("ExportedBy") - .HasDatabaseName("ix_commission_settlement_export_events_exported_by"); - - b.HasIndex("TenantId", "SettlementId", "CreatedAt") - .HasDatabaseName("ix_commission_settlement_export_events_tenant_id_settlement_id~"); - - b.ToTable("commission_settlement_export_events", null, t => - { - t.HasCheckConstraint("ck_commission_export_events_rows", "row_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AttributionType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("attribution_type"); - - b.Property("CommissionAmountCents") - .HasColumnType("integer") - .HasColumnName("commission_amount_cents"); - - b.Property("CommissionRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("commission_rate"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("GrossAmountCents") - .HasColumnType("integer") - .HasColumnName("gross_amount_cents"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RateSource") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("rate_source"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_no"); - - b.Property("SourcePaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("source_paid_at"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_type"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlement_items_tenant_id_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_commission_settlement_items_referrer_user_id"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_commission_settlement_items_student_user_id"); - - b.HasIndex("TenantId", "SettlementId") - .HasDatabaseName("ix_commission_settlement_items_tenant_id_settlement_id"); - - b.HasIndex("TenantId", "ReferrerUserId", "SourcePaidAt") - .HasDatabaseName("ix_commission_settlement_items_tenant_id_referrer_user_id_sour~"); - - b.HasIndex("TenantId", "SourceType", "SourceId") - .IsUnique() - .HasDatabaseName("ix_commission_settlement_items_tenant_id_source_type_source_id"); - - b.ToTable("commission_settlement_items", null, t => - { - t.HasCheckConstraint("ck_commission_settlement_items_amounts", "gross_amount_cents >= 0 and commission_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commission_settlement_items_rate", "commission_rate >= 0 and commission_rate <= 1"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementProof", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("ExternalUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("external_url"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentAccount") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("payment_account"); - - b.Property("PaymentMethod") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_method"); - - b.Property("ProofType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("proof_type"); - - b.Property("ReviewNote") - .HasColumnType("text") - .HasColumnName("review_note"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubmittedBy") - .HasColumnType("uuid") - .HasColumnName("submitted_by"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_proofs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlement_proofs_tenant_id_id"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commission_settlement_proofs_reviewed_by"); - - b.HasIndex("SubmittedBy") - .HasDatabaseName("ix_commission_settlement_proofs_submitted_by"); - - b.HasIndex("TenantId", "AssetId") - .HasDatabaseName("ix_commission_settlement_proofs_tenant_id_asset_id"); - - b.HasIndex("TenantId", "SettlementId", "Status", "CreatedAt") - .HasDatabaseName("ix_commission_settlement_proofs_tenant_id_settlement_id_status~"); - - b.ToTable("commission_settlement_proofs", null, t => - { - t.HasCheckConstraint("ck_commission_settlement_proofs_amount", "amount_cents is null or amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignmentConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("assignment_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssignmentCursor") - .HasColumnType("integer") - .HasColumnName("assignment_cursor"); - - b.Property("AssignmentMode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assignment_mode"); - - b.Property("AssignmentPool") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("assignment_pool") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DelaySeconds") - .HasColumnType("integer") - .HasColumnName("delay_seconds"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("ExamType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("exam_type"); - - b.Property("FormName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("form_name"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TimeoutSeconds") - .HasColumnType("integer") - .HasColumnName("timeout_seconds"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Url") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_crm_config"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_config_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_crm_config_tenant_id"); - - b.HasIndex("TenantId", "AssignmentMode") - .HasDatabaseName("ix_crm_config_tenant_id_assignment_mode"); - - b.ToTable("crm_config", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempt") - .HasColumnType("integer") - .HasColumnName("attempt"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("HttpCode") - .HasColumnType("integer") - .HasColumnName("http_code"); - - b.Property("LeadId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Outcome") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("outcome"); - - b.Property("RecordId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("record_id"); - - b.Property("RequestBody") - .HasColumnType("text") - .HasColumnName("request_body"); - - b.Property("RequestPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("request_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ResponseSummary") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("response_summary"); - - b.Property("SignedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("signed_at"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_crm_webhook_log"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_webhook_log_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_log_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RecordId", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_log_tenant_id_record_id_created_at"); - - b.ToTable("crm_webhook_log", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookQueueItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IdempotencyKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("idempotency_key"); - - b.Property("LastAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_attempt_at"); - - b.Property("LastError") - .HasColumnType("text") - .HasColumnName("last_error"); - - b.Property("LastHttpCode") - .HasColumnType("integer") - .HasColumnName("last_http_code"); - - b.Property("LastResponseSummary") - .HasColumnType("text") - .HasColumnName("last_response_summary"); - - b.Property("LeadId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("NextAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_attempt_at"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Provider") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RecordId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("record_id"); - - b.Property("ScheduledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("scheduled_at"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Source") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TargetUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("target_url"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_crm_webhook_queue"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_webhook_queue_tenant_id_id"); - - b.HasIndex("TenantId", "IdempotencyKey") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_idempotency_key") - .HasFilter("idempotency_key is not null"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RecordId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_record_id") - .HasFilter("record_id is not null"); - - b.HasIndex("TenantId", "RecordId", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_record_id_created_at") - .HasFilter("record_id is not null"); - - b.HasIndex("TenantId", "Source", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_source_created_at") - .HasFilter("source = 'tenant.student.crm_push'"); - - b.HasIndex("TenantId", "Status", "ScheduledAt", "NextAttemptAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_status_scheduled_at_next_attemp~"); - - b.ToTable("crm_webhook_queue", null, t => - { - t.HasCheckConstraint("ck_crm_webhook_queue_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Channel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LandingPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("landing_path"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_referral_codes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_codes_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_referral_codes_user_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_referral_codes_tenant_id_code"); - - b.HasIndex("TenantId", "UserId") - .IsUnique() - .HasDatabaseName("ix_referral_codes_tenant_id_user_id"); - - b.HasIndex("TenantId", "Code", "Status") - .HasDatabaseName("ix_referral_codes_tenant_id_code_status"); - - b.ToTable("referral_codes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralLead", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("assigned_at"); - - b.Property("AssignedBy") - .HasColumnType("uuid") - .HasColumnName("assigned_by"); - - b.Property("AssignedToUserId") - .HasColumnType("uuid") - .HasColumnName("assigned_to_user_id"); - - b.Property("AssignmentMode") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assignment_mode"); - - b.Property("BindType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("bind_type"); - - b.Property("BoundAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("bound_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FirstTrackId") - .HasColumnType("uuid") - .HasColumnName("first_track_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ProtectedUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("protected_until"); - - b.Property("RefCode") - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Source") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_referral_leads"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_leads_tenant_id_id"); - - b.HasIndex("AssignedBy") - .HasDatabaseName("ix_referral_leads_assigned_by"); - - b.HasIndex("AssignedToUserId") - .HasDatabaseName("ix_referral_leads_assigned_to_user_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_referral_leads_referrer_user_id"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_referral_leads_student_user_id"); - - b.HasIndex("TenantId", "FirstTrackId") - .HasDatabaseName("ix_referral_leads_tenant_id_first_track_id"); - - b.HasIndex("TenantId", "StudentUserId") - .IsUnique() - .HasDatabaseName("ix_referral_leads_tenant_id_student_user_id"); - - b.HasIndex("TenantId", "ReferrerUserId", "BoundAt") - .HasDatabaseName("ix_referral_leads_tenant_id_referrer_user_id_bound_at"); - - b.HasIndex("TenantId", "AssignedToUserId", "Status", "BoundAt") - .HasDatabaseName("ix_referral_leads_tenant_id_assigned_to_user_id_status_bound_at"); - - b.ToTable("referral_leads", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralQrcode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Page") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("page"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("QrcodeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("qrcode_url"); - - b.Property("RefCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("Scene") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("scene"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_referral_qrcodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_qrcodes_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_referral_qrcodes_user_id"); - - b.HasIndex("TenantId", "RefCode", "Status") - .HasDatabaseName("ix_referral_qrcodes_tenant_id_ref_code_status"); - - b.HasIndex("TenantId", "Provider", "Scene", "Page") - .IsUnique() - .HasDatabaseName("ix_referral_qrcodes_tenant_id_provider_scene_page"); - - b.ToTable("referral_qrcodes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTeamEdge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LeaderUserId") - .HasColumnType("uuid") - .HasColumnName("leader_user_id"); - - b.Property("MemberUserId") - .HasColumnType("uuid") - .HasColumnName("member_user_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RelationType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("relation_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_referral_team_edges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_team_edges_tenant_id_id"); - - b.HasIndex("LeaderUserId") - .HasDatabaseName("ix_referral_team_edges_leader_user_id"); - - b.HasIndex("MemberUserId") - .HasDatabaseName("ix_referral_team_edges_member_user_id"); - - b.HasIndex("TenantId", "LeaderUserId", "Status") - .HasDatabaseName("ix_referral_team_edges_tenant_id_leader_user_id_status"); - - b.HasIndex("TenantId", "MemberUserId", "RelationType") - .IsUnique() - .HasDatabaseName("ix_referral_team_edges_tenant_id_member_user_id_relation_type"); - - b.ToTable("referral_team_edges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTrack", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("LeadId") - .HasColumnType("uuid") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefCode") - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Source") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source"); - - b.Property("TargetUserId") - .HasColumnType("uuid") - .HasColumnName("target_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_referral_tracks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_tracks_tenant_id_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_referral_tracks_referrer_user_id"); - - b.HasIndex("TargetUserId") - .HasDatabaseName("ix_referral_tracks_target_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_referral_tracks_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "LeadId", "CreatedAt") - .HasDatabaseName("ix_referral_tracks_tenant_id_lead_id_created_at"); - - b.HasIndex("TenantId", "RefCode", "EventType", "CreatedAt") - .HasDatabaseName("ix_referral_tracks_tenant_id_ref_code_event_type_created_at"); - - b.ToTable("referral_tracks", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.TenantCommissionSetting", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Config") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DefaultRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("default_rate"); - - b.Property("MinSettlementCents") - .HasColumnType("integer") - .HasColumnName("min_settlement_cents"); - - b.Property("SettlementCycle") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("settlement_cycle"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_commission_settings"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_commission_settings_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_commission_settings_tenant_id"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_commission_settings_updated_by"); - - b.ToTable("tenant_commission_settings", null, t => - { - t.HasCheckConstraint("ck_tenant_commission_settings_default_rate", "default_rate >= 0 and default_rate <= 1"); - - t.HasCheckConstraint("ck_tenant_commission_settings_min_settlement", "min_settlement_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.StudentProfile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AvatarPreset") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("male") - .HasColumnName("avatar_preset"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LastCheckInDate") - .HasColumnType("date") - .HasColumnName("last_check_in_date"); - - b.Property("LegacyUserId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_user_id"); - - b.Property("MasteredWordsCount") - .HasColumnType("integer") - .HasColumnName("mastered_words_count"); - - b.Property("ModuleSelections") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("module_selections") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Progress") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("progress") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionsAnsweredToday") - .HasColumnType("integer") - .HasColumnName("questions_answered_today"); - - b.Property("RecentActivities") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("recent_activities") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SelectedMajorId") - .HasColumnType("uuid") - .HasColumnName("selected_major_id"); - - b.Property("SelectedSchoolId") - .HasColumnType("uuid") - .HasColumnName("selected_school_id"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_student_profiles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_student_profiles_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_student_profiles_user_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_student_profiles_tenant_id_region_id"); - - b.HasIndex("TenantId", "SelectedMajorId") - .HasDatabaseName("ix_student_profiles_tenant_id_selected_major_id"); - - b.HasIndex("TenantId", "SelectedSchoolId") - .HasDatabaseName("ix_student_profiles_tenant_id_selected_school_id"); - - b.HasIndex("TenantId", "UserId") - .IsUnique() - .HasDatabaseName("ix_student_profiles_tenant_id_user_id"); - - b.ToTable("student_profiles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessFailedCount") - .HasColumnType("integer") - .HasColumnName("access_failed_count"); - - b.Property("AvatarUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("avatar_url"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("concurrency_stamp"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Email") - .HasMaxLength(320) - .HasColumnType("citext") - .HasColumnName("email"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean") - .HasColumnName("email_confirmed"); - - b.Property("ForcePasswordChange") - .HasColumnType("boolean") - .HasColumnName("force_password_change"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_seen_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean") - .HasColumnName("lockout_enabled"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone") - .HasColumnName("lockout_end"); - - b.Property("Name") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("NormalizedEmail") - .HasMaxLength(320) - .HasColumnType("character varying(320)") - .HasColumnName("normalized_email"); - - b.Property("NormalizedUserName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("normalized_user_name"); - - b.Property("PasswordHash") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("password_hash"); - - b.Property("Phone") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("PhoneNumber") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone_number"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean") - .HasColumnName("phone_number_confirmed"); - - b.Property("PrimaryRole") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("primary_role"); - - b.Property("RawProfile") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_profile") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Score") - .HasColumnType("integer") - .HasColumnName("score"); - - b.Property("SecurityStamp") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("security_stamp"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean") - .HasColumnName("two_factor_enabled"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("user_name"); - - b.HasKey("Id") - .HasName("pk_users"); - - b.HasIndex("LegacyId") - .IsUnique() - .HasDatabaseName("ix_users_legacy_id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("email_index"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("user_name_index"); - - b.HasIndex("Phone") - .IsUnique() - .HasDatabaseName("ix_users_phone"); - - b.ToTable("users", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.UserIdentity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Email") - .HasMaxLength(320) - .HasColumnType("citext") - .HasColumnName("email"); - - b.Property("OpenId") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("open_id"); - - b.Property("Phone") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderSubject") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("provider_subject"); - - b.Property("UnionId") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("union_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_identities"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_identities_user_id"); - - b.HasIndex("Provider", "ProviderSubject") - .IsUnique() - .HasDatabaseName("ix_user_identities_provider_provider_subject"); - - b.ToTable("user_identities", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CollectionName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("collection_name"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FieldPath") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("field_path"); - - b.Property("IssueCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("LegacyId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_id"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("RawValueSample") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("raw_value_sample"); - - b.Property("RunId") - .HasColumnType("uuid") - .HasColumnName("run_id"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_pb_import_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_pb_import_issues_tenant_id_id"); - - b.HasIndex("TenantId", "RunId") - .HasDatabaseName("ix_pb_import_issues_tenant_id_run_id"); - - b.HasIndex("RunId", "Severity", "CollectionName") - .HasDatabaseName("ix_pb_import_issues_run_id_severity_collection_name"); - - b.HasIndex("TenantId", "CollectionName", "LegacyId") - .HasDatabaseName("ix_pb_import_issues_tenant_id_collection_name_legacy_id"); - - b.ToTable("pb_import_issues", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportRun", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("SourceKind") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_kind"); - - b.Property("SourceName") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("source_name"); - - b.Property("StartedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at") - .HasDefaultValueSql("now()"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_pb_import_runs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_pb_import_runs_tenant_id_id"); - - b.HasIndex("TenantId", "Status", "StartedAt") - .HasDatabaseName("ix_pb_import_runs_tenant_id_status_started_at"); - - b.ToTable("pb_import_runs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseRawRecord", b => - { - b.Property("RunId") - .HasColumnType("uuid") - .HasColumnName("run_id"); - - b.Property("CollectionName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("collection_name"); - - b.Property("LegacyId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_id"); - - b.Property("Errors") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("errors") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("ImportedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("imported_at") - .HasDefaultValueSql("now()"); - - b.Property("Normalized") - .HasColumnType("boolean") - .HasColumnName("normalized"); - - b.Property("Record") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("record") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("RunId", "CollectionName", "LegacyId") - .HasName("pk_pb_raw_records"); - - b.HasIndex("TenantId", "RunId") - .HasDatabaseName("ix_pb_raw_records_tenant_id_run_id"); - - b.HasIndex("TenantId", "CollectionName", "LegacyId") - .HasDatabaseName("ix_pb_raw_records_tenant_id_collection_name_legacy_id"); - - b.ToTable("pb_raw_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.AnswerRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AnswerText") - .HasColumnType("text") - .HasColumnName("answer_text"); - - b.Property("AnsweredAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("answered_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsCorrect") - .HasColumnType("boolean") - .HasColumnName("is_correct"); - - b.Property("LegacyCategoryId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_category_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyQuestionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_question_id"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("SelectedOptions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("selected_options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SessionQuestionId") - .HasColumnType("uuid") - .HasColumnName("session_question_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_answer_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_answer_records_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_answer_records_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_answer_records_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PracticeSessionId", "SessionQuestionId") - .HasDatabaseName("ix_answer_records_tenant_id_practice_session_id_session_questi~"); - - b.HasIndex("TenantId", "UserId", "PracticeSessionId") - .HasDatabaseName("ix_answer_records_tenant_id_user_id_practice_session_id"); - - b.ToTable("answer_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.DashboardDailyStat", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActiveUsers") - .HasColumnType("integer") - .HasColumnName("active_users"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("NewOrders") - .HasColumnType("integer") - .HasColumnName("new_orders"); - - b.Property("NewQuestions") - .HasColumnType("integer") - .HasColumnName("new_questions"); - - b.Property("NewRevenueCents") - .HasColumnType("integer") - .HasColumnName("new_revenue_cents"); - - b.Property("NewUsers") - .HasColumnType("integer") - .HasColumnName("new_users"); - - b.Property("RebuiltAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rebuilt_at"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("StatDate") - .HasColumnType("date") - .HasColumnName("stat_date"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_dashboard_daily_stats"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_dashboard_daily_stats_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_region_id"); - - b.HasIndex("TenantId", "StatDate", "RegionId") - .IsUnique() - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_stat_date_region_id"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "StatDate", "RegionId"), false); - - b.ToTable("dashboard_daily_stats", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ExamDate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("ExamAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("exam_at"); - - b.Property("ExamName") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("exam_name"); - - b.Property("ExamType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("exam_type"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_exam_dates"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_exam_dates_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_exam_dates_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_exam_dates_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId", "ExamAt") - .HasDatabaseName("ix_exam_dates_tenant_id_school_id_exam_at"); - - b.ToTable("exam_dates", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.FavoriteQuestion", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.HasKey("TenantId", "UserId", "QuestionReferenceId") - .HasName("pk_favorite_questions"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_favorite_questions_user_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_favorite_questions_question_owner_tenant_id_question_id"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_favorite_questions_tenant_id_question_reference_id"); - - b.ToTable("favorite_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeAccessEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessMode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("access_mode"); - - b.Property("ConsumedFreeQuota") - .HasColumnType("integer") - .HasColumnName("consumed_free_quota"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementId") - .HasColumnType("uuid") - .HasColumnName("entitlement_id"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("event_type"); - - b.Property("GrantedCount") - .HasColumnType("integer") - .HasColumnName("granted_count"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("Reason") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("reason"); - - b.Property("RequestedCount") - .HasColumnType("integer") - .HasColumnName("requested_count"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_access_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_access_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_access_events_user_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .HasDatabaseName("ix_practice_access_events_tenant_id_practice_session_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_practice_access_events_tenant_id_user_id_created_at"); - - b.ToTable("practice_access_events", null, t => - { - t.HasCheckConstraint("ck_practice_access_events_consumed_free_quota", "consumed_free_quota >= 0"); - - t.HasCheckConstraint("ck_practice_access_events_granted_count", "granted_count >= 0"); - - t.HasCheckConstraint("ck_practice_access_events_requested_count", "requested_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeDailyUsage", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FreeLimit") - .HasColumnType("integer") - .HasColumnName("free_limit"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsageDate") - .ValueGeneratedOnAdd() - .HasColumnType("date") - .HasColumnName("usage_date") - .HasDefaultValueSql("current_date"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_daily_usage"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_daily_usage_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_daily_usage_user_id"); - - b.HasIndex("TenantId", "UserId", "UsageDate") - .HasDatabaseName("ix_practice_daily_usage_tenant_id_user_id_usage_date"); - - b.HasIndex("TenantId", "UserId", "UsageDate", "ScopeType", "ScopeId") - .IsUnique() - .HasDatabaseName("ix_practice_daily_usage_tenant_id_user_id_usage_date_scope_typ~"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "UserId", "UsageDate", "ScopeType", "ScopeId"), false); - - b.ToTable("practice_daily_usage", null, t => - { - t.HasCheckConstraint("ck_practice_daily_usage_free_limit", "free_limit >= 0"); - - t.HasCheckConstraint("ck_practice_daily_usage_used_count", "used_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessEntitlementId") - .HasColumnType("uuid") - .HasColumnName("access_entitlement_id"); - - b.Property("AccessMode") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("free") - .HasColumnName("access_mode"); - - b.Property("AccessSnapshot") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_snapshot") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("BlueprintId") - .HasColumnType("uuid") - .HasColumnName("blueprint_id"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("ConsumedFreeQuota") - .HasColumnType("integer") - .HasColumnName("consumed_free_quota"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("mode"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("StartedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at") - .HasDefaultValueSql("now()"); - - b.Property("TargetId") - .HasColumnType("uuid") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_sessions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_sessions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "UserId", "Id") - .HasName("ak_practice_sessions_tenant_id_user_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_sessions_user_id"); - - b.HasIndex("TenantId", "BlueprintId") - .HasDatabaseName("ix_practice_sessions_tenant_id_blueprint_id"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_sessions_tenant_id_collection_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_practice_sessions_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_practice_sessions_tenant_id_entry_id"); - - b.HasIndex("TenantId", "UserId", "StartedAt") - .HasDatabaseName("ix_practice_sessions_tenant_id_user_id_started_at"); - - b.ToTable("practice_sessions", null, t => - { - t.HasCheckConstraint("ck_practice_sessions_consumed_free_quota", "consumed_free_quota >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionQuestion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Position") - .HasColumnType("integer") - .HasColumnName("position"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("QuestionVersionId") - .HasColumnType("uuid") - .HasColumnName("question_version_id"); - - b.Property("Score") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("score"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_practice_session_questions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_questions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "PracticeSessionId", "Id") - .HasName("ak_practice_session_questions_tenant_id_practice_session_id_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId", "QuestionVersionId") - .HasDatabaseName("ix_practice_session_questions_question_owner_tenant_id_questio~"); - - b.HasIndex("TenantId", "PracticeSessionId", "Position") - .IsUnique() - .HasDatabaseName("ix_practice_session_questions_tenant_id_practice_session_id_po~"); - - b.HasIndex("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_practice_session_questions_tenant_id_question_owner_tenant_~"); - - b.ToTable("practice_session_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReport", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Accuracy") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("accuracy"); - - b.Property("AnsweredCount") - .HasColumnType("integer") - .HasColumnName("answered_count"); - - b.Property("BlueprintId") - .HasColumnType("uuid") - .HasColumnName("blueprint_id"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DurationSeconds") - .HasColumnType("integer") - .HasColumnName("duration_seconds"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("mode"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionResults") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("question_results") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Score") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("score"); - - b.Property("SectionStats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("section_stats") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("SubmittedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("submitted_at") - .HasDefaultValueSql("now()"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalQuestions") - .HasColumnType("integer") - .HasColumnName("total_questions"); - - b.Property("TotalScore") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("total_score"); - - b.Property("UnansweredCount") - .HasColumnType("integer") - .HasColumnName("unanswered_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.Property("WrongQuestionIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("wrong_question_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.HasKey("Id") - .HasName("pk_practice_session_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_reports_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_session_reports_user_id"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_session_reports_tenant_id_collection_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .IsUnique() - .HasDatabaseName("ix_practice_session_reports_tenant_id_practice_session_id"); - - b.HasIndex("TenantId", "BlueprintId", "SubmittedAt") - .HasDatabaseName("ix_practice_session_reports_tenant_id_blueprint_id_submitted_at") - .HasFilter("blueprint_id is not null"); - - b.HasIndex("TenantId", "UserId", "SubmittedAt") - .HasDatabaseName("ix_practice_session_reports_tenant_id_user_id_submitted_at"); - - b.ToTable("practice_session_reports", null, t => - { - t.HasCheckConstraint("ck_practice_session_reports_accuracy", "accuracy >= 0 and accuracy <= 1"); - - t.HasCheckConstraint("ck_practice_session_reports_counts", "total_questions >= 0 and answered_count >= 0 and correct_count >= 0 and wrong_count >= 0 and unanswered_count >= 0"); - - t.HasCheckConstraint("ck_practice_session_reports_duration", "duration_seconds >= 0"); - - t.HasCheckConstraint("ck_practice_session_reports_scores", "score >= 0 and total_score >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReportSection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Accuracy") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("accuracy"); - - b.Property("AnsweredCount") - .HasColumnType("integer") - .HasColumnName("answered_count"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("QuestionType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("question_type"); - - b.Property("ReportId") - .HasColumnType("uuid") - .HasColumnName("report_id"); - - b.Property("Score") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("score"); - - b.Property("SectionKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("section_key"); - - b.Property("SectionName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("section_name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("total_score"); - - b.Property("UnansweredCount") - .HasColumnType("integer") - .HasColumnName("unanswered_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.HasKey("Id") - .HasName("pk_practice_session_report_sections"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_report_sections_tenant_id_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_practice_session~"); - - b.HasIndex("TenantId", "ReportId", "SectionKey") - .IsUnique() - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_report_id_sectio~"); - - b.HasIndex("TenantId", "ReportId", "SortOrder") - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_report_id_sort_o~"); - - b.ToTable("practice_session_report_sections", null, t => - { - t.HasCheckConstraint("ck_practice_session_report_sections_accuracy", "accuracy >= 0 and accuracy <= 1"); - - t.HasCheckConstraint("ck_practice_session_report_sections_counts", "question_count >= 0 and answered_count >= 0 and correct_count >= 0 and wrong_count >= 0 and unanswered_count >= 0"); - - t.HasCheckConstraint("ck_practice_session_report_sections_scores", "score >= 0 and total_score >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RecentPractice", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LastAccessAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_access_at"); - - b.Property("LastPracticeAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_practice_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("practice_type"); - - b.Property("Progress") - .HasColumnType("integer") - .HasColumnName("progress"); - - b.Property("TargetLegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("target_legacy_id"); - - b.Property("TargetName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("target_name"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_recent_practices"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_recent_practices_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_recent_practices_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_recent_practices_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UserId", "LastAccessAt") - .HasDatabaseName("ix_recent_practices_tenant_id_user_id_last_access_at"); - - b.ToTable("recent_practices", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.Report", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attachments") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("attachments") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("Contact") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("contact"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("HandledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("handled_at"); - - b.Property("HandledBy") - .HasColumnType("uuid") - .HasColumnName("handled_by"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("priority"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("Resolution") - .HasColumnType("text") - .HasColumnName("resolution"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_reports_tenant_id_id"); - - b.HasIndex("HandledBy") - .HasDatabaseName("ix_reports_handled_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_reports_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_reports_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "QuestionId", "CreatedAt") - .HasDatabaseName("ix_reports_tenant_id_question_id_created_at") - .HasFilter("question_id is not null"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_reports_tenant_id_status_created_at"); - - b.ToTable("reports", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ReportStatusEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("ReportId") - .HasColumnType("uuid") - .HasColumnName("report_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_report_status_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_report_status_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_report_status_events_actor_user_id"); - - b.HasIndex("TenantId", "ReportId", "CreatedAt") - .HasDatabaseName("ix_report_status_events_tenant_id_report_id_created_at"); - - b.ToTable("report_status_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RevenueDailyStat", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CodeCount") - .HasColumnType("integer") - .HasColumnName("code_count"); - - b.Property("CodeEstimated") - .HasColumnType("integer") - .HasColumnName("code_estimated"); - - b.Property("CodeUsed") - .HasColumnType("integer") - .HasColumnName("code_used"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EstimatedRevenueCents") - .HasColumnType("integer") - .HasColumnName("estimated_revenue_cents"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("OrderCount") - .HasColumnType("integer") - .HasColumnName("order_count"); - - b.Property("RealRevenueCents") - .HasColumnType("integer") - .HasColumnName("real_revenue_cents"); - - b.Property("RebuiltAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rebuilt_at"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("StatDate") - .HasColumnType("date") - .HasColumnName("stat_date"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_revenue_daily_stats"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_revenue_daily_stats_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_region_id"); - - b.HasIndex("TenantId", "StatDate", "RegionId", "SaleType") - .IsUnique() - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_stat_date_region_id_sale_type"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "StatDate", "RegionId", "SaleType"), false); - - b.ToTable("revenue_daily_stats", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.UserScoreEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BalanceAfter") - .HasColumnType("integer") - .HasColumnName("balance_after"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("event_type"); - - b.Property("IdempotencyKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("idempotency_key"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_score_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_score_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_score_events_user_id"); - - b.HasIndex("TenantId", "IdempotencyKey") - .IsUnique() - .HasDatabaseName("ix_user_score_events_tenant_id_idempotency_key"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_user_score_events_tenant_id_user_id_created_at"); - - b.ToTable("user_score_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.WrongQuestion", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("LastWrongAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_wrong_at") - .HasDefaultValueSql("now()"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("WrongCount") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(1) - .HasColumnName("wrong_count"); - - b.HasKey("TenantId", "UserId", "QuestionReferenceId") - .HasName("pk_wrong_questions"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_wrong_questions_user_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_wrong_questions_question_owner_tenant_id_question_id"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_wrong_questions_tenant_id_question_reference_id"); - - b.ToTable("wrong_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Announcement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BackgroundColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("background_color"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Link") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("link"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_announcements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_announcements_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_announcements_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "IsActive", "SortOrder") - .HasDatabaseName("ix_announcements_tenant_id_is_active_sort_order"); - - b.ToTable("announcements", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.AuditLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Action") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("action"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("TargetId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_audit_logs"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_audit_logs_actor_user_id"); - - b.HasIndex("TenantId", "CreatedAt") - .HasDatabaseName("ix_audit_logs_tenant_id_created_at"); - - b.HasIndex("TenantId", "TargetType", "TargetId", "CreatedAt") - .HasDatabaseName("ix_audit_logs_tenant_id_target_type_target_id_created_at"); - - b.ToTable("audit_logs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Area") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("area"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Icon") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("ParentCode") - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("parent_code"); - - b.Property("Path") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("path"); - - b.Property("PermissionCode") - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_backend_menus"); - - b.HasAlternateKey("Code") - .HasName("ak_backend_menus_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_backend_menus_code"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_backend_menus_permission_code"); - - b.HasIndex("Area", "ParentCode", "SortOrder") - .HasDatabaseName("ix_backend_menus_area_parent_code_sort_order"); - - b.ToTable("backend_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendPermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Area") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("area"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Module") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("module"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_backend_permissions"); - - b.HasAlternateKey("Code") - .HasName("ak_backend_permissions_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_backend_permissions_code"); - - b.HasIndex("Area", "Module", "SortOrder") - .HasDatabaseName("ix_backend_permissions_area_module_sort_order"); - - b.ToTable("backend_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("JobType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("job_type"); - - b.Property("LastError") - .HasMaxLength(4000) - .HasColumnType("character varying(4000)") - .HasColumnName("last_error"); - - b.Property("LockExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("lock_expires_at"); - - b.Property("LockedBy") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("locked_by"); - - b.Property("MaxRetries") - .HasColumnType("integer") - .HasColumnName("max_retries"); - - b.Property("OutputAssetId") - .HasColumnType("uuid") - .HasColumnName("output_asset_id"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Result") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("result") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RetryCount") - .HasColumnType("integer") - .HasColumnName("retry_count"); - - b.Property("RunAfter") - .HasColumnType("timestamp with time zone") - .HasColumnName("run_after"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_background_jobs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_background_jobs_tenant_id_id"); - - b.HasIndex("TenantId", "OutputAssetId") - .HasDatabaseName("ix_background_jobs_tenant_id_output_asset_id"); - - b.HasIndex("TenantId", "JobType", "Status", "CreatedAt") - .HasDatabaseName("ix_background_jobs_tenant_id_job_type_status_created_at"); - - b.HasIndex("TenantId", "Status", "RunAfter", "CreatedAt") - .HasDatabaseName("ix_background_jobs_tenant_id_status_run_after_created_at"); - - b.ToTable("background_jobs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Badge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("ConditionExtra") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("condition_extra") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ConditionField") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("condition_field"); - - b.Property("ConditionOperator") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("condition_operator"); - - b.Property("ConditionValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)") - .HasColumnName("condition_value"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IconUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon_url"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Level") - .HasColumnType("integer") - .HasColumnName("level"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnlockType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("unlock_type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_badges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_badges_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_badges_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "Category", "IsActive", "SortOrder") - .HasDatabaseName("ix_badges_tenant_id_category_is_active_sort_order"); - - b.ToTable("badges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Banner", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BackgroundColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("background_color"); - - b.Property("BorderColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("border_color"); - - b.Property("ButtonLink") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("button_link"); - - b.Property("ButtonText") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("button_text"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Subtitle") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("subtitle"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_banners"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_banners_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_banners_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_banners_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("banners", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Faq", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Answer") - .HasColumnType("text") - .HasColumnName("answer"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Question") - .HasColumnType("text") - .HasColumnName("question"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_faqs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_faqs_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_faqs_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_faqs_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("faqs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_backend_roles"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_backend_roles_code"); - - b.HasIndex("Status", "Code") - .HasDatabaseName("ix_platform_backend_roles_status_code"); - - b.ToTable("platform_backend_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("MenuCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("menu_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_role_menus"); - - b.HasIndex("MenuCode") - .HasDatabaseName("ix_platform_backend_role_menus_menu_code"); - - b.HasIndex("RoleId", "MenuCode") - .IsUnique() - .HasDatabaseName("ix_platform_backend_role_menus_role_id_menu_code"); - - b.ToTable("platform_backend_role_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("PermissionCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_role_permissions"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_platform_backend_role_permissions_permission_code"); - - b.HasIndex("RoleId", "PermissionCode") - .IsUnique() - .HasDatabaseName("ix_platform_backend_role_permissions_role_id_permission_code"); - - b.ToTable("platform_backend_role_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_user_roles"); - - b.HasIndex("RoleId") - .HasDatabaseName("ix_platform_backend_user_roles_role_id"); - - b.HasIndex("UserId", "RoleId") - .IsUnique() - .HasDatabaseName("ix_platform_backend_user_roles_user_id_role_id"); - - b.ToTable("platform_backend_user_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DataScope") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("data_scope") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_roles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_roles_tenant_id_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_roles_tenant_id_code"); - - b.HasIndex("TenantId", "Status", "Code") - .HasDatabaseName("ix_tenant_backend_roles_tenant_id_status_code"); - - b.ToTable("tenant_backend_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("MenuCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("menu_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_role_menus"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_role_menus_tenant_id_id"); - - b.HasIndex("MenuCode") - .HasDatabaseName("ix_tenant_backend_role_menus_menu_code"); - - b.HasIndex("TenantId", "RoleId", "MenuCode") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_role_menus_tenant_id_role_id_menu_code"); - - b.ToTable("tenant_backend_role_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("PermissionCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_role_permissions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_role_permissions_tenant_id_id"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_tenant_backend_role_permissions_permission_code"); - - b.HasIndex("TenantId", "RoleId", "PermissionCode") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_role_permissions_tenant_id_role_id_permissio~"); - - b.ToTable("tenant_backend_role_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_user_roles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_user_roles_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_backend_user_roles_user_id"); - - b.HasIndex("TenantId", "RoleId") - .HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_role_id"); - - b.HasIndex("TenantId", "UserId", "RoleId") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_user_id_role_id"); - - b.ToTable("tenant_backend_user_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActionLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("action_label"); - - b.Property("ActionPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("action_path"); - - b.Property("AdoptionId") - .HasColumnType("uuid") - .HasColumnName("adoption_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DedupeKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("dedupe_key"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NotificationType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("notification_type"); - - b.Property("ReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("read_at"); - - b.Property("ReadBy") - .HasColumnType("uuid") - .HasColumnName("read_by"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("SourceQuestionBankId") - .HasColumnType("uuid") - .HasColumnName("source_question_bank_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_content_notifications"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_content_notifications_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_content_notifications_created_by"); - - b.HasIndex("ReadBy") - .HasDatabaseName("ix_tenant_content_notifications_read_by"); - - b.HasIndex("TenantId", "SourceQuestionBankId") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_source_question_bank~"); - - b.HasIndex("TenantId", "NotificationType", "DedupeKey") - .IsUnique() - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_notification_type_de~") - .HasFilter("dedupe_key is not null"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_status_created_at"); - - b.HasIndex("TenantId", "AdoptionId", "NotificationType", "Status") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_adoption_id_notifica~"); - - b.ToTable("tenant_content_notifications", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActivePublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("active_public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ActiveTemplateCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("active_template_code"); - - b.Property("ActiveTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("active_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DraftPublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftTemplateCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("draft_template_code"); - - b.Property("DraftTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftUpdatedBy") - .HasColumnType("uuid") - .HasColumnName("draft_updated_by"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublishedBy") - .HasColumnType("uuid") - .HasColumnName("published_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_theme_configs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_theme_configs_tenant_id_id"); - - b.HasIndex("ActiveTemplateCode") - .HasDatabaseName("ix_tenant_theme_configs_active_template_code"); - - b.HasIndex("DraftTemplateCode") - .HasDatabaseName("ix_tenant_theme_configs_draft_template_code"); - - b.HasIndex("DraftUpdatedBy") - .HasDatabaseName("ix_tenant_theme_configs_draft_updated_by"); - - b.HasIndex("PublishedBy") - .HasDatabaseName("ix_tenant_theme_configs_published_by"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_theme_configs_tenant_id"); - - b.ToTable("tenant_theme_configs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("PreviewImageUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_image_url"); - - b.Property("PublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Theme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_theme_templates"); - - b.HasAlternateKey("Code") - .HasName("ak_tenant_theme_templates_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_tenant_theme_templates_code"); - - b.HasIndex("Status", "SortOrder", "Code") - .HasDatabaseName("ix_tenant_theme_templates_status_sort_order_code"); - - b.ToTable("tenant_theme_templates", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserBadge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BadgeId") - .HasColumnType("uuid") - .HasColumnName("badge_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("GrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("granted_at"); - - b.Property("GrantedBy") - .HasColumnType("uuid") - .HasColumnName("granted_by"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_badges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_badges_tenant_id_id"); - - b.HasIndex("GrantedBy") - .HasDatabaseName("ix_user_badges_granted_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_badges_user_id"); - - b.HasIndex("TenantId", "BadgeId") - .HasDatabaseName("ix_user_badges_tenant_id_badge_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_user_badges_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UserId", "BadgeId") - .IsUnique() - .HasDatabaseName("ix_user_badges_tenant_id_user_id_badge_id"); - - b.ToTable("user_badges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserNotification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActionLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("action_label"); - - b.Property("ActionPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("action_path"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DedupeKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("dedupe_key"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NotificationType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("notification_type"); - - b.Property("ReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("read_at"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_notifications"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_notifications_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_user_notifications_created_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_notifications_user_id"); - - b.HasIndex("TenantId", "SourceType", "SourceId") - .HasDatabaseName("ix_user_notifications_tenant_id_source_type_source_id"); - - b.HasIndex("TenantId", "UserId", "NotificationType", "DedupeKey") - .IsUnique() - .HasDatabaseName("ix_user_notifications_tenant_id_user_id_notification_type_dedu~") - .HasFilter("dedupe_key is not null"); - - b.HasIndex("TenantId", "UserId", "Status", "CreatedAt") - .HasDatabaseName("ix_user_notifications_tenant_id_user_id_status_created_at"); - - b.ToTable("user_notifications", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AcknowledgedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("acknowledged_at"); - - b.Property("AcknowledgedBy") - .HasColumnType("uuid") - .HasColumnName("acknowledged_by"); - - b.Property("Action") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("action"); - - b.Property("AuditLogId") - .HasColumnType("uuid") - .HasColumnName("audit_log_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FirstSeenAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("first_seen_at") - .HasDefaultValueSql("now()"); - - b.Property("LastSeenAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_seen_at") - .HasDefaultValueSql("now()"); - - b.Property("ResolutionNote") - .HasColumnType("text") - .HasColumnName("resolution_note"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("ResolvedBy") - .HasColumnType("uuid") - .HasColumnName("resolved_by"); - - b.Property("RuleId") - .HasColumnType("uuid") - .HasColumnName("rule_id"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .HasColumnType("text") - .HasColumnName("summary"); - - b.Property("TargetId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_audit_alerts"); - - b.HasIndex("AcknowledgedBy") - .HasDatabaseName("ix_platform_audit_alerts_acknowledged_by"); - - b.HasIndex("AuditLogId") - .HasDatabaseName("ix_platform_audit_alerts_audit_log_id"); - - b.HasIndex("ResolvedBy") - .HasDatabaseName("ix_platform_audit_alerts_resolved_by"); - - b.HasIndex("RuleId", "AuditLogId") - .IsUnique() - .HasDatabaseName("ix_platform_audit_alerts_rule_id_audit_log_id"); - - b.HasIndex("Status", "Severity", "CreatedAt") - .HasDatabaseName("ix_platform_audit_alerts_status_severity_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_audit_alerts_tenant_id_status_created_at"); - - b.ToTable("platform_audit_alerts", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlertRule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.PrimitiveCollection("ActionPatterns") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("action_patterns") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("Conditions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("conditions") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.PrimitiveCollection("TargetTypes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("target_types") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_audit_alert_rules"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_audit_alert_rules_code"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_platform_audit_alert_rules_tenant_id"); - - b.HasIndex("Enabled", "Severity", "Code") - .HasDatabaseName("ix_platform_audit_alert_rules_enabled_severity_code"); - - b.ToTable("platform_audit_alert_rules", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationChannel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ChannelCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel_code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MinReminderLevel") - .HasColumnType("integer") - .HasColumnName("min_reminder_level"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("provider"); - - b.PrimitiveCollection("ReminderChannels") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("reminder_channels") - .HasDefaultValueSql("array['internal']::text[]"); - - b.PrimitiveCollection("ReminderTypes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("reminder_types") - .HasDefaultValueSql("array['overdue', 'final_notice']::text[]"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.PrimitiveCollection("TenantIds") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("uuid[]") - .HasColumnName("tenant_ids") - .HasDefaultValueSql("'{}'::uuid[]"); - - b.Property("TimeoutSeconds") - .HasColumnType("integer") - .HasColumnName("timeout_seconds"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WebhookUrl") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("webhook_url"); - - b.HasKey("Id") - .HasName("pk_platform_dunning_notification_channels"); - - b.HasIndex("ChannelCode") - .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_channels_channel_code"); - - b.HasIndex("Enabled", "MinReminderLevel", "ChannelCode") - .HasDatabaseName("ix_platform_dunning_notification_channels_enabled_min_reminder~"); - - b.ToTable("platform_dunning_notification_channels", null, t => - { - t.HasCheckConstraint("ck_platform_dunning_channels_level", "min_reminder_level between 1 and 20"); - - t.HasCheckConstraint("ck_platform_dunning_channels_timeout", "timeout_seconds between 1 and 60"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("ChannelId") - .HasColumnType("uuid") - .HasColumnName("channel_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("LastAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_attempt_at"); - - b.Property("LastError") - .HasColumnType("text") - .HasColumnName("last_error"); - - b.Property("LastHttpCode") - .HasColumnType("integer") - .HasColumnName("last_http_code"); - - b.Property("LastResponseSummary") - .HasColumnType("text") - .HasColumnName("last_response_summary"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NextAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_attempt_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("provider"); - - b.Property("ReminderId") - .HasColumnType("uuid") - .HasColumnName("reminder_id"); - - b.Property("RequestPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("request_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ScheduledAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("scheduled_at") - .HasDefaultValueSql("now()"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_dunning_notification_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_platform_dunning_notification_events_tenant_id_id"); - - b.HasIndex("ChannelId") - .HasDatabaseName("ix_platform_dunning_notification_events_channel_id"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_invoice_id"); - - b.HasIndex("TenantId", "ReminderId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_reminder_id"); - - b.HasIndex("InvoiceId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_invoice_id_status_crea~"); - - b.HasIndex("ReminderId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_reminder_id_status_cre~"); - - b.HasIndex("TenantId", "ChannelId", "ReminderId") - .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_channel_id_r~"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_status_creat~"); - - b.HasIndex("Status", "NextAttemptAt", "ScheduledAt", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_status_next_attempt_at~"); - - b.ToTable("platform_dunning_notification_events", null, t => - { - t.HasCheckConstraint("ck_platform_dunning_events_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformSaasPlan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BaseAmountCents") - .HasColumnType("integer") - .HasColumnName("base_amount_cents"); - - b.Property("BillingCycle") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("billing_cycle"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("FeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("IncludedQuotas") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("included_quotas") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OveragePrices") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("overage_prices") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_saas_plans"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_saas_plans_code"); - - b.HasIndex("Status", "SortOrder") - .HasDatabaseName("ix_platform_saas_plans_status_sort_order"); - - b.ToTable("platform_saas_plans", null, t => - { - t.HasCheckConstraint("ck_platform_saas_plans_amount", "base_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("BankAccountMasked") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("bank_account_masked"); - - b.Property("BankName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("bank_name"); - - b.Property("BillingAddress") - .HasColumnType("text") - .HasColumnName("billing_address"); - - b.Property("BillingName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("billing_name"); - - b.Property("ContactEmail") - .HasMaxLength(300) - .HasColumnType("citext") - .HasColumnName("contact_email"); - - b.Property("ContactName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("contact_name"); - - b.Property("ContactPhone") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("contact_phone"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceTitle") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("invoice_title"); - - b.Property("InvoiceType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TaxId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("tax_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_billing_profiles"); - - b.ToTable("tenant_billing_profiles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BalanceCents") - .HasColumnType("integer") - .HasColumnName("balance_cents"); - - b.Property("BillingPeriodEnd") - .HasColumnType("date") - .HasColumnName("billing_period_end"); - - b.Property("BillingPeriodStart") - .HasColumnType("date") - .HasColumnName("billing_period_start"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("DiscountCents") - .HasColumnType("integer") - .HasColumnName("discount_cents"); - - b.Property("DueDate") - .HasColumnType("date") - .HasColumnName("due_date"); - - b.Property("InvoiceNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("invoice_no"); - - b.Property("InvoiceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - - b.Property("IssuedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("issued_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaidCents") - .HasColumnType("integer") - .HasColumnName("paid_cents"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubtotalCents") - .HasColumnType("integer") - .HasColumnName("subtotal_cents"); - - b.Property("TaxCents") - .HasColumnType("integer") - .HasColumnName("tax_cents"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCents") - .HasColumnType("integer") - .HasColumnName("total_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoices"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoices_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoices_created_by"); - - b.HasIndex("TenantId", "InvoiceNo") - .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_invoice_no"); - - b.HasIndex("TenantId", "BillingPeriodStart", "BillingPeriodEnd") - .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_billing_period_start_billing_peri~") - .HasFilter("invoice_type = 'usage_overage' and status <> 'void' and metadata->>'source' = 'usage_overage_auto'"); - - b.HasIndex("TenantId", "Status", "DueDate") - .HasDatabaseName("ix_tenant_invoices_tenant_id_status_due_date"); - - b.ToTable("tenant_invoices", null, t => - { - t.HasCheckConstraint("ck_tenant_invoices_amounts", "subtotal_cents >= 0 and discount_cents >= 0 and tax_cents >= 0 and total_cents >= 0 and paid_cents >= 0 and balance_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoices_period", "billing_period_end is null or billing_period_start is null or billing_period_end >= billing_period_start"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("description"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("ItemRefId") - .HasColumnType("uuid") - .HasColumnName("item_ref_id"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Quantity") - .HasPrecision(12, 2) - .HasColumnType("numeric(12,2)") - .HasColumnName("quantity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitAmountCents") - .HasColumnType("integer") - .HasColumnName("unit_amount_cents"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_items_tenant_id_id"); - - b.HasIndex("InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_invoice_id"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_tenant_id_invoice_id"); - - b.ToTable("tenant_invoice_items", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_items_amounts", "unit_amount_cents >= 0 and amount_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoice_items_quantity", "quantity > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("Method") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("method"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_no"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ReceivedBy") - .HasColumnType("uuid") - .HasColumnName("received_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_payments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_payments_tenant_id_id"); - - b.HasIndex("ReceivedBy") - .HasDatabaseName("ix_tenant_invoice_payments_received_by"); - - b.HasIndex("InvoiceId", "Status") - .HasDatabaseName("ix_tenant_invoice_payments_invoice_id_status"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_invoice_id"); - - b.HasIndex("TenantId", "PaymentNo") - .IsUnique() - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_payment_no"); - - b.ToTable("tenant_invoice_payments", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_payments_amount", "amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AcknowledgedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("acknowledged_at"); - - b.Property("BalanceCentsSnapshot") - .HasColumnType("integer") - .HasColumnName("balance_cents_snapshot"); - - b.Property("Channel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("channel"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DueDate") - .HasColumnType("date") - .HasColumnName("due_date"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("Message") - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ReminderDate") - .HasColumnType("date") - .HasColumnName("reminder_date"); - - b.Property("ReminderLevel") - .HasColumnType("integer") - .HasColumnName("reminder_level"); - - b.Property("ReminderType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("reminder_type"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_reminders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_reminders_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoice_reminders_created_by"); - - b.HasIndex("InvoiceId", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_invoice_id_reminder_date"); - - b.HasIndex("TenantId", "Status", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_status_reminder_date"); - - b.HasIndex("TenantId", "InvoiceId", "ReminderType", "Channel", "ReminderDate") - .IsUnique() - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_invoice_id_reminder_type~"); - - b.ToTable("tenant_invoice_reminders", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_reminders_balance", "balance_cents_snapshot >= 0"); - - t.HasCheckConstraint("ck_tenant_invoice_reminders_level", "reminder_level between 1 and 20"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CurrentVersionId") - .HasColumnType("uuid") - .HasColumnName("current_version_id"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("ExamMarkers") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("exam_markers") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("HasVideoExplanation") - .HasColumnType("boolean") - .HasColumnName("has_video_explanation"); - - b.Property("LegacyCategoryId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_category_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyNodeId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_node_id"); - - b.Property("LegacySubjectId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_subject_id"); - - b.Property("MediaUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("media_url"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("PrimaryCollectionId") - .HasColumnType("uuid") - .HasColumnName("primary_collection_id"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("type"); - - b.Property("TypeLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("type_label"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_questions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_questions_tenant_id_id"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_questions_tenant_id_category_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_questions_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_questions_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_questions_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_questions_tenant_id_node_id"); - - b.HasIndex("TenantId", "PrimaryCollectionId") - .HasDatabaseName("ix_questions_tenant_id_primary_collection_id"); - - b.HasIndex("TenantId", "QuestionBankId") - .HasDatabaseName("ix_questions_tenant_id_question_bank_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_questions_tenant_id_subject_id"); - - b.HasIndex("TenantId", "Id", "CurrentVersionId") - .HasDatabaseName("ix_questions_tenant_id_id_current_version_id"); - - b.ToTable("questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionBank", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_banks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_banks_tenant_id_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_question_banks_tenant_id_region_id"); - - b.ToTable("question_banks", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AnswerText") - .HasColumnType("text") - .HasColumnName("answer_text"); - - b.Property("CodeLang") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("code_lang"); - - b.Property("CodeTemplate") - .HasColumnType("text") - .HasColumnName("code_template"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CorrectOptionIndex") - .HasColumnType("integer") - .HasColumnName("correct_option_index"); - - b.Property("CorrectOptionIndices") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("correct_option_indices") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Explanation") - .HasColumnType("text") - .HasColumnName("explanation"); - - b.Property("Options") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubQuestions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("sub_questions") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("VersionNo") - .HasColumnType("integer") - .HasColumnName("version_no"); - - b.HasKey("Id") - .HasName("pk_question_versions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_versions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "QuestionId", "Id") - .HasName("ak_question_versions_tenant_id_question_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_question_versions_created_by"); - - b.HasIndex("TenantId", "QuestionId", "VersionNo") - .IsUnique() - .HasDatabaseName("ix_question_versions_tenant_id_question_id_version_no"); - - b.ToTable("question_versions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ConsumedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("consumed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("purpose"); - - b.Property("Realm") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("realm"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("security_stamp"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("token_hash"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_challenges"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_auth_challenges_tenant_id"); - - b.HasIndex("TokenHash") - .IsUnique() - .HasDatabaseName("ix_auth_challenges_token_hash"); - - b.HasIndex("UserId", "Purpose", "ExpiresAt") - .HasDatabaseName("ix_auth_challenges_user_id_purpose_expires_at"); - - b.ToTable("auth_challenges", null, t => - { - t.HasCheckConstraint("ck_auth_challenges_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FailureCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("failure_code"); - - b.Property("Identifier") - .HasMaxLength(320) - .HasColumnType("character varying(320)") - .HasColumnName("identifier"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Result") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("result"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_login_events"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_auth_login_events_user_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_auth_login_events_tenant_id_user_id_created_at"); - - b.ToTable("auth_login_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ParentSessionId") - .HasColumnType("uuid") - .HasColumnName("parent_session_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Realm") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("realm"); - - b.Property("ReplacedBySessionId") - .HasColumnType("uuid") - .HasColumnName("replaced_by_session_id"); - - b.Property("RevokedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("revoked_at"); - - b.Property("RevokedReason") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("revoked_reason"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("security_stamp"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TokenFamilyId") - .HasColumnType("uuid") - .HasColumnName("token_family_id"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("token_hash"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_sessions"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_auth_sessions_tenant_id"); - - b.HasIndex("TokenHash") - .IsUnique() - .HasDatabaseName("ix_auth_sessions_token_hash") - .HasAnnotation("Tiku:GlobalUnique", true); - - b.HasIndex("UserId") - .HasDatabaseName("ix_auth_sessions_user_id"); - - b.HasIndex("TokenFamilyId", "RevokedAt") - .HasDatabaseName("ix_auth_sessions_token_family_id_revoked_at"); - - b.HasIndex("Realm", "TenantId", "UserId", "ExpiresAt") - .HasDatabaseName("ix_auth_sessions_realm_tenant_id_user_id_expires_at") - .HasFilter("revoked_at is null"); - - b.ToTable("auth_sessions", null, t => - { - t.HasCheckConstraint("ck_auth_sessions_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsSendRateLimit", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Dimension") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("dimension"); - - b.Property("ScopeHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("scope_hash"); - - b.Property("BucketStart") - .HasColumnType("timestamp with time zone") - .HasColumnName("bucket_start"); - - b.Property("RequestCount") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(0) - .HasColumnName("request_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId", "Dimension", "ScopeHash", "BucketStart") - .HasName("pk_sms_send_rate_limits"); - - b.HasIndex("UpdatedAt") - .HasDatabaseName("ix_sms_send_rate_limits_updated_at"); - - b.ToTable("sms_send_rate_limits", null, t => - { - t.HasCheckConstraint("ck_sms_send_rate_limits_request_count", "request_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsVerificationCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("CodeHash") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("code_hash"); - - b.Property("ConsumedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("consumed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Phone") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("purpose"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_sms_verification_codes"); - - b.HasIndex("TenantId", "Phone", "Purpose") - .IsUnique() - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose") - .HasFilter("consumed_at is null and status in ('pending', 'sent')"); - - b.HasIndex("TenantId", "Phone", "Purpose", "CreatedAt") - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose_created_at") - .HasFilter("consumed_at is null and status in ('pending', 'sent')"); - - b.HasIndex("TenantId", "Phone", "Purpose", "ExpiresAt") - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose_expires_at"); - - b.ToTable("sms_verification_codes", null, t => - { - t.HasCheckConstraint("ck_sms_verification_codes_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.Tenant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BillingStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("billing_status"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegalName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("legal_name"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("mode"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OwnerUserId") - .HasColumnType("uuid") - .HasColumnName("owner_user_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("slug"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenants"); - - b.HasIndex("LegacyId") - .IsUnique() - .HasDatabaseName("ix_tenants_legacy_id"); - - b.HasIndex("Mode") - .IsUnique() - .HasDatabaseName("ix_tenants_mode") - .HasFilter("mode = 'platform_owned'"); - - b.HasIndex("OwnerUserId") - .HasDatabaseName("ix_tenants_owner_user_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_tenants_slug"); - - b.ToTable("tenants", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantBranding", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("BrandName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("brand_name"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FaviconUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("favicon_url"); - - b.Property("LogoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("logo_url"); - - b.Property("OrganizationName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("organization_name"); - - b.Property("PublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ServiceAccountName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("service_account_name"); - - b.Property("ServiceWechat") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("service_wechat"); - - b.Property("ShortName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("short_name"); - - b.Property("Slogan") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("slogan"); - - b.Property("Theme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_branding"); - - b.ToTable("tenant_branding", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClass", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_classes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_classes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_classes_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_classes_updated_by"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_tenant_classes_tenant_id_code") - .HasFilter("code is not null and code <> ''"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_tenant_classes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_tenant_classes_tenant_id_region_id") - .HasFilter("region_id is not null"); - - b.HasIndex("TenantId", "Status", "SortOrder", "CreatedAt") - .HasDatabaseName("ix_tenant_classes_tenant_id_status_sort_order_created_at"); - - b.ToTable("tenant_classes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClassMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClassId") - .HasColumnType("uuid") - .HasColumnName("class_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("JoinedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at") - .HasDefaultValueSql("now()"); - - b.Property("LeftAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("left_at"); - - b.Property("MemberType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("member_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_class_members"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_class_members_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_class_members_user_id"); - - b.HasIndex("TenantId", "ClassId", "Status", "MemberType") - .HasDatabaseName("ix_tenant_class_members_tenant_id_class_id_status_member_type"); - - b.HasIndex("TenantId", "ClassId", "UserId", "MemberType") - .IsUnique() - .HasDatabaseName("ix_tenant_class_members_tenant_id_class_id_user_id_member_type"); - - b.HasIndex("TenantId", "UserId", "Status", "MemberType") - .HasDatabaseName("ix_tenant_class_members_tenant_id_user_id_status_member_type"); - - b.ToTable("tenant_class_members", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantDomain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DnsVerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("dns_verified_at"); - - b.Property("DomainType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("domain_type"); - - b.Property("Host") - .IsRequired() - .HasMaxLength(253) - .HasColumnType("citext") - .HasColumnName("host"); - - b.Property("IsPrimary") - .HasColumnType("boolean") - .HasColumnName("is_primary"); - - b.Property("LastCheckedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_checked_at"); - - b.Property("LastFailureReason") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("last_failure_reason"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TlsReadyAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("tls_ready_at"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VerificationToken") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("verification_token"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_tenant_domains"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_domains_tenant_id_id"); - - b.HasIndex("Host") - .IsUnique() - .HasDatabaseName("ix_tenant_domains_host") - .HasAnnotation("Tiku:GlobalUnique", true); - - b.HasIndex("TenantId", "IsPrimary") - .IsUnique() - .HasDatabaseName("ix_tenant_domains_tenant_id_is_primary") - .HasFilter("is_primary"); - - b.ToTable("tenant_domains", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantExternalProvider", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Capability") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("capability"); - - b.Property("ConfigPublic") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config_public") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("display_name"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_external_providers"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_external_providers_tenant_id_id"); - - b.HasIndex("TenantId", "Capability", "Provider") - .IsUnique() - .HasDatabaseName("ix_tenant_external_providers_tenant_id_capability_provider"); - - b.HasIndex("TenantId", "Capability", "Status", "Priority") - .HasDatabaseName("ix_tenant_external_providers_tenant_id_capability_status_prior~"); - - b.ToTable("tenant_external_providers", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantFrontendConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ConfigVersion") - .IsConcurrencyToken() - .HasColumnType("integer") - .HasColumnName("config_version"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DraftBranding") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_branding") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftFeatures") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_features") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftHomeModules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_home_modules") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("DraftNavigation") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_navigation") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("DraftTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublishedBranding") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_branding") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedFeatures") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_features") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedHomeModules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_home_modules") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("PublishedNavigation") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_navigation") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("PublishedTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SchemaVersion") - .HasColumnType("integer") - .HasColumnName("schema_version"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_frontend_configs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_frontend_configs_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_frontend_configs_tenant_id"); - - b.ToTable("tenant_frontend_configs", null, t => - { - t.HasCheckConstraint("ck_tenant_frontend_configs_config_version", "config_version > 0"); - - t.HasCheckConstraint("ck_tenant_frontend_configs_schema_version", "schema_version > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantMembership", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyRole") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("legacy_role"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("role"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_memberships"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_memberships_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_memberships_user_id"); - - b.HasIndex("TenantId", "UserId", "Role") - .IsUnique() - .HasDatabaseName("ix_tenant_memberships_tenant_id_user_id_role"); - - b.ToTable("tenant_memberships", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EncryptedPayload") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encrypted_payload"); - - b.Property("EncryptionKeyId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("encryption_key_id"); - - b.Property("EncryptionNonce") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encryption_nonce"); - - b.Property("EncryptionTag") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encryption_tag"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)") - .HasColumnName("purpose"); - - b.Property("RotatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rotated_at"); - - b.Property("SecretKey") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)") - .HasColumnName("secret_key"); - - b.Property("SecretRef") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_secrets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_secrets_tenant_id_id"); - - b.HasIndex("TenantId", "SecretRef") - .IsUnique() - .HasDatabaseName("ix_tenant_secrets_tenant_id_secret_ref"); - - b.HasIndex("TenantId", "Purpose", "Provider", "SecretKey") - .IsUnique() - .HasDatabaseName("ix_tenant_secrets_tenant_id_purpose_provider_secret_key"); - - b.HasIndex("TenantId", "Purpose", "Provider", "Status") - .HasDatabaseName("ix_tenant_secrets_tenant_id_purpose_provider_status"); - - b.ToTable("tenant_secrets", null, t => - { - t.HasCheckConstraint("ck_tenant_secrets_encryption_envelope", "octet_length(encrypted_payload) > 0 and octet_length(encryption_nonce) = 12 and octet_length(encryption_tag) = 16 and encryption_key_id <> ''"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSettings", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("AdminFeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("admin_feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublicConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_settings"); - - b.ToTable("tenant_settings", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentFollowup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignedToUserId") - .HasColumnType("uuid") - .HasColumnName("assigned_to_user_id"); - - b.Property("ClassId") - .HasColumnType("uuid") - .HasColumnName("class_id"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CompletedBy") - .HasColumnType("uuid") - .HasColumnName("completed_by"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("DueAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("due_at"); - - b.Property("FollowupType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("followup_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("priority"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_student_followups"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_student_followups_tenant_id_id"); - - b.HasIndex("AssignedToUserId") - .HasDatabaseName("ix_tenant_student_followups_assigned_to_user_id"); - - b.HasIndex("CompletedBy") - .HasDatabaseName("ix_tenant_student_followups_completed_by"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_tenant_student_followups_student_user_id"); - - b.HasIndex("TenantId", "AssignedToUserId", "Status", "DueAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_assigned_to_user_id_stat~") - .HasFilter("assigned_to_user_id is not null"); - - b.HasIndex("TenantId", "ClassId", "Status", "DueAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_class_id_status_due_at") - .HasFilter("class_id is not null"); - - b.HasIndex("TenantId", "StudentUserId", "Status", "DueAt", "CreatedAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_student_user_id_status_d~"); - - b.ToTable("tenant_student_followups", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentNote", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Content") - .IsRequired() - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("IsPinned") - .HasColumnType("boolean") - .HasColumnName("is_pinned"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NoteType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("note_type"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_tenant_student_notes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_student_notes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_student_notes_created_by"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_tenant_student_notes_student_user_id"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_student_notes_updated_by"); - - b.HasIndex("TenantId", "CreatedBy", "CreatedAt") - .HasDatabaseName("ix_tenant_student_notes_tenant_id_created_by_created_at") - .HasFilter("created_by is not null"); - - b.HasIndex("TenantId", "StudentUserId", "IsPinned", "CreatedAt") - .HasDatabaseName("ix_tenant_student_notes_tenant_id_student_user_id_is_pinned_cr~"); - - b.ToTable("tenant_student_notes", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_claims_users_user_id"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_logins_users_user_id"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_tokens_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Category", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_categories_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_categories_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_categories_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Major", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_majors_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_majors_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_majors_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ModuleNode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_module_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_module_nodes_tenant_id_parent_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.QuestionTaxonomyAssignment", b => - { - b.HasOne("Tiku.Domain.Catalog.TaxonomyNode", null) - .WithMany() - .HasForeignKey("TaxonomyOwnerTenantId", "TaxonomyNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_question_taxonomy_assignments_taxonomy_nodes_taxonomy_owner~"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_taxonomy_assignments_questions_tenant_id_question_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Region", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_regions_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.RegionModule", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_region_modules_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_region_modules_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.School", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_schools_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_schools_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_schools_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineField", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_scoreline_fields_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_fields_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_scoreline_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Subject", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subjects_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.TaxonomyNode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_taxonomy_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.TaxonomyNode", null) - .WithMany() - .HasForeignKey("ParentOwnerTenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_taxonomy_nodes_taxonomy_nodes_parent_owner_tenant_id_parent~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.ActivationCode", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AgentUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_users_agent_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activation_codes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UsedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_users_used_by"); - - b.HasOne("Tiku.Domain.Commerce.CodeBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_code_batches_tenant_id_batch_id"); - - b.HasOne("Tiku.Domain.Commerce.CouponRedemption", null) - .WithMany() - .HasForeignKey("TenantId", "CouponRedemptionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_coupon_redemptions_tenant_id_coupon_redemp~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "UsedRegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_regions_tenant_id_used_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CodeBatch", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_code_batches_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucher", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_adjustment_vouchers_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_reconciliation_batche~"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationIssue", null) - .WithMany() - .HasForeignKey("TenantId", "IssueId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_reconciliation_issues~"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_reconciliation_items_~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_refund_requests_tenan~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucherEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_voucher_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_adjustment_voucher_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceAdjustmentVoucher", null) - .WithMany() - .HasForeignKey("TenantId", "VoucherId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_adjustment_voucher_events_commerce_adjustment_vouc~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationBatch", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_batches_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_batches_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssue", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedTo") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_assigned_to"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ResolvedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_resolved_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_reconciliation_batc~"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_reconciliation_item~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_refund_requests_ten~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssueEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issue_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issue_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationIssue", null) - .WithMany() - .HasForeignKey("TenantId", "IssueId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issue_events_commerce_reconciliatio~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_items_commerce_reconciliation_batch~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_commerce_refund_requests_tena~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_events_commerce_refund_requests_tenant_id_r~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundRequest", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ProcessedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_processed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("RequestedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_requested_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_requests_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_requests_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_refund_requests_payments_tenant_id_payment_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Coupon", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_coupons_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_coupons_svip_plans_tenant_id_plan_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CouponRedemption", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_coupon_redemptions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.Coupon", null) - .WithMany() - .HasForeignKey("TenantId", "CouponId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_coupons_tenant_id_coupon_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_svip_plans_tenant_id_plan_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Entitlement", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("RevokedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_entitlements_users_revoked_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entitlements_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entitlements_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Order", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_orders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_orders_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_orders_svip_plans_tenant_id_plan_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_orders_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.OrderItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_order_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_order_items_orders_tenant_id_order_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Payment", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payments_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payments_orders_tenant_id_order_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PaymentEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_payment_events_payments_tenant_id_payment_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityClaim", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.PointActivityTask", null) - .WithMany() - .HasForeignKey("TenantId", "TaskId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_point_activity_tasks_tenant_id_task_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityTask", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_tasks_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_point_exchange_items_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeOrder", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.PointExchangeItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_point_exchange_items_tenant_id_item_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Product", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_products_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_products_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.SvipPlan", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_svip_plans_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_svip_plans_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_subscriptions_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_usage_records_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_ai_recommendation_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_ai_recommendation_reports_users_user_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_ai_recommendation_reports_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AppAsset", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_app_assets_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAsset", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_assets_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_updated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("VerifiedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_verified_by"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetAccessEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_asset_access_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_asset_access_events_users_user_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_asset_access_events_content_assets_tenant_id_asset_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetSecurityScanEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_asset_security_scan_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_asset_security_scan_events_content_assets_tenant_id~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentEntry", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_entries_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_entries_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_entries_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_entries_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportIssue", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_content_import_issues_content_import_items_tenant_id_item_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportJob", null) - .WithMany() - .HasForeignKey("TenantId", "JobId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_issues_content_import_jobs_tenant_id_job_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportJob", null) - .WithMany() - .HasForeignKey("TenantId", "JobId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_items_content_import_jobs_tenant_id_job_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportJob", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_import_jobs_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_jobs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "TargetCategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_categories_tenant_id_target_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "TargetContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_content_nodes_tenant_id_target_content_~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "TargetQuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_question_banks_tenant_id_target_questio~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "TargetRegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_regions_tenant_id_target_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "TargetSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_subjects_tenant_id_target_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentNode", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_nodes_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_nodes_users_updated_by"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_nodes_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_content_nodes_content_nodes_tenant_id_parent_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_nodes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookChapter", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_chapters_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_chapters_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_chapters_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.HandbookSubject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_handbook_chapters_handbook_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookEntry", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_entries_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.HandbookChapter", null) - .WithMany() - .HasForeignKey("TenantId", "ChapterId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_handbook_entries_handbook_chapters_tenant_id_chapter_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_entries_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_entries_content_entries_tenant_id_entry_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookSubject", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_subjects_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ImageAsset", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_images_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.PracticeBlueprint", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_blueprints_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_blueprints_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_blueprints_users_updated_by"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_question_collections_tenant_id_collecti~"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_content_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollection", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_collections_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collections_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_collections_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_content_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_question_banks_tenant_id_question_bank~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollectionItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_question_collection_items_questions_question_owner_tenant_i~"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_question_collections_tenant_id_co~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_tenant_question_references_tenant~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionTypeGroup", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_type_groups_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_type_groups_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionVideo", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_videos_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_videos_questions_tenant_id_question_id"); - - b.HasOne("Tiku.Domain.Content.VideoExplanation", null) - .WithMany() - .HasForeignKey("TenantId", "VideoId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_videos_video_explanations_tenant_id_video_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.SubjectShare", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SourceSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_subjects_tenant_id_source_subject_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "TargetSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_subjects_tenant_id_target_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionBankPreference", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_bank_preferences_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_question_bank_preferences_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_bank_preferences_users_updated_by"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("QuestionBankOwnerTenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_question_bank_preferences_question_banks_question_ba~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionReference", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_references_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_question_references_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_question_references_questions_question_owner_tenant_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordFavorite", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_users_user_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyWord", null) - .WithMany() - .HasForeignKey("TenantId", "WordId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_vocabulary_words_tenant_id_word_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordProgress", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_users_user_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyWord", null) - .WithMany() - .HasForeignKey("TenantId", "WordId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_vocabulary_words_tenant_id_word_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoExplanation", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_explanations_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_video_explanations_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoPlaybackProgress", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_playback_progress_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_playback_progress_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_video_playback_progress_questions_tenant_id_question_id"); - - b.HasOne("Tiku.Domain.Content.VideoExplanation", null) - .WithMany() - .HasForeignKey("TenantId", "VideoId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_playback_progress_video_explanations_tenant_id_video_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyUnit", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_vocabulary_units_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyWord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_vocabulary_words_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyUnit", null) - .WithMany() - .HasForeignKey("TenantId", "UnitId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_vocabulary_units_tenant_id_unit_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlement", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("GeneratedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_generated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("PaidBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_paid_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlements_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlements_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementExportEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ExportedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_export_events_users_exported_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_export_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_export_events_commission_settlements_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementItem", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_items_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_items_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_items_commission_settlements_tenant_i~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementProof", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("SubmittedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_users_submitted_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_proofs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_content_assets_tenant_id_asset~"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_proofs_commission_settlements_tenant_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmConfig", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_config_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookLog", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_webhook_log_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookQueueItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_webhook_queue_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralCode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_codes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_codes_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralLead", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_assigned_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedToUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_assigned_to_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_leads_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_leads_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.ReferralTrack", null) - .WithMany() - .HasForeignKey("TenantId", "FirstTrackId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_referral_tracks_tenant_id_first_track_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralQrcode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_qrcodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_qrcodes_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTeamEdge", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("LeaderUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_team_edges_users_leader_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("MemberUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_team_edges_users_member_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_team_edges_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTrack", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("TargetUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_users_target_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_tracks_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.ReferralLead", null) - .WithMany() - .HasForeignKey("TenantId", "LeadId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_referral_leads_tenant_id_lead_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.TenantCommissionSetting", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_commission_settings_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_commission_settings_users_updated_by"); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.StudentProfile", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_student_profiles_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_student_profiles_users_user_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "SelectedMajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_majors_tenant_id_selected_major_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SelectedSchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_schools_tenant_id_selected_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.UserIdentity", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_identities_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportIssue", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_import_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Import.PocketBaseImportRun", null) - .WithMany() - .HasForeignKey("TenantId", "RunId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_pb_import_issues_pb_import_runs_tenant_id_run_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportRun", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_import_runs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseRawRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_raw_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Import.PocketBaseImportRun", null) - .WithMany() - .HasForeignKey("TenantId", "RunId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_raw_records_pb_import_runs_tenant_id_run_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.AnswerRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_answer_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_answer_records_users_user_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSessionQuestion", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId", "SessionQuestionId") - .HasPrincipalKey("TenantId", "PracticeSessionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_answer_records_practice_session_questions_tenant_id_practic~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "UserId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "UserId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_answer_records_practice_sessions_tenant_id_user_id_practice~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.DashboardDailyStat", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_dashboard_daily_stats_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_dashboard_daily_stats_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ExamDate", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_exam_dates_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_exam_dates_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_exam_dates_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.FavoriteQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_favorite_questions_questions_question_owner_tenant_id_quest~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_tenant_question_references_tenant_id_que~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeAccessEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_access_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_access_events_users_user_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_access_events_practice_sessions_tenant_id_practice~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeDailyUsage", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_daily_usage_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_daily_usage_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSession", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_sessions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_sessions_users_user_id"); - - b.HasOne("Tiku.Domain.Content.PracticeBlueprint", null) - .WithMany() - .HasForeignKey("TenantId", "BlueprintId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_practice_blueprints_tenant_id_blueprint_id"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_question_collections_tenant_id_collection~"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_content_entries_tenant_id_entry_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_practice_sessions_tenant_id_prac~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionVersion", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId", "QuestionVersionId") - .HasPrincipalKey("TenantId", "QuestionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_question_versions_question_owner~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_tenant_question_references_tenan~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReport", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_users_user_id"); - - b.HasOne("Tiku.Domain.Content.PracticeBlueprint", null) - .WithMany() - .HasForeignKey("TenantId", "BlueprintId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_session_reports_practice_blueprints_tenant_id_blue~"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_session_reports_question_collections_tenant_id_col~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_practice_sessions_tenant_id_practi~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReportSection", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_practice_sessions_tenant_i~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSessionReport", null) - .WithMany() - .HasForeignKey("TenantId", "ReportId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_practice_session_reports_t~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RecentPractice", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_recent_practices_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_recent_practices_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.Report", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("HandledBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_reports_users_handled_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_reports_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_reports_questions_tenant_id_question_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ReportStatusEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_report_status_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_report_status_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.Report", null) - .WithMany() - .HasForeignKey("TenantId", "ReportId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_report_status_events_reports_tenant_id_report_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RevenueDailyStat", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_revenue_daily_stats_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_revenue_daily_stats_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.UserScoreEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_score_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_score_events_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.WrongQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_wrong_questions_questions_question_owner_tenant_id_question~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_tenant_question_references_tenant_id_questi~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Announcement", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_announcements_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.AuditLog", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_audit_logs_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_audit_logs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_backend_menus_backend_permissions_permission_code"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_background_jobs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "OutputAssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_background_jobs_content_assets_tenant_id_output_asset_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Badge", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Banner", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_banners_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_banners_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Faq", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_faqs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_faqs_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendMenu", null) - .WithMany() - .HasForeignKey("MenuCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_menus_backend_menus_menu_code"); - - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_menus_platform_backend_roles_role_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_permissions_backend_permissions_permi~"); - - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_permissions_platform_backend_roles_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b => - { - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_user_roles_platform_backend_roles_role_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_user_roles_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_roles_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendMenu", null) - .WithMany() - .HasForeignKey("MenuCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_menus_backend_menus_menu_code"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_menus_tenant_backend_roles_tenant_id_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_permissions_backend_permissions_permiss~"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_permissions_tenant_backend_roles_tenant~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_user_roles_users_user_id"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_user_roles_tenant_backend_roles_tenant_id_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_content_notifications_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReadBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_content_notifications_users_read_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_content_notifications_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "SourceQuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_tenant_content_notifications_question_banks_tenant_id_sourc~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeConfig", b => - { - b.HasOne("Tiku.Domain.Operations.TenantThemeTemplate", null) - .WithMany() - .HasForeignKey("ActiveTemplateCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_tenant_theme_templates_active_template~"); - - b.HasOne("Tiku.Domain.Operations.TenantThemeTemplate", null) - .WithMany() - .HasForeignKey("DraftTemplateCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_tenant_theme_templates_draft_template_~"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("DraftUpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_users_draft_updated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("PublishedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_users_published_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_theme_configs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserBadge", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("GrantedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_user_badges_users_granted_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_badges_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_user_badges_users_user_id"); - - b.HasOne("Tiku.Domain.Operations.Badge", null) - .WithMany() - .HasForeignKey("TenantId", "BadgeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_user_badges_badges_tenant_id_badge_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserNotification", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_user_notifications_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_notifications_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_notifications_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AcknowledgedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_platform_audit_alerts_users_acknowledged_by"); - - b.HasOne("Tiku.Domain.Operations.AuditLog", null) - .WithMany() - .HasForeignKey("AuditLogId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_audit_alerts_audit_logs_audit_log_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ResolvedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_platform_audit_alerts_users_resolved_by"); - - b.HasOne("Tiku.Domain.Platform.PlatformAuditAlertRule", null) - .WithMany() - .HasForeignKey("RuleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_audit_alerts_platform_audit_alert_rules_rule_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_platform_audit_alerts_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlertRule", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_platform_audit_alert_rules_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => - { - b.HasOne("Tiku.Domain.Platform.PlatformDunningNotificationChannel", null) - .WithMany() - .HasForeignKey("ChannelId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_platform_dunning_notif~"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoices_tenant~"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoiceReminder", null) - .WithMany() - .HasForeignKey("TenantId", "ReminderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoice_reminde~"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Platform.TenantBillingProfile", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_billing_profiles_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoices_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoices_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReceivedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_payments_users_received_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_reminders_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenant_invoices_tenant_id_invoice_~"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "PrimaryCollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_collections_tenant_id_primary_collection~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_banks_tenant_id_question_bank_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_subjects_tenant_id_subject_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionVersion", null) - .WithMany() - .HasForeignKey("TenantId", "Id", "CurrentVersionId") - .HasPrincipalKey("TenantId", "QuestionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_versions_tenant_id_id_current_version_id"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionBank", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_banks_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_banks_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionVersion", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_versions_users_created_by"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_versions_questions_tenant_id_question_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_auth_challenges_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_auth_login_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_auth_login_events_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthSession", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_auth_sessions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsSendRateLimit", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sms_send_rate_limits_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsVerificationCode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sms_verification_codes_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.Tenant", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("OwnerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenants_users_owner_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantBranding", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Tenancy.TenantBranding", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_branding_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClass", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_classes_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_classes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_classes_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_tenant_classes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClassMember", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_users_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.TenantClass", null) - .WithMany() - .HasForeignKey("TenantId", "ClassId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_tenant_classes_tenant_id_class_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantDomain", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_domains_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantExternalProvider", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_external_providers_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantFrontendConfig", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_frontend_configs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantMembership", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_memberships_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_memberships_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_secrets_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSettings", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Tenancy.TenantSettings", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_settings_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentFollowup", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedToUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_users_assigned_to_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CompletedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_users_completed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_followups_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_followups_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Tenancy.TenantClass", null) - .WithMany() - .HasForeignKey("TenantId", "ClassId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_tenant_classes_tenant_id_class_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentNote", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_notes_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_notes_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_notes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_notes_users_updated_by"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260728093337_RemoveMfaAuthentication.cs b/Tiku.Infrastructure/Persistence/Migrations/20260728093337_RemoveMfaAuthentication.cs deleted file mode 100644 index 118b6b8..0000000 --- a/Tiku.Infrastructure/Persistence/Migrations/20260728093337_RemoveMfaAuthentication.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Tiku.Infrastructure.Persistence.Migrations -{ - /// - public partial class RemoveMfaAuthentication : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "mfa_satisfied", - table: "auth_sessions"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "mfa_satisfied", - table: "auth_sessions", - type: "boolean", - nullable: false, - defaultValue: false); - } - } -} diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260729012205_AddDistributedSecurityFoundation.Designer.cs b/Tiku.Infrastructure/Persistence/Migrations/20260729012205_AddDistributedSecurityFoundation.Designer.cs deleted file mode 100644 index 952db6b..0000000 --- a/Tiku.Infrastructure/Persistence/Migrations/20260729012205_AddDistributedSecurityFoundation.Designer.cs +++ /dev/null @@ -1,18569 +0,0 @@ -// -using System; -using System.Text.Json; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Tiku.Infrastructure.Persistence; - -#nullable disable - -namespace Tiku.Infrastructure.Persistence.Migrations -{ - [DbContext(typeof(TikuDbContext))] - [Migration("20260729012205_AddDistributedSecurityFoundation")] - partial class AddDistributedSecurityFoundation - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.10") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "citext"); - NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "ltree"); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.InboxState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Consumed") - .HasColumnType("timestamp with time zone") - .HasColumnName("consumed"); - - b.Property("ConsumerId") - .HasColumnType("uuid") - .HasColumnName("consumer_id"); - - b.Property("Delivered") - .HasColumnType("timestamp with time zone") - .HasColumnName("delivered"); - - b.Property("ExpirationTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("expiration_time"); - - b.Property("LastSequenceNumber") - .HasColumnType("bigint") - .HasColumnName("last_sequence_number"); - - b.Property("LockId") - .HasColumnType("uuid") - .HasColumnName("lock_id"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("ReceiveCount") - .HasColumnType("integer") - .HasColumnName("receive_count"); - - b.Property("Received") - .HasColumnType("timestamp with time zone") - .HasColumnName("received"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("bytea") - .HasColumnName("row_version"); - - b.HasKey("Id") - .HasName("pk_inbox_state"); - - b.HasAlternateKey("MessageId", "ConsumerId") - .HasName("ak_inbox_state_message_id_consumer_id"); - - b.HasIndex("Delivered") - .HasDatabaseName("ix_inbox_state_delivered"); - - b.ToTable("inbox_state", (string)null); - }); - - modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b => - { - b.Property("SequenceNumber") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasColumnName("sequence_number"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceNumber")); - - b.Property("Body") - .IsRequired() - .HasColumnType("text") - .HasColumnName("body"); - - b.Property("ContentType") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("content_type"); - - b.Property("ConversationId") - .HasColumnType("uuid") - .HasColumnName("conversation_id"); - - b.Property("CorrelationId") - .HasColumnType("uuid") - .HasColumnName("correlation_id"); - - b.Property("DestinationAddress") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("destination_address"); - - b.Property("EnqueueTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("enqueue_time"); - - b.Property("ExpirationTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("expiration_time"); - - b.Property("FaultAddress") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("fault_address"); - - b.Property("Headers") - .HasColumnType("text") - .HasColumnName("headers"); - - b.Property("InboxConsumerId") - .HasColumnType("uuid") - .HasColumnName("inbox_consumer_id"); - - b.Property("InboxMessageId") - .HasColumnType("uuid") - .HasColumnName("inbox_message_id"); - - b.Property("InitiatorId") - .HasColumnType("uuid") - .HasColumnName("initiator_id"); - - b.Property("MessageId") - .HasColumnType("uuid") - .HasColumnName("message_id"); - - b.Property("MessageType") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message_type"); - - b.Property("OutboxId") - .HasColumnType("uuid") - .HasColumnName("outbox_id"); - - b.Property("Properties") - .HasColumnType("text") - .HasColumnName("properties"); - - b.Property("RequestId") - .HasColumnType("uuid") - .HasColumnName("request_id"); - - b.Property("ResponseAddress") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("response_address"); - - b.Property("SentTime") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_time"); - - b.Property("SourceAddress") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("source_address"); - - b.HasKey("SequenceNumber") - .HasName("pk_outbox_message"); - - b.HasIndex("EnqueueTime") - .HasDatabaseName("ix_outbox_message_enqueue_time"); - - b.HasIndex("ExpirationTime") - .HasDatabaseName("ix_outbox_message_expiration_time"); - - b.HasIndex("OutboxId", "SequenceNumber") - .IsUnique() - .HasDatabaseName("ix_outbox_message_outbox_id_sequence_number"); - - b.HasIndex("InboxMessageId", "InboxConsumerId", "SequenceNumber") - .IsUnique() - .HasDatabaseName("ix_outbox_message_inbox_message_id_inbox_consumer_id_sequence_~"); - - b.ToTable("outbox_message", (string)null); - }); - - modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxState", b => - { - b.Property("OutboxId") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("outbox_id"); - - b.Property("Created") - .HasColumnType("timestamp with time zone") - .HasColumnName("created"); - - b.Property("Delivered") - .HasColumnType("timestamp with time zone") - .HasColumnName("delivered"); - - b.Property("LastSequenceNumber") - .HasColumnType("bigint") - .HasColumnName("last_sequence_number"); - - b.Property("LockId") - .HasColumnType("uuid") - .HasColumnName("lock_id"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("bytea") - .HasColumnName("row_version"); - - b.HasKey("OutboxId") - .HasName("pk_outbox_state"); - - b.HasIndex("Created") - .HasDatabaseName("ix_outbox_state_created"); - - b.ToTable("outbox_state", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FriendlyName") - .HasColumnType("text") - .HasColumnName("friendly_name"); - - b.Property("Xml") - .HasColumnType("text") - .HasColumnName("xml"); - - b.HasKey("Id") - .HasName("pk_data_protection_keys"); - - b.ToTable("data_protection_keys", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasColumnName("id"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text") - .HasColumnName("claim_type"); - - b.Property("ClaimValue") - .HasColumnType("text") - .HasColumnName("claim_value"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_claims"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_claims_user_id"); - - b.ToTable("user_claims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text") - .HasColumnName("login_provider"); - - b.Property("ProviderKey") - .HasColumnType("text") - .HasColumnName("provider_key"); - - b.Property("ProviderDisplayName") - .HasColumnType("text") - .HasColumnName("provider_display_name"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("LoginProvider", "ProviderKey") - .HasName("pk_user_logins"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_logins_user_id"); - - b.ToTable("user_logins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("LoginProvider") - .HasColumnType("text") - .HasColumnName("login_provider"); - - b.Property("Name") - .HasColumnType("text") - .HasColumnName("name"); - - b.Property("Value") - .HasColumnType("text") - .HasColumnName("value"); - - b.HasKey("UserId", "LoginProvider", "Name") - .HasName("pk_user_tokens"); - - b.ToTable("user_tokens", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Category", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CategoryType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("category_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("SvipQuestionLimit") - .HasColumnType("integer") - .HasColumnName("svip_question_limit"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_categories"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_categories_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_categories_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_categories_tenant_id_node_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_categories_tenant_id_subject_id"); - - b.ToTable("categories", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Major", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StudyTips") - .HasColumnType("text") - .HasColumnName("study_tips"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_majors"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_majors_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_majors_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_majors_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_majors_tenant_id_school_id"); - - b.ToTable("majors", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ModuleNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyModuleId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_module_id"); - - b.Property("LegacyParentId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_parent_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("Path") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("path"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_module_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_module_nodes_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_module_nodes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_module_nodes_tenant_id_module_id"); - - b.HasIndex("TenantId", "ParentId") - .HasDatabaseName("ix_module_nodes_tenant_id_parent_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_module_nodes_tenant_id_region_id"); - - b.ToTable("module_nodes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.QuestionTaxonomyAssignment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsPrimary") - .HasColumnType("boolean") - .HasColumnName("is_primary"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("TaxonomyNodeId") - .HasColumnType("uuid") - .HasColumnName("taxonomy_node_id"); - - b.Property("TaxonomyOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("taxonomy_owner_tenant_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_question_taxonomy_assignments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_taxonomy_assignments_tenant_id_id"); - - b.HasIndex("TaxonomyOwnerTenantId", "TaxonomyNodeId") - .HasDatabaseName("ix_question_taxonomy_assignments_taxonomy_owner_tenant_id_taxo~"); - - b.HasIndex("TenantId", "QuestionId", "TaxonomyOwnerTenantId", "TaxonomyNodeId") - .IsUnique() - .HasDatabaseName("ix_question_taxonomy_assignments_tenant_id_question_id_taxonom~"); - - b.ToTable("question_taxonomy_assignments", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Region", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("code"); - - b.Property("Config") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FullName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("full_name"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsHot") - .HasColumnType("boolean") - .HasColumnName("is_hot"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Pinyin") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("pinyin"); - - b.Property("ShortName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("short_name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_regions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_regions_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_regions_tenant_id_legacy_id"); - - b.ToTable("regions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.RegionModule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsPrimarySchoolModule") - .HasColumnType("boolean") - .HasColumnName("is_primary_school_module"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Route") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("route"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TextColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("text_color"); - - b.Property("Type") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_region_modules"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_region_modules_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_region_modules_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_region_modules_tenant_id_region_id"); - - b.ToTable("region_modules", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.School", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("ProfessionalExamDate") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("professional_exam_date"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_schools"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_schools_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_schools_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_schools_tenant_id_module_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_schools_tenant_id_region_id"); - - b.ToTable("schools", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineField", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("FieldKey") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("field_key"); - - b.Property("FieldName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("field_name"); - - b.Property("FieldType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("field_type"); - - b.Property("IsFilter") - .HasColumnType("boolean") - .HasColumnName("is_filter"); - - b.Property("IsRequired") - .HasColumnType("boolean") - .HasColumnName("is_required"); - - b.Property("IsTrend") - .HasColumnType("boolean") - .HasColumnName("is_trend"); - - b.Property("IsVisible") - .HasColumnType("boolean") - .HasColumnName("is_visible"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Options") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Placeholder") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("placeholder"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Unit") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("unit"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_scoreline_fields"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_scoreline_fields_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_scoreline_fields_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "FieldKey") - .IsUnique() - .HasDatabaseName("ix_scoreline_fields_tenant_id_region_id_field_key"); - - b.HasIndex("TenantId", "RegionId", "IsFilter", "SortOrder") - .HasDatabaseName("ix_scoreline_fields_tenant_id_region_id_is_filter_sort_order"); - - b.ToTable("scoreline_fields", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FieldValues") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("field_values") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("major_name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SchoolName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("school_name"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Year") - .HasColumnType("integer") - .HasColumnName("year"); - - b.HasKey("Id") - .HasName("pk_scoreline_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_scoreline_records_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_scoreline_records_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_scoreline_records_tenant_id_major_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_scoreline_records_tenant_id_school_id"); - - b.HasIndex("TenantId", "Year") - .HasDatabaseName("ix_scoreline_records_tenant_id_year"); - - b.HasIndex("TenantId", "RegionId", "SchoolId", "MajorId", "Year") - .HasDatabaseName("ix_scoreline_records_tenant_id_region_id_school_id_major_id_ye~"); - - b.ToTable("scoreline_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Subject", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorLegacyIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("major_legacy_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("ModuleId") - .HasColumnType("uuid") - .HasColumnName("module_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_subjects"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_subjects_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_subjects_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_subjects_tenant_id_major_id"); - - b.HasIndex("TenantId", "ModuleId") - .HasDatabaseName("ix_subjects_tenant_id_module_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_subjects_tenant_id_node_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_subjects_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_subjects_tenant_id_school_id"); - - b.ToTable("subjects", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.TaxonomyNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Depth") - .HasColumnType("integer") - .HasColumnName("depth"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("node_type"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("ParentOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("parent_owner_tenant_id"); - - b.Property("Path") - .HasColumnType("ltree") - .HasColumnName("path"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_taxonomy_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_taxonomy_nodes_tenant_id_id"); - - b.HasIndex("ParentOwnerTenantId", "ParentId") - .HasDatabaseName("ix_taxonomy_nodes_parent_owner_tenant_id_parent_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_taxonomy_nodes_tenant_id_code"); - - b.ToTable("taxonomy_nodes", null, t => - { - t.HasCheckConstraint("ck_taxonomy_nodes_parent_pair", "(parent_owner_tenant_id is null) = (parent_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.ActivationCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AgentUserId") - .HasColumnType("uuid") - .HasColumnName("agent_user_id"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CouponCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("coupon_code"); - - b.Property("CouponRedemptionId") - .HasColumnType("uuid") - .HasColumnName("coupon_redemption_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("IsUsed") - .HasColumnType("boolean") - .HasColumnName("is_used"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("SoldTo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("sold_to"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitPriceCents") - .HasColumnType("integer") - .HasColumnName("unit_price_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("used_at"); - - b.Property("UsedBy") - .HasColumnType("uuid") - .HasColumnName("used_by"); - - b.Property("UsedRegionId") - .HasColumnType("uuid") - .HasColumnName("used_region_id"); - - b.HasKey("Id") - .HasName("pk_activation_codes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_activation_codes_tenant_id_id"); - - b.HasIndex("AgentUserId") - .HasDatabaseName("ix_activation_codes_agent_user_id"); - - b.HasIndex("UsedBy") - .HasDatabaseName("ix_activation_codes_used_by"); - - b.HasIndex("TenantId", "BatchId") - .HasDatabaseName("ix_activation_codes_tenant_id_batch_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_activation_codes_tenant_id_code"); - - b.HasIndex("TenantId", "CouponRedemptionId") - .HasDatabaseName("ix_activation_codes_tenant_id_coupon_redemption_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_activation_codes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UsedRegionId") - .HasDatabaseName("ix_activation_codes_tenant_id_used_region_id"); - - b.ToTable("activation_codes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CodeBatch", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CampaignName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("campaign_name"); - - b.Property("Channel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel"); - - b.Property("CommissionRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("commission_rate"); - - b.Property("CostPriceCents") - .HasColumnType("integer") - .HasColumnName("cost_price_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("DefaultUnitPriceCents") - .HasColumnType("integer") - .HasColumnName("default_unit_price_cents"); - - b.Property("IssuedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("issued_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_code_batches"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_code_batches_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_code_batches_tenant_id_legacy_id"); - - b.ToTable("code_batches", null, t => - { - t.HasCheckConstraint("ck_code_batches_amounts", "default_unit_price_cents >= 0 and cost_price_cents >= 0"); - - t.HasCheckConstraint("ck_code_batches_counts", "total_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucher", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("ClosedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("closed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("Direction") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("direction"); - - b.Property("IssueId") - .HasColumnType("uuid") - .HasColumnName("issue_id"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProofAssetKey") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("proof_asset_key"); - - b.Property("Reason") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("reason"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VoucherNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("voucher_no"); - - b.HasKey("Id") - .HasName("pk_commerce_adjustment_vouchers"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_adjustment_vouchers_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_adjustment_vouchers_created_by"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commerce_adjustment_vouchers_reviewed_by"); - - b.HasIndex("TenantId", "BatchId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_batch_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_item_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_order_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "VoucherNo") - .IsUnique() - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_voucher_no"); - - b.HasIndex("TenantId", "IssueId", "CreatedAt") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_issue_id_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_adjustment_vouchers_tenant_id_status_created_at"); - - b.ToTable("commerce_adjustment_vouchers", null, t => - { - t.HasCheckConstraint("ck_commerce_adjustment_vouchers_amount", "amount_cents > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucherEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("Note") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.Property("VoucherId") - .HasColumnType("uuid") - .HasColumnName("voucher_id"); - - b.HasKey("Id") - .HasName("pk_commerce_adjustment_voucher_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_adjustment_voucher_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_adjustment_voucher_events_actor_user_id"); - - b.HasIndex("TenantId", "VoucherId", "CreatedAt") - .HasDatabaseName("ix_commerce_adjustment_voucher_events_tenant_id_voucher_id_cre~"); - - b.ToTable("commerce_adjustment_voucher_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationBatch", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BillDate") - .HasColumnType("date") - .HasColumnName("bill_date"); - - b.Property("BillType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("bill_type"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DuplicateCount") - .HasColumnType("integer") - .HasColumnName("duplicate_count"); - - b.Property("Error") - .HasColumnType("text") - .HasColumnName("error"); - - b.Property("FeeCents") - .HasColumnType("integer") - .HasColumnName("fee_cents"); - - b.Property("IgnoredCount") - .HasColumnType("integer") - .HasColumnName("ignored_count"); - - b.Property("MatchedCount") - .HasColumnType("integer") - .HasColumnName("matched_count"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MismatchCount") - .HasColumnType("integer") - .HasColumnName("mismatch_count"); - - b.Property("MissingLocalCount") - .HasColumnType("integer") - .HasColumnName("missing_local_count"); - - b.Property("MissingProviderCount") - .HasColumnType("integer") - .HasColumnName("missing_provider_count"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source"); - - b.Property("SourceHash") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("source_hash"); - - b.Property("SourceName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("source_name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_batches"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_batches_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_reconciliation_batches_created_by"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_batches_tenant_id_status_created_at"); - - b.HasIndex("TenantId", "Provider", "BillDate", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_batches_tenant_id_provider_bill_dat~"); - - b.ToTable("commerce_reconciliation_batches", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_batches_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commerce_reconciliation_batches_counts", "total_count >= 0 and matched_count >= 0 and mismatch_count >= 0 and missing_local_count >= 0 and missing_provider_count >= 0 and duplicate_count >= 0 and ignored_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("AssignedTo") - .HasColumnType("uuid") - .HasColumnName("assigned_to"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DueAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("due_at"); - - b.Property("IssueCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("IssueNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_no"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("MatchStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("match_status"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("OrderNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("RefundNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("ResolutionNote") - .HasColumnType("text") - .HasColumnName("resolution_note"); - - b.Property("ResolutionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("resolution_type"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("ResolvedBy") - .HasColumnType("uuid") - .HasColumnName("resolved_by"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("summary"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TransactionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("transaction_type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_issues_tenant_id_id"); - - b.HasIndex("AssignedTo") - .HasDatabaseName("ix_commerce_reconciliation_issues_assigned_to"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_commerce_reconciliation_issues_created_by"); - - b.HasIndex("ResolvedBy") - .HasDatabaseName("ix_commerce_reconciliation_issues_resolved_by"); - - b.HasIndex("TenantId", "IssueNo") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_issue_no"); - - b.HasIndex("TenantId", "ItemId") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_item_id") - .HasFilter("item_id is not null and status in ('open', 'investigating', 'escalated')"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_order_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "OrderNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_order_no_created_at"); - - b.HasIndex("TenantId", "PaymentId", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_payment_id_created~"); - - b.HasIndex("TenantId", "AssignedTo", "Status", "DueAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_assigned_to_status~"); - - b.HasIndex("TenantId", "BatchId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_batch_id_status_cr~"); - - b.HasIndex("TenantId", "Status", "Severity", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issues_tenant_id_status_severity_cr~"); - - b.ToTable("commerce_reconciliation_issues", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_issues_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssueEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("IssueId") - .HasColumnType("uuid") - .HasColumnName("issue_id"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_issue_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_issue_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_reconciliation_issue_events_actor_user_id"); - - b.HasIndex("TenantId", "IssueId", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_issue_events_tenant_id_issue_id_cre~"); - - b.ToTable("commerce_reconciliation_issue_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BatchId") - .HasColumnType("uuid") - .HasColumnName("batch_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FeeCents") - .HasColumnType("integer") - .HasColumnName("fee_cents"); - - b.Property("IssueCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("LocalStatus") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("local_status"); - - b.Property("MatchStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("match_status"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("OrderNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("ProviderStatus") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider_status"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RefundAmountCents") - .HasColumnType("integer") - .HasColumnName("refund_amount_cents"); - - b.Property("RefundNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("RefundedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("refunded_at"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TransactionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("transaction_type"); - - b.HasKey("Id") - .HasName("pk_commerce_reconciliation_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_reconciliation_items_tenant_id_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_order_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundRequestId") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_refund_request_id"); - - b.HasIndex("TenantId", "BatchId", "RowNo") - .IsUnique() - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_batch_id_row_no"); - - b.HasIndex("TenantId", "OrderNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_order_no_created_at"); - - b.HasIndex("TenantId", "BatchId", "MatchStatus", "RowNo") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_batch_id_match_stat~"); - - b.HasIndex("TenantId", "Provider", "ProviderTradeNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_provider_provider_t~"); - - b.HasIndex("TenantId", "RefundNo", "ProviderRefundNo", "CreatedAt") - .HasDatabaseName("ix_commerce_reconciliation_items_tenant_id_refund_no_provider_~"); - - b.ToTable("commerce_reconciliation_items", null, t => - { - t.HasCheckConstraint("ck_commerce_reconciliation_items_amounts", "amount_cents >= 0 and refund_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commerce_reconciliation_items_row", "row_no > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("RefundRequestId") - .HasColumnType("uuid") - .HasColumnName("refund_request_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_commerce_refund_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_refund_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_commerce_refund_events_actor_user_id"); - - b.HasIndex("TenantId", "RefundRequestId", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_events_tenant_id_refund_request_id_created_~"); - - b.ToTable("commerce_refund_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundRequest", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CancelledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("cancelled_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementAction") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("entitlement_action"); - - b.Property("FailedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("failed_at"); - - b.Property("FailureReason") - .HasColumnType("text") - .HasColumnName("failure_reason"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProcessedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("processed_at"); - - b.Property("ProcessedBy") - .HasColumnType("uuid") - .HasColumnName("processed_by"); - - b.Property("Provider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderRefundNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_refund_no"); - - b.Property("Reason") - .HasColumnType("text") - .HasColumnName("reason"); - - b.Property("RefundNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("refund_no"); - - b.Property("RequestedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("requested_at") - .HasDefaultValueSql("now()"); - - b.Property("RequestedBy") - .HasColumnType("uuid") - .HasColumnName("requested_by"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SucceededAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("succeeded_at"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commerce_refund_requests"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commerce_refund_requests_tenant_id_id"); - - b.HasIndex("ProcessedBy") - .HasDatabaseName("ix_commerce_refund_requests_processed_by"); - - b.HasIndex("RequestedBy") - .HasDatabaseName("ix_commerce_refund_requests_requested_by"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commerce_refund_requests_reviewed_by"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_payment_id"); - - b.HasIndex("TenantId", "RefundNo") - .IsUnique() - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_refund_no"); - - b.HasIndex("TenantId", "OrderId", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_order_id_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_commerce_refund_requests_tenant_id_status_created_at"); - - b.ToTable("commerce_refund_requests", null, t => - { - t.HasCheckConstraint("ck_commerce_refund_requests_amount", "amount_cents > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Coupon", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DiscountType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("discount_type"); - - b.Property("DiscountValue") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("discount_value"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MaxUses") - .HasColumnType("integer") - .HasColumnName("max_uses"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("Source") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property("ValidFrom") - .HasColumnType("timestamp with time zone") - .HasColumnName("valid_from"); - - b.Property("ValidTo") - .HasColumnType("timestamp with time zone") - .HasColumnName("valid_to"); - - b.HasKey("Id") - .HasName("pk_coupons"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_coupons_tenant_id_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_coupons_tenant_id_code"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_coupons_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_coupons_tenant_id_plan_id"); - - b.ToTable("coupons", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CouponRedemption", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClaimedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("claimed_at"); - - b.Property("CouponCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("coupon_code"); - - b.Property("CouponId") - .HasColumnType("uuid") - .HasColumnName("coupon_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DiscountAppliedCents") - .HasColumnType("integer") - .HasColumnName("discount_applied_cents"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("Source") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("used_at"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_coupon_redemptions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_coupon_redemptions_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_coupon_redemptions_user_id"); - - b.HasIndex("TenantId", "CouponId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_coupon_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_coupon_redemptions_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_plan_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_region_id"); - - b.HasIndex("TenantId", "OrderId", "UserId") - .HasDatabaseName("ix_coupon_redemptions_tenant_id_order_id_user_id") - .HasFilter("order_id is not null"); - - b.HasIndex("TenantId", "UserId", "CouponId") - .IsUnique() - .HasDatabaseName("ix_coupon_redemptions_tenant_id_user_id_coupon_id") - .HasFilter("coupon_id is not null"); - - b.ToTable("coupon_redemptions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Entitlement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementType") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("entitlement_type"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("LegacySourceId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_source_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RevokedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("revoked_at"); - - b.Property("RevokedBy") - .HasColumnType("uuid") - .HasColumnName("revoked_by"); - - b.Property("RevokedReason") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("revoked_reason"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("StartsAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at") - .HasDefaultValueSql("now()"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_entitlements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_entitlements_tenant_id_id"); - - b.HasIndex("RevokedBy") - .HasDatabaseName("ix_entitlements_revoked_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_entitlements_user_id"); - - b.HasIndex("TenantId", "UserId", "Status", "ExpiresAt") - .HasDatabaseName("ix_entitlements_tenant_id_user_id_status_expires_at"); - - b.ToTable("entitlements", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyPlanId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_plan_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("LegacyUserId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_user_id"); - - b.Property("OrderNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PayMethod") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("pay_method"); - - b.Property("PayProvider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("pay_provider"); - - b.Property("PlanId") - .HasColumnType("uuid") - .HasColumnName("plan_id"); - - b.Property("ProductName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("product_name"); - - b.Property("ProductType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("product_type"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefundedAmountCents") - .HasColumnType("integer") - .HasColumnName("refunded_amount_cents"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("trade_no"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_orders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_orders_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_orders_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_orders_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderNo") - .IsUnique() - .HasDatabaseName("ix_orders_tenant_id_order_no"); - - b.HasIndex("TenantId", "PlanId") - .HasDatabaseName("ix_orders_tenant_id_plan_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_orders_tenant_id_region_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_orders_tenant_id_user_id_created_at"); - - b.ToTable("orders", null, t => - { - t.HasCheckConstraint("ck_orders_amount", "amount_cents >= 0"); - - t.HasCheckConstraint("ck_orders_refunded_amount", "refunded_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.OrderItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("item_type"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("Quantity") - .HasColumnType("integer") - .HasColumnName("quantity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalAmountCents") - .HasColumnType("integer") - .HasColumnName("total_amount_cents"); - - b.Property("UnitAmountCents") - .HasColumnType("integer") - .HasColumnName("unit_amount_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_order_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_order_items_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_order_items_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderId") - .HasDatabaseName("ix_order_items_tenant_id_order_id"); - - b.ToTable("order_items", null, t => - { - t.HasCheckConstraint("ck_order_items_amounts", "unit_amount_cents >= 0 and total_amount_cents >= 0"); - - t.HasCheckConstraint("ck_order_items_quantity", "quantity > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Payment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyOrderId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_order_id"); - - b.Property("Method") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("method"); - - b.Property("OrderId") - .HasColumnType("uuid") - .HasColumnName("order_id"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefundedAmountCents") - .HasColumnType("integer") - .HasColumnName("refunded_amount_cents"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_payments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_payments_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_payments_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "OrderId", "UpdatedAt") - .HasDatabaseName("ix_payments_tenant_id_order_id_updated_at"); - - b.HasIndex("TenantId", "Provider", "ProviderTradeNo") - .IsUnique() - .HasDatabaseName("ix_payments_tenant_id_provider_provider_trade_no") - .HasFilter("provider_trade_no is not null"); - - b.ToTable("payments", null, t => - { - t.HasCheckConstraint("ck_payments_amount", "amount_cents >= 0"); - - t.HasCheckConstraint("ck_payments_refunded_amount", "refunded_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PaymentEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Error") - .HasColumnType("text") - .HasColumnName("error"); - - b.Property("EventId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("event_id"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaymentId") - .HasColumnType("uuid") - .HasColumnName("payment_id"); - - b.Property("ProcessedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("processed_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("SignatureValid") - .HasColumnType("boolean") - .HasColumnName("signature_valid"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_payment_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_payment_events_tenant_id_id"); - - b.HasIndex("TenantId", "PaymentId") - .HasDatabaseName("ix_payment_events_tenant_id_payment_id"); - - b.HasIndex("TenantId", "Provider", "EventId") - .IsUnique() - .HasDatabaseName("ix_payment_events_tenant_id_provider_event_id"); - - b.ToTable("payment_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClaimedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("claimed_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TaskId") - .HasColumnType("uuid") - .HasColumnName("task_id"); - - b.Property("TaskKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("task_key"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_point_activity_claims"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_activity_claims_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_point_activity_claims_user_id"); - - b.HasIndex("TenantId", "TaskId") - .HasDatabaseName("ix_point_activity_claims_tenant_id_task_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_point_activity_claims_tenant_id_user_id_created_at"); - - b.HasIndex("TenantId", "UserId", "TaskId", "SourceType", "SourceId") - .IsUnique() - .HasDatabaseName("ix_point_activity_claims_tenant_id_user_id_task_id_source_type~") - .HasFilter("source_type is not null and source_id is not null"); - - b.ToTable("point_activity_claims", null, t => - { - t.HasCheckConstraint("ck_point_activity_claims_points", "points > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityTask", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("EndsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ends_at"); - - b.Property("MaxClaimsPerUser") - .HasColumnType("integer") - .HasColumnName("max_claims_per_user"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("Rules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TaskKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("task_key"); - - b.Property("TaskType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("task_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_point_activity_tasks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_activity_tasks_tenant_id_id"); - - b.HasIndex("TenantId", "TaskKey") - .IsUnique() - .HasDatabaseName("ix_point_activity_tasks_tenant_id_task_key"); - - b.HasIndex("TenantId", "Status", "SortOrder") - .HasDatabaseName("ix_point_activity_tasks_tenant_id_status_sort_order"); - - b.ToTable("point_activity_tasks", null, t => - { - t.HasCheckConstraint("ck_point_activity_tasks_max_claims", "max_claims_per_user > 0"); - - t.HasCheckConstraint("ck_point_activity_tasks_points", "points > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("EndsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("ends_at"); - - b.Property("FulfillmentPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("fulfillment_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ItemKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("item_key"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("PointsCost") - .HasColumnType("integer") - .HasColumnName("points_cost"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Stock") - .HasColumnType("integer") - .HasColumnName("stock"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_point_exchange_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_exchange_items_tenant_id_id"); - - b.HasIndex("TenantId", "ItemKey") - .IsUnique() - .HasDatabaseName("ix_point_exchange_items_tenant_id_item_key"); - - b.HasIndex("TenantId", "RegionId", "Status", "SortOrder") - .HasDatabaseName("ix_point_exchange_items_tenant_id_region_id_status_sort_order"); - - b.ToTable("point_exchange_items", null, t => - { - t.HasCheckConstraint("ck_point_exchange_items_days", "days is null or days >= 0"); - - t.HasCheckConstraint("ck_point_exchange_items_points_cost", "points_cost > 0"); - - t.HasCheckConstraint("ck_point_exchange_items_stock", "stock is null or stock >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeOrder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CancelledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("cancelled_at"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FulfillmentSnapshot") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("fulfillment_snapshot") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("ItemName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("item_name"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("OrderNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("order_no"); - - b.Property("OrderedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("ordered_at") - .HasDefaultValueSql("now()"); - - b.Property("PointsCost") - .HasColumnType("integer") - .HasColumnName("points_cost"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_point_exchange_orders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_point_exchange_orders_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_point_exchange_orders_user_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_point_exchange_orders_tenant_id_item_id"); - - b.HasIndex("TenantId", "OrderNo") - .IsUnique() - .HasDatabaseName("ix_point_exchange_orders_tenant_id_order_no"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_point_exchange_orders_tenant_id_user_id_created_at"); - - b.ToTable("point_exchange_orders", null, t => - { - t.HasCheckConstraint("ck_point_exchange_orders_points_cost", "points_cost > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Cover") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cover"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DetailImages") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("detail_images") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("IsActive") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(true) - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyPriceText") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_price_text"); - - b.Property("Link") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("link"); - - b.Property("PreviewIframe") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_iframe"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_products"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_products_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_products_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "Type", "IsActive", "SortOrder") - .HasDatabaseName("ix_products_tenant_id_region_id_type_is_active_sort_order"); - - b.ToTable("products", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.SvipPlan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Badge") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("badge"); - - b.Property("CouponOnly") - .HasColumnType("boolean") - .HasColumnName("coupon_only"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Days") - .HasColumnType("integer") - .HasColumnName("days"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OriginalPriceCents") - .HasColumnType("integer") - .HasColumnName("original_price_cents"); - - b.Property("PerDayLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("per_day_label"); - - b.Property("PriceCents") - .HasColumnType("integer") - .HasColumnName("price_cents"); - - b.Property("Recommended") - .HasColumnType("boolean") - .HasColumnName("recommended"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VpEnabled") - .HasColumnType("boolean") - .HasColumnName("vp_enabled"); - - b.Property("VpProductId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("vp_product_id"); - - b.HasKey("Id") - .HasName("pk_svip_plans"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_svip_plans_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_svip_plans_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_svip_plans_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("svip_plans", null, t => - { - t.HasCheckConstraint("ck_svip_plans_days", "days >= 0"); - - t.HasCheckConstraint("ck_svip_plans_price", "price_cents >= 0 and (original_price_cents is null or original_price_cents >= 0)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BillingCycle") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("billing_cycle"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PlanCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("plan_code"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_subscriptions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_subscriptions_tenant_id_id"); - - b.HasIndex("TenantId", "Status", "ExpiresAt") - .HasDatabaseName("ix_tenant_subscriptions_tenant_id_status_expires_at"); - - b.ToTable("tenant_subscriptions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MetricKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("metric_key"); - - b.Property("MetricValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)") - .HasColumnName("metric_value"); - - b.Property("PeriodEnd") - .HasColumnType("date") - .HasColumnName("period_end"); - - b.Property("PeriodStart") - .HasColumnType("date") - .HasColumnName("period_start"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_usage_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_usage_records_tenant_id_id"); - - b.HasIndex("TenantId", "MetricKey", "PeriodStart", "PeriodEnd") - .HasDatabaseName("ix_tenant_usage_records_tenant_id_metric_key_period_start_peri~"); - - b.ToTable("tenant_usage_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContextPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("context_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("GeneratedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("generated_at"); - - b.Property("InputPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("input_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Model") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("model"); - - b.Property("PromptVersion") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("prompt_version"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("ResultPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("result_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_ai_recommendation_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_ai_recommendation_reports_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_ai_recommendation_reports_user_id"); - - b.HasIndex("TenantId", "RegionId", "CreatedAt") - .HasDatabaseName("ix_ai_recommendation_reports_tenant_id_region_id_created_at"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_ai_recommendation_reports_tenant_id_user_id_created_at"); - - b.ToTable("ai_recommendation_reports", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AppAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssetKey") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("asset_key"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_app_assets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_app_assets_tenant_id_id"); - - b.HasIndex("TenantId", "AssetKey") - .IsUnique() - .HasDatabaseName("ix_app_assets_tenant_id_asset_key"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_app_assets_tenant_id_legacy_id"); - - b.ToTable("app_assets", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssetKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("asset_key"); - - b.Property("AssetType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("asset_type"); - - b.Property("Bucket") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("bucket"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("CdnUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cdn_url"); - - b.Property("ChecksumSha256") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("checksum_sha256"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("DownloadCount") - .HasColumnType("integer") - .HasColumnName("download_count"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("FileSizeBytes") - .HasColumnType("bigint") - .HasColumnName("file_size_bytes"); - - b.Property("IsPublic") - .HasColumnType("boolean") - .HasColumnName("is_public"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MimeType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("mime_type"); - - b.Property("ObjectKey") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("object_key"); - - b.Property("PreviewObjectKey") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("preview_object_key"); - - b.Property("PreviewStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("preview_status"); - - b.Property("PreviewUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_url"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SecurityFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("security_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SecurityScanProvider") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("security_scan_provider"); - - b.Property("SecurityScanStatus") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("not_required") - .HasColumnName("security_scan_status"); - - b.Property("SecurityScanSummary") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("security_scan_summary") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SecurityScannedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("security_scanned_at"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StorageProvider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("storage_provider"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("UploadStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("upload_status"); - - b.Property("VerificationDetails") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("verification_details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.Property("VerifiedBy") - .HasColumnType("uuid") - .HasColumnName("verified_by"); - - b.Property("VerifiedChecksumSha256") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("verified_checksum_sha256"); - - b.Property("VerifiedSizeBytes") - .HasColumnType("bigint") - .HasColumnName("verified_size_bytes"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_assets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_assets_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_assets_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_assets_updated_by"); - - b.HasIndex("VerifiedBy") - .HasDatabaseName("ix_content_assets_verified_by"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_content_assets_tenant_id_category_id"); - - b.HasIndex("TenantId", "ChecksumSha256") - .HasDatabaseName("ix_content_assets_tenant_id_checksum_sha256") - .HasFilter("checksum_sha256 is not null"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_content_assets_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_assets_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PreviewStatus") - .HasDatabaseName("ix_content_assets_tenant_id_preview_status") - .HasFilter("preview_status <> 'none'"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_content_assets_tenant_id_region_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_content_assets_tenant_id_subject_id"); - - b.HasIndex("TenantId", "StorageProvider", "Bucket", "ObjectKey") - .HasDatabaseName("ix_content_assets_tenant_id_storage_provider_bucket_object_key"); - - b.HasIndex("TenantId", "UploadStatus", "Status", "UpdatedAt") - .HasDatabaseName("ix_content_assets_tenant_id_upload_status_status_updated_at"); - - b.HasIndex("TenantId", "SecurityScanStatus", "UploadStatus", "Status", "UpdatedAt") - .HasDatabaseName("ix_content_assets_tenant_id_security_scan_status_upload_status~"); - - b.HasIndex("TenantId", "Status", "Visibility", "AssetType", "RegionId", "SubjectId", "CategoryId", "SortOrder") - .HasDatabaseName("ix_content_assets_tenant_id_status_visibility_asset_type_regio~"); - - b.ToTable("content_assets", null, t => - { - t.HasCheckConstraint("ck_content_assets_download_count", "download_count >= 0"); - - t.HasCheckConstraint("ck_content_assets_file_size", "file_size_bytes is null or file_size_bytes >= 0"); - - t.HasCheckConstraint("ck_content_assets_verified_checksum", "verified_checksum_sha256 is null or verified_checksum_sha256 ~ '^[a-f0-9]{64}$'"); - - t.HasCheckConstraint("ck_content_assets_verified_size", "verified_size_bytes is null or verified_size_bytes >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetAccessEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("access_type"); - - b.Property("ActorRole") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("actor_role"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("AssetType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("asset_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DenyCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("deny_code"); - - b.Property("Disposition") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("disposition"); - - b.Property("ExpiresInSeconds") - .HasColumnType("integer") - .HasColumnName("expires_in_seconds"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Result") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("result"); - - b.Property("SignatureMode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("signature_mode"); - - b.Property("StorageProvider") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("storage_provider"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("Visibility") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_asset_access_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_asset_access_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_content_asset_access_events_user_id"); - - b.HasIndex("TenantId", "AssetId", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_asset_id_created_at"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_user_id_created_at") - .HasFilter("user_id is not null"); - - b.HasIndex("TenantId", "AccessType", "Result", "CreatedAt") - .HasDatabaseName("ix_content_asset_access_events_tenant_id_access_type_result_cr~"); - - b.ToTable("content_asset_access_events", null, t => - { - t.HasCheckConstraint("ck_content_asset_access_events_expiry", "expires_in_seconds is null or expires_in_seconds > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetSecurityScanEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.PrimitiveCollection("IssueCodes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("issue_codes") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RiskLevel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("risk_level"); - - b.Property("ScanStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scan_status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_content_asset_security_scan_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_asset_security_scan_events_tenant_id_id"); - - b.HasIndex("TenantId", "AssetId", "CreatedAt") - .HasDatabaseName("ix_content_asset_security_scan_events_tenant_id_asset_id_creat~"); - - b.HasIndex("TenantId", "ScanStatus", "RiskLevel", "CreatedAt") - .HasDatabaseName("ix_content_asset_security_scan_events_tenant_id_scan_status_ri~"); - - b.ToTable("content_asset_security_scan_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("description"); - - b.Property("EntryKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("entry_key"); - - b.Property("EntryType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("entry_type"); - - b.Property("Icon") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LayoutConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("layout_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Route") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("route"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_content_entries"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_entries_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_entries_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_entries_updated_by"); - - b.HasIndex("TenantId", "EntryKey") - .IsUnique() - .HasDatabaseName("ix_content_entries_tenant_id_entry_key"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_entries_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "EntryType", "IsActive", "SortOrder") - .HasDatabaseName("ix_content_entries_tenant_id_region_id_entry_type_is_active_so~"); - - b.ToTable("content_entries", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FieldPath") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("field_path"); - - b.Property("ItemId") - .HasColumnType("uuid") - .HasColumnName("item_id"); - - b.Property("JobId") - .HasColumnType("uuid") - .HasColumnName("job_id"); - - b.Property("Message") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("message"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_content_import_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_issues_tenant_id_id"); - - b.HasIndex("TenantId", "ItemId") - .HasDatabaseName("ix_content_import_issues_tenant_id_item_id"); - - b.HasIndex("TenantId", "JobId", "Severity", "RowNo") - .HasDatabaseName("ix_content_import_issues_tenant_id_job_id_severity_row_no"); - - b.ToTable("content_import_issues", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("content_hash"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExternalId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("external_id"); - - b.Property("IssuesCount") - .HasColumnType("integer") - .HasColumnName("issues_count"); - - b.Property("JobId") - .HasColumnType("uuid") - .HasColumnName("job_id"); - - b.Property("NormalizedPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("normalized_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RowNo") - .HasColumnType("integer") - .HasColumnName("row_no"); - - b.Property("SourcePayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("source_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TargetId") - .HasColumnType("uuid") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_content_import_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_items_tenant_id_id"); - - b.HasIndex("TenantId", "JobId", "RowNo") - .IsUnique() - .HasDatabaseName("ix_content_import_items_tenant_id_job_id_row_no"); - - b.HasIndex("TenantId", "JobId", "Status", "RowNo") - .HasDatabaseName("ix_content_import_items_tenant_id_job_id_status_row_no"); - - b.ToTable("content_import_items", null, t => - { - t.HasCheckConstraint("ck_content_import_items_issues_count", "issues_count >= 0"); - - t.HasCheckConstraint("ck_content_import_items_row_no", "row_no >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DryRun") - .HasColumnType("boolean") - .HasColumnName("dry_run"); - - b.Property("ErrorCount") - .HasColumnType("integer") - .HasColumnName("error_count"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("ImportType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("import_type"); - - b.Property("InsertedCount") - .HasColumnType("integer") - .HasColumnName("inserted_count"); - - b.Property("NormalizedPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("normalized_payload") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SkippedCount") - .HasColumnType("integer") - .HasColumnName("skipped_count"); - - b.Property("SourceFormat") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_format"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SourceName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("source_name"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("summary") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TargetCategoryId") - .HasColumnType("uuid") - .HasColumnName("target_category_id"); - - b.Property("TargetContentNodeId") - .HasColumnType("uuid") - .HasColumnName("target_content_node_id"); - - b.Property("TargetQuestionBankId") - .HasColumnType("uuid") - .HasColumnName("target_question_bank_id"); - - b.Property("TargetRegionId") - .HasColumnType("uuid") - .HasColumnName("target_region_id"); - - b.Property("TargetSubjectId") - .HasColumnType("uuid") - .HasColumnName("target_subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCount") - .HasColumnType("integer") - .HasColumnName("total_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedCount") - .HasColumnType("integer") - .HasColumnName("updated_count"); - - b.Property("ValidCount") - .HasColumnType("integer") - .HasColumnName("valid_count"); - - b.Property("WarningCount") - .HasColumnType("integer") - .HasColumnName("warning_count"); - - b.HasKey("Id") - .HasName("pk_content_import_jobs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_import_jobs_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_import_jobs_created_by"); - - b.HasIndex("TenantId", "TargetCategoryId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_category_id"); - - b.HasIndex("TenantId", "TargetContentNodeId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_content_node_id"); - - b.HasIndex("TenantId", "TargetQuestionBankId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_question_bank_id"); - - b.HasIndex("TenantId", "TargetRegionId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_region_id"); - - b.HasIndex("TenantId", "TargetSubjectId") - .HasDatabaseName("ix_content_import_jobs_tenant_id_target_subject_id"); - - b.HasIndex("TenantId", "ImportType", "Status", "CreatedAt") - .HasDatabaseName("ix_content_import_jobs_tenant_id_import_type_status_created_at"); - - b.ToTable("content_import_jobs", null, t => - { - t.HasCheckConstraint("ck_content_import_jobs_counts", "total_count >= 0 and valid_count >= 0 and error_count >= 0 and warning_count >= 0 and inserted_count >= 0 and updated_count >= 0 and skipped_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentNode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Depth") - .HasColumnType("integer") - .HasColumnName("depth"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsLeaf") - .HasColumnType("boolean") - .HasColumnName("is_leaf"); - - b.Property("IsSelectable") - .HasColumnType("boolean") - .HasColumnName("is_selectable"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MarkerConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("marker_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MarkerType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("marker_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("node_key"); - - b.Property("NodeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("node_type"); - - b.Property("ParentId") - .HasColumnType("uuid") - .HasColumnName("parent_id"); - - b.Property("Path") - .HasColumnType("ltree") - .HasColumnName("path"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_content_nodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_content_nodes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_content_nodes_created_by"); - - b.HasIndex("Path") - .HasDatabaseName("ix_content_nodes_path"); - - NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Path"), "gist"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_content_nodes_updated_by"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_content_nodes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MarkerType") - .HasDatabaseName("ix_content_nodes_tenant_id_marker_type") - .HasFilter("marker_type is not null"); - - b.HasIndex("TenantId", "ParentId") - .HasDatabaseName("ix_content_nodes_tenant_id_parent_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_content_nodes_tenant_id_region_id"); - - b.HasIndex("TenantId", "EntryId", "NodeKey") - .IsUnique() - .HasDatabaseName("ix_content_nodes_tenant_id_entry_id_node_key"); - - b.HasIndex("TenantId", "EntryId", "ParentId", "SortOrder") - .HasDatabaseName("ix_content_nodes_tenant_id_entry_id_parent_id_sort_order"); - - b.ToTable("content_nodes", null, t => - { - t.HasCheckConstraint("ck_content_nodes_depth", "depth >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookChapter", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_chapters"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_chapters_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_chapters_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_chapters_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_handbook_chapters_tenant_id_subject_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_chapters_tenant_id_entry_id_content_node_id_subjec~"); - - b.ToTable("handbook_chapters", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ChapterId") - .HasColumnType("uuid") - .HasColumnName("chapter_id"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("Summary") - .HasColumnType("text") - .HasColumnName("summary"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_entries"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_entries_tenant_id_id"); - - b.HasIndex("TenantId", "ChapterId") - .HasDatabaseName("ix_handbook_entries_tenant_id_chapter_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_entries_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_entries_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "ChapterId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_entries_tenant_id_entry_id_content_node_id_chapter~"); - - b.ToTable("handbook_entries", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookSubject", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Icon") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("MajorId") - .HasColumnType("uuid") - .HasColumnName("major_id"); - - b.Property("MajorLegacyIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("major_legacy_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_handbook_subjects"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_handbook_subjects_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_handbook_subjects_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "MajorId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_major_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId") - .HasDatabaseName("ix_handbook_subjects_tenant_id_school_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_handbook_subjects_tenant_id_entry_id_content_node_id_region~"); - - b.ToTable("handbook_subjects", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ImageAsset", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("CdnUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("cdn_url"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FileName") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("file_name"); - - b.Property("IsPublic") - .HasColumnType("boolean") - .HasColumnName("is_public"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_images"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_images_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_images_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "Category", "IsPublic") - .HasDatabaseName("ix_images_tenant_id_category_is_public"); - - b.ToTable("images", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.PracticeBlueprint", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssemblyType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assembly_type"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("mode"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("PassScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("pass_score"); - - b.Property("QuestionLimit") - .HasColumnType("integer") - .HasColumnName("question_limit"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Rules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Sections") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("sections") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_practice_blueprints"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_blueprints_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_practice_blueprints_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_practice_blueprints_updated_by"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_collection_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_practice_blueprints_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_practice_blueprints_tenant_id_region_id"); - - b.HasIndex("TenantId", "NodeId", "CollectionId", "Mode", "Status", "SortOrder") - .HasDatabaseName("ix_practice_blueprints_tenant_id_node_id_collection_id_mode_st~"); - - b.ToTable("practice_blueprints", null, t => - { - t.HasCheckConstraint("ck_practice_blueprints_duration", "duration_minutes is null or duration_minutes > 0"); - - t.HasCheckConstraint("ck_practice_blueprints_question_limit", "question_limit is null or question_limit > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessRules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_rules") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("CollectionType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("collection_type"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Filters") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("filters") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_question_collections"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_collections_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_question_collections_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_question_collections_updated_by"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_question_collections_tenant_id_category_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_question_collections_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_collections_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "QuestionBankId") - .HasDatabaseName("ix_question_collections_tenant_id_question_bank_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_question_collections_tenant_id_region_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_question_collections_tenant_id_subject_id"); - - b.HasIndex("TenantId", "NodeId", "Status", "SortOrder") - .HasDatabaseName("ix_question_collections_tenant_id_node_id_status_sort_order"); - - b.ToTable("question_collections", null, t => - { - t.HasCheckConstraint("ck_question_collections_duration", "duration_minutes is null or duration_minutes > 0"); - - t.HasCheckConstraint("ck_question_collections_question_count", "question_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollectionItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("Required") - .HasColumnType("boolean") - .HasColumnName("required"); - - b.Property("Score") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("score"); - - b.Property("SectionKey") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("section_key"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_collection_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_collection_items_tenant_id_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_question_collection_items_question_owner_tenant_id_question~"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_question_collection_items_tenant_id_question_reference_id"); - - b.HasIndex("TenantId", "CollectionId", "QuestionReferenceId") - .IsUnique() - .HasDatabaseName("ix_question_collection_items_tenant_id_collection_id_question_~"); - - b.HasIndex("TenantId", "CollectionId", "SectionKey", "SortOrder") - .HasDatabaseName("ix_question_collection_items_tenant_id_collection_id_section_k~"); - - b.ToTable("question_collection_items", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionTypeGroup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("display_name"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Types") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("types") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_type_groups"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_type_groups_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_type_groups_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_question_type_groups_tenant_id_subject_id_is_active_sort_or~"); - - b.ToTable("question_type_groups", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionVideo", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyQuestionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_question_id"); - - b.Property("LegacyVideoId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_video_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VideoId") - .HasColumnType("uuid") - .HasColumnName("video_id"); - - b.Property("VideoType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("video_type"); - - b.HasKey("Id") - .HasName("pk_question_videos"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_videos_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_question_videos_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "VideoId") - .HasDatabaseName("ix_question_videos_tenant_id_video_id"); - - b.HasIndex("TenantId", "QuestionId", "VideoId", "VideoType") - .IsUnique() - .HasDatabaseName("ix_question_videos_tenant_id_question_id_video_id_video_type"); - - b.ToTable("question_videos", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.SubjectShare", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("SourceSubjectId") - .HasColumnType("uuid") - .HasColumnName("source_subject_id"); - - b.Property("TargetSubjectId") - .HasColumnType("uuid") - .HasColumnName("target_subject_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Id") - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacySourceSubjectId") - .HasColumnType("text") - .HasColumnName("legacy_source_subject_id"); - - b.Property("LegacyTargetSubjectId") - .HasColumnType("text") - .HasColumnName("legacy_target_subject_id"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at"); - - b.HasKey("TenantId", "SourceSubjectId", "TargetSubjectId") - .HasName("pk_subject_shares"); - - b.HasIndex("TenantId", "TargetSubjectId") - .HasDatabaseName("ix_subject_shares_tenant_id_target_subject_id"); - - b.ToTable("subject_shares", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionBankPreference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Alias") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("alias"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("IsVisible") - .HasColumnType("boolean") - .HasColumnName("is_visible"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NavigationLocation") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("navigation_location"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("QuestionBankOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_bank_owner_tenant_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_question_bank_preferences"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_question_bank_preferences_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_question_bank_preferences_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_question_bank_preferences_updated_by"); - - b.HasIndex("QuestionBankOwnerTenantId", "QuestionBankId") - .HasDatabaseName("ix_tenant_question_bank_preferences_question_bank_owner_tenant~"); - - b.HasIndex("TenantId", "QuestionBankOwnerTenantId", "QuestionBankId") - .IsUnique() - .HasDatabaseName("ix_tenant_question_bank_preferences_tenant_id_question_bank_ow~"); - - b.ToTable("tenant_question_bank_preferences", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionReference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_question_references"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_question_references_tenant_id_id"); - - b.HasAlternateKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasName("ak_tenant_question_references_tenant_id_question_owner_tenant_~"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_question_references_created_by"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_tenant_question_references_question_owner_tenant_id_questio~"); - - b.ToTable("tenant_question_references", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordFavorite", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FavoritedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("favorited_at"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacyUserId") - .HasColumnType("text") - .HasColumnName("legacy_user_id"); - - b.Property("LegacyWordId") - .HasColumnType("text") - .HasColumnName("legacy_word_id"); - - b.Property("Note") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WordId") - .HasColumnType("uuid") - .HasColumnName("word_id"); - - b.HasKey("Id") - .HasName("pk_user_word_favorites"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_word_favorites_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_word_favorites_user_id"); - - b.HasIndex("TenantId", "WordId") - .HasDatabaseName("ix_user_word_favorites_tenant_id_word_id"); - - b.HasIndex("TenantId", "UserId", "WordId") - .IsUnique() - .HasDatabaseName("ix_user_word_favorites_tenant_id_user_id_word_id"); - - b.ToTable("user_word_favorites", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordProgress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CorrectStreak") - .HasColumnType("integer") - .HasColumnName("correct_streak"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DueLevel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("due_level"); - - b.Property("EaseFactor") - .ValueGeneratedOnAdd() - .HasPrecision(4, 2) - .HasColumnType("numeric(4,2)") - .HasDefaultValue(2.50m) - .HasColumnName("ease_factor"); - - b.Property("LastResult") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("last_result"); - - b.Property("LastReviewAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_review_at"); - - b.Property("LegacyId") - .HasColumnType("text") - .HasColumnName("legacy_id"); - - b.Property("LegacyUserId") - .HasColumnType("text") - .HasColumnName("legacy_user_id"); - - b.Property("LegacyWordId") - .HasColumnType("text") - .HasColumnName("legacy_word_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NextReviewAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_review_at"); - - b.Property("ReviewCount") - .HasColumnType("integer") - .HasColumnName("review_count"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WordId") - .HasColumnType("uuid") - .HasColumnName("word_id"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.HasKey("Id") - .HasName("pk_user_word_progress"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_word_progress_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_word_progress_user_id"); - - b.HasIndex("TenantId", "WordId") - .HasDatabaseName("ix_user_word_progress_tenant_id_word_id"); - - b.HasIndex("TenantId", "UserId", "WordId") - .IsUnique() - .HasDatabaseName("ix_user_word_progress_tenant_id_user_id_word_id"); - - b.HasIndex("TenantId", "UserId", "NextReviewAt", "Status", "DueLevel") - .HasDatabaseName("ix_user_word_progress_tenant_id_user_id_next_review_at_status_~"); - - b.ToTable("user_word_progress", null, t => - { - t.HasCheckConstraint("ck_user_word_progress_correct_streak", "correct_streak >= 0"); - - t.HasCheckConstraint("ck_user_word_progress_ease_factor", "ease_factor >= 1.30 and ease_factor <= 3.00"); - - t.HasCheckConstraint("ck_user_word_progress_review_count", "review_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoExplanation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("DurationSeconds") - .HasColumnType("integer") - .HasColumnName("duration_seconds"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("IsGeneral") - .HasColumnType("boolean") - .HasColumnName("is_general"); - - b.Property("KnowledgeTags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("knowledge_tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacySubjectId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_subject_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ThumbnailUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("thumbnail_url"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VideoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("video_url"); - - b.HasKey("Id") - .HasName("pk_video_explanations"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_video_explanations_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_video_explanations_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "SourceHash") - .HasDatabaseName("ix_video_explanations_tenant_id_source_hash") - .HasFilter("source_hash is not null"); - - b.HasIndex("TenantId", "SubjectId", "IsActive", "SortOrder") - .HasDatabaseName("ix_video_explanations_tenant_id_subject_id_is_active_sort_order"); - - b.ToTable("video_explanations", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoPlaybackProgress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DurationSeconds") - .HasColumnType("integer") - .HasColumnName("duration_seconds"); - - b.Property("IsCompleted") - .HasColumnType("boolean") - .HasColumnName("is_completed"); - - b.Property("LastPlayedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_played_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PlayCount") - .HasColumnType("integer") - .HasColumnName("play_count"); - - b.Property("PositionSeconds") - .HasColumnType("integer") - .HasColumnName("position_seconds"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("VideoId") - .HasColumnType("uuid") - .HasColumnName("video_id"); - - b.Property("WatchedSeconds") - .HasColumnType("integer") - .HasColumnName("watched_seconds"); - - b.HasKey("Id") - .HasName("pk_video_playback_progress"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_video_playback_progress_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_video_playback_progress_user_id"); - - b.HasIndex("TenantId", "QuestionId") - .HasDatabaseName("ix_video_playback_progress_tenant_id_question_id"); - - b.HasIndex("TenantId", "VideoId") - .HasDatabaseName("ix_video_playback_progress_tenant_id_video_id"); - - b.HasIndex("TenantId", "UserId", "LastPlayedAt") - .HasDatabaseName("ix_video_playback_progress_tenant_id_user_id_last_played_at"); - - b.HasIndex("TenantId", "UserId", "VideoId") - .IsUnique() - .HasDatabaseName("ix_video_playback_progress_tenant_id_user_id_video_id") - .HasFilter("question_id is null"); - - b.HasIndex("TenantId", "UserId", "VideoId", "QuestionId") - .IsUnique() - .HasDatabaseName("ix_video_playback_progress_tenant_id_user_id_video_id_question~") - .HasFilter("question_id is not null"); - - b.ToTable("video_playback_progress", null, t => - { - t.HasCheckConstraint("ck_video_playback_progress_duration", "duration_seconds is null or duration_seconds >= 0"); - - t.HasCheckConstraint("ck_video_playback_progress_play_count", "play_count >= 0"); - - t.HasCheckConstraint("ck_video_playback_progress_position", "position_seconds >= 0"); - - t.HasCheckConstraint("ck_video_playback_progress_watched", "watched_seconds >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyUnit", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WordCount") - .HasColumnType("integer") - .HasColumnName("word_count"); - - b.HasKey("Id") - .HasName("pk_vocabulary_units"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_vocabulary_units_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_vocabulary_units_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_vocabulary_units_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_vocabulary_units_tenant_id_region_id"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_vocabulary_units_tenant_id_entry_id_content_node_id_region_~"); - - b.ToTable("vocabulary_units", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyWord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("Example") - .HasColumnType("text") - .HasColumnName("example"); - - b.Property("ExampleTranslation") - .HasColumnType("text") - .HasColumnName("example_translation"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Meaning") - .HasColumnType("text") - .HasColumnName("meaning"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Phonetic") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("phonetic"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitId") - .HasColumnType("uuid") - .HasColumnName("unit_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Word") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("word"); - - b.HasKey("Id") - .HasName("pk_vocabulary_words"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_vocabulary_words_tenant_id_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_vocabulary_words_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_vocabulary_words_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UnitId", "IsActive", "SortOrder", "CreatedAt") - .HasDatabaseName("ix_vocabulary_words_tenant_id_unit_id_is_active_sort_order_cre~"); - - b.HasIndex("TenantId", "EntryId", "ContentNodeId", "UnitId", "IsActive", "SortOrder") - .HasDatabaseName("ix_vocabulary_words_tenant_id_entry_id_content_node_id_unit_id~"); - - b.ToTable("vocabulary_words", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CommissionAmountCents") - .HasColumnType("integer") - .HasColumnName("commission_amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DefaultRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("default_rate"); - - b.Property("EffectiveRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("effective_rate"); - - b.Property("GeneratedBy") - .HasColumnType("uuid") - .HasColumnName("generated_by"); - - b.Property("GrossAmountCents") - .HasColumnType("integer") - .HasColumnName("gross_amount_cents"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaidBy") - .HasColumnType("uuid") - .HasColumnName("paid_by"); - - b.Property("PaidUserCount") - .HasColumnType("integer") - .HasColumnName("paid_user_count"); - - b.Property("PaymentAccount") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("payment_account"); - - b.Property("PaymentMethod") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_method"); - - b.Property("PeriodEnd") - .HasColumnType("date") - .HasColumnName("period_end"); - - b.Property("PeriodStart") - .HasColumnType("date") - .HasColumnName("period_start"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Remark") - .HasColumnType("text") - .HasColumnName("remark"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("SettlementNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("settlement_no"); - - b.Property("SourceCount") - .HasColumnType("integer") - .HasColumnName("source_count"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlements_tenant_id_id"); - - b.HasIndex("GeneratedBy") - .HasDatabaseName("ix_commission_settlements_generated_by"); - - b.HasIndex("PaidBy") - .HasDatabaseName("ix_commission_settlements_paid_by"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_commission_settlements_referrer_user_id"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commission_settlements_reviewed_by"); - - b.HasIndex("TenantId", "SettlementNo") - .IsUnique() - .HasDatabaseName("ix_commission_settlements_tenant_id_settlement_no"); - - b.HasIndex("TenantId", "ReferrerUserId", "PeriodStart") - .HasDatabaseName("ix_commission_settlements_referrer_start"); - - b.HasIndex("TenantId", "ReferrerUserId", "PeriodStart", "PeriodEnd") - .IsUnique() - .HasDatabaseName("ux_commission_settlements_referrer_period"); - - b.ToTable("commission_settlements", null, t => - { - t.HasCheckConstraint("ck_commission_settlements_amounts", "gross_amount_cents >= 0 and commission_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commission_settlements_counts", "source_count >= 0 and paid_user_count >= 0"); - - t.HasCheckConstraint("ck_commission_settlements_period", "period_end >= period_start"); - - t.HasCheckConstraint("ck_commission_settlements_rates", "default_rate >= 0 and default_rate <= 1 and (effective_rate is null or (effective_rate >= 0 and effective_rate <= 1))"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementExportEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ContentSha256") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("content_sha256"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExportFormat") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)") - .HasColumnName("export_format"); - - b.Property("ExportedBy") - .HasColumnType("uuid") - .HasColumnName("exported_by"); - - b.Property("Filename") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("filename"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RowCount") - .HasColumnType("integer") - .HasColumnName("row_count"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_export_events"); - - b.HasIndex("ExportedBy") - .HasDatabaseName("ix_commission_settlement_export_events_exported_by"); - - b.HasIndex("TenantId", "SettlementId", "CreatedAt") - .HasDatabaseName("ix_commission_settlement_export_events_tenant_id_settlement_id~"); - - b.ToTable("commission_settlement_export_events", null, t => - { - t.HasCheckConstraint("ck_commission_export_events_rows", "row_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AttributionType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("attribution_type"); - - b.Property("CommissionAmountCents") - .HasColumnType("integer") - .HasColumnName("commission_amount_cents"); - - b.Property("CommissionRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("commission_rate"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("GrossAmountCents") - .HasColumnType("integer") - .HasColumnName("gross_amount_cents"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RateSource") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("rate_source"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceNo") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_no"); - - b.Property("SourcePaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("source_paid_at"); - - b.Property("SourceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_type"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlement_items_tenant_id_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_commission_settlement_items_referrer_user_id"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_commission_settlement_items_student_user_id"); - - b.HasIndex("TenantId", "SettlementId") - .HasDatabaseName("ix_commission_settlement_items_tenant_id_settlement_id"); - - b.HasIndex("TenantId", "ReferrerUserId", "SourcePaidAt") - .HasDatabaseName("ix_commission_settlement_items_tenant_id_referrer_user_id_sour~"); - - b.HasIndex("TenantId", "SourceType", "SourceId") - .IsUnique() - .HasDatabaseName("ix_commission_settlement_items_tenant_id_source_type_source_id"); - - b.ToTable("commission_settlement_items", null, t => - { - t.HasCheckConstraint("ck_commission_settlement_items_amounts", "gross_amount_cents >= 0 and commission_amount_cents >= 0"); - - t.HasCheckConstraint("ck_commission_settlement_items_rate", "commission_rate >= 0 and commission_rate <= 1"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementProof", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("AssetId") - .HasColumnType("uuid") - .HasColumnName("asset_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("ExternalUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("external_url"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentAccount") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("payment_account"); - - b.Property("PaymentMethod") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_method"); - - b.Property("ProofType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("proof_type"); - - b.Property("ReviewNote") - .HasColumnType("text") - .HasColumnName("review_note"); - - b.Property("ReviewedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("reviewed_at"); - - b.Property("ReviewedBy") - .HasColumnType("uuid") - .HasColumnName("reviewed_by"); - - b.Property("SettlementId") - .HasColumnType("uuid") - .HasColumnName("settlement_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubmittedBy") - .HasColumnType("uuid") - .HasColumnName("submitted_by"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_commission_settlement_proofs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_commission_settlement_proofs_tenant_id_id"); - - b.HasIndex("ReviewedBy") - .HasDatabaseName("ix_commission_settlement_proofs_reviewed_by"); - - b.HasIndex("SubmittedBy") - .HasDatabaseName("ix_commission_settlement_proofs_submitted_by"); - - b.HasIndex("TenantId", "AssetId") - .HasDatabaseName("ix_commission_settlement_proofs_tenant_id_asset_id"); - - b.HasIndex("TenantId", "SettlementId", "Status", "CreatedAt") - .HasDatabaseName("ix_commission_settlement_proofs_tenant_id_settlement_id_status~"); - - b.ToTable("commission_settlement_proofs", null, t => - { - t.HasCheckConstraint("ck_commission_settlement_proofs_amount", "amount_cents is null or amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignmentConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("assignment_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("AssignmentCursor") - .HasColumnType("integer") - .HasColumnName("assignment_cursor"); - - b.Property("AssignmentMode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assignment_mode"); - - b.Property("AssignmentPool") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("assignment_pool") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DelaySeconds") - .HasColumnType("integer") - .HasColumnName("delay_seconds"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("ExamType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("exam_type"); - - b.Property("FormName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("form_name"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TimeoutSeconds") - .HasColumnType("integer") - .HasColumnName("timeout_seconds"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("Url") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("url"); - - b.HasKey("Id") - .HasName("pk_crm_config"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_config_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_crm_config_tenant_id"); - - b.HasIndex("TenantId", "AssignmentMode") - .HasDatabaseName("ix_crm_config_tenant_id_assignment_mode"); - - b.ToTable("crm_config", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempt") - .HasColumnType("integer") - .HasColumnName("attempt"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("HttpCode") - .HasColumnType("integer") - .HasColumnName("http_code"); - - b.Property("LeadId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Outcome") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("outcome"); - - b.Property("RecordId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("record_id"); - - b.Property("RequestBody") - .HasColumnType("text") - .HasColumnName("request_body"); - - b.Property("RequestPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("request_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ResponseSummary") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("response_summary"); - - b.Property("SignedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("signed_at"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_crm_webhook_log"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_webhook_log_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_log_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RecordId", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_log_tenant_id_record_id_created_at"); - - b.ToTable("crm_webhook_log", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookQueueItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IdempotencyKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("idempotency_key"); - - b.Property("LastAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_attempt_at"); - - b.Property("LastError") - .HasColumnType("text") - .HasColumnName("last_error"); - - b.Property("LastHttpCode") - .HasColumnType("integer") - .HasColumnName("last_http_code"); - - b.Property("LastResponseSummary") - .HasColumnType("text") - .HasColumnName("last_response_summary"); - - b.Property("LeadId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("NextAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_attempt_at"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Provider") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("RecordId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("record_id"); - - b.Property("ScheduledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("scheduled_at"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Source") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TargetUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("target_url"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_crm_webhook_queue"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_crm_webhook_queue_tenant_id_id"); - - b.HasIndex("TenantId", "IdempotencyKey") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_idempotency_key") - .HasFilter("idempotency_key is not null"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RecordId") - .IsUnique() - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_record_id") - .HasFilter("record_id is not null"); - - b.HasIndex("TenantId", "RecordId", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_record_id_created_at") - .HasFilter("record_id is not null"); - - b.HasIndex("TenantId", "Source", "CreatedAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_source_created_at") - .HasFilter("source = 'tenant.student.crm_push'"); - - b.HasIndex("TenantId", "Status", "ScheduledAt", "NextAttemptAt") - .HasDatabaseName("ix_crm_webhook_queue_tenant_id_status_scheduled_at_next_attemp~"); - - b.ToTable("crm_webhook_queue", null, t => - { - t.HasCheckConstraint("ck_crm_webhook_queue_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Channel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LandingPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("landing_path"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_referral_codes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_codes_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_referral_codes_user_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_referral_codes_tenant_id_code"); - - b.HasIndex("TenantId", "UserId") - .IsUnique() - .HasDatabaseName("ix_referral_codes_tenant_id_user_id"); - - b.HasIndex("TenantId", "Code", "Status") - .HasDatabaseName("ix_referral_codes_tenant_id_code_status"); - - b.ToTable("referral_codes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralLead", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("assigned_at"); - - b.Property("AssignedBy") - .HasColumnType("uuid") - .HasColumnName("assigned_by"); - - b.Property("AssignedToUserId") - .HasColumnType("uuid") - .HasColumnName("assigned_to_user_id"); - - b.Property("AssignmentMode") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("assignment_mode"); - - b.Property("BindType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("bind_type"); - - b.Property("BoundAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("bound_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FirstTrackId") - .HasColumnType("uuid") - .HasColumnName("first_track_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ProtectedUntil") - .HasColumnType("timestamp with time zone") - .HasColumnName("protected_until"); - - b.Property("RefCode") - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Source") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_referral_leads"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_leads_tenant_id_id"); - - b.HasIndex("AssignedBy") - .HasDatabaseName("ix_referral_leads_assigned_by"); - - b.HasIndex("AssignedToUserId") - .HasDatabaseName("ix_referral_leads_assigned_to_user_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_referral_leads_referrer_user_id"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_referral_leads_student_user_id"); - - b.HasIndex("TenantId", "FirstTrackId") - .HasDatabaseName("ix_referral_leads_tenant_id_first_track_id"); - - b.HasIndex("TenantId", "StudentUserId") - .IsUnique() - .HasDatabaseName("ix_referral_leads_tenant_id_student_user_id"); - - b.HasIndex("TenantId", "ReferrerUserId", "BoundAt") - .HasDatabaseName("ix_referral_leads_tenant_id_referrer_user_id_bound_at"); - - b.HasIndex("TenantId", "AssignedToUserId", "Status", "BoundAt") - .HasDatabaseName("ix_referral_leads_tenant_id_assigned_to_user_id_status_bound_at"); - - b.ToTable("referral_leads", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralQrcode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ErrorMessage") - .HasColumnType("text") - .HasColumnName("error_message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Page") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("page"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("provider"); - - b.Property("QrcodeUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("qrcode_url"); - - b.Property("RefCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("Scene") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("scene"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_referral_qrcodes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_qrcodes_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_referral_qrcodes_user_id"); - - b.HasIndex("TenantId", "RefCode", "Status") - .HasDatabaseName("ix_referral_qrcodes_tenant_id_ref_code_status"); - - b.HasIndex("TenantId", "Provider", "Scene", "Page") - .IsUnique() - .HasDatabaseName("ix_referral_qrcodes_tenant_id_provider_scene_page"); - - b.ToTable("referral_qrcodes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTeamEdge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LeaderUserId") - .HasColumnType("uuid") - .HasColumnName("leader_user_id"); - - b.Property("MemberUserId") - .HasColumnType("uuid") - .HasColumnName("member_user_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RelationType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("relation_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_referral_team_edges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_team_edges_tenant_id_id"); - - b.HasIndex("LeaderUserId") - .HasDatabaseName("ix_referral_team_edges_leader_user_id"); - - b.HasIndex("MemberUserId") - .HasDatabaseName("ix_referral_team_edges_member_user_id"); - - b.HasIndex("TenantId", "LeaderUserId", "Status") - .HasDatabaseName("ix_referral_team_edges_tenant_id_leader_user_id_status"); - - b.HasIndex("TenantId", "MemberUserId", "RelationType") - .IsUnique() - .HasDatabaseName("ix_referral_team_edges_tenant_id_member_user_id_relation_type"); - - b.ToTable("referral_team_edges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTrack", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("event_type"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("LeadId") - .HasColumnType("uuid") - .HasColumnName("lead_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RefCode") - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("ref_code"); - - b.Property("ReferrerUserId") - .HasColumnType("uuid") - .HasColumnName("referrer_user_id"); - - b.Property("Source") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source"); - - b.Property("TargetUserId") - .HasColumnType("uuid") - .HasColumnName("target_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_referral_tracks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_referral_tracks_tenant_id_id"); - - b.HasIndex("ReferrerUserId") - .HasDatabaseName("ix_referral_tracks_referrer_user_id"); - - b.HasIndex("TargetUserId") - .HasDatabaseName("ix_referral_tracks_target_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_referral_tracks_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "LeadId", "CreatedAt") - .HasDatabaseName("ix_referral_tracks_tenant_id_lead_id_created_at"); - - b.HasIndex("TenantId", "RefCode", "EventType", "CreatedAt") - .HasDatabaseName("ix_referral_tracks_tenant_id_ref_code_event_type_created_at"); - - b.ToTable("referral_tracks", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.TenantCommissionSetting", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Config") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DefaultRate") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("default_rate"); - - b.Property("MinSettlementCents") - .HasColumnType("integer") - .HasColumnName("min_settlement_cents"); - - b.Property("SettlementCycle") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("settlement_cycle"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_commission_settings"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_commission_settings_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_commission_settings_tenant_id"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_commission_settings_updated_by"); - - b.ToTable("tenant_commission_settings", null, t => - { - t.HasCheckConstraint("ck_tenant_commission_settings_default_rate", "default_rate >= 0 and default_rate <= 1"); - - t.HasCheckConstraint("ck_tenant_commission_settings_min_settlement", "min_settlement_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.StudentProfile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AvatarPreset") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("male") - .HasColumnName("avatar_preset"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LastCheckInDate") - .HasColumnType("date") - .HasColumnName("last_check_in_date"); - - b.Property("LegacyUserId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_user_id"); - - b.Property("MasteredWordsCount") - .HasColumnType("integer") - .HasColumnName("mastered_words_count"); - - b.Property("ModuleSelections") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("module_selections") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Progress") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("progress") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("QuestionsAnsweredToday") - .HasColumnType("integer") - .HasColumnName("questions_answered_today"); - - b.Property("RecentActivities") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("recent_activities") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SelectedMajorId") - .HasColumnType("uuid") - .HasColumnName("selected_major_id"); - - b.Property("SelectedSchoolId") - .HasColumnType("uuid") - .HasColumnName("selected_school_id"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_student_profiles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_student_profiles_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_student_profiles_user_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_student_profiles_tenant_id_region_id"); - - b.HasIndex("TenantId", "SelectedMajorId") - .HasDatabaseName("ix_student_profiles_tenant_id_selected_major_id"); - - b.HasIndex("TenantId", "SelectedSchoolId") - .HasDatabaseName("ix_student_profiles_tenant_id_selected_school_id"); - - b.HasIndex("TenantId", "UserId") - .IsUnique() - .HasDatabaseName("ix_student_profiles_tenant_id_user_id"); - - b.ToTable("student_profiles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessFailedCount") - .HasColumnType("integer") - .HasColumnName("access_failed_count"); - - b.Property("AvatarUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("avatar_url"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("concurrency_stamp"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Email") - .HasMaxLength(320) - .HasColumnType("citext") - .HasColumnName("email"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean") - .HasColumnName("email_confirmed"); - - b.Property("ForcePasswordChange") - .HasColumnType("boolean") - .HasColumnName("force_password_change"); - - b.Property("LastSeenAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_seen_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean") - .HasColumnName("lockout_enabled"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone") - .HasColumnName("lockout_end"); - - b.Property("Name") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("NormalizedEmail") - .HasMaxLength(320) - .HasColumnType("character varying(320)") - .HasColumnName("normalized_email"); - - b.Property("NormalizedUserName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("normalized_user_name"); - - b.Property("PasswordHash") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("password_hash"); - - b.Property("Phone") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("PhoneNumber") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone_number"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean") - .HasColumnName("phone_number_confirmed"); - - b.Property("PrimaryRole") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("primary_role"); - - b.Property("RawProfile") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_profile") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Score") - .HasColumnType("integer") - .HasColumnName("score"); - - b.Property("SecurityStamp") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("security_stamp"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean") - .HasColumnName("two_factor_enabled"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("user_name"); - - b.HasKey("Id") - .HasName("pk_users"); - - b.HasIndex("LegacyId") - .IsUnique() - .HasDatabaseName("ix_users_legacy_id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("email_index"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("user_name_index"); - - b.HasIndex("Phone") - .IsUnique() - .HasDatabaseName("ix_users_phone"); - - b.ToTable("users", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.UserIdentity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Email") - .HasMaxLength(320) - .HasColumnType("citext") - .HasColumnName("email"); - - b.Property("OpenId") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("open_id"); - - b.Property("Phone") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderSubject") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("provider_subject"); - - b.Property("UnionId") - .HasMaxLength(255) - .HasColumnType("character varying(255)") - .HasColumnName("union_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_identities"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_identities_user_id"); - - b.HasIndex("Provider", "ProviderSubject") - .IsUnique() - .HasDatabaseName("ix_user_identities_provider_provider_subject"); - - b.ToTable("user_identities", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportIssue", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CollectionName") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("collection_name"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FieldPath") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("field_path"); - - b.Property("IssueCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("issue_code"); - - b.Property("LegacyId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_id"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("RawValueSample") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("raw_value_sample"); - - b.Property("RunId") - .HasColumnType("uuid") - .HasColumnName("run_id"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_pb_import_issues"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_pb_import_issues_tenant_id_id"); - - b.HasIndex("TenantId", "RunId") - .HasDatabaseName("ix_pb_import_issues_tenant_id_run_id"); - - b.HasIndex("RunId", "Severity", "CollectionName") - .HasDatabaseName("ix_pb_import_issues_run_id_severity_collection_name"); - - b.HasIndex("TenantId", "CollectionName", "LegacyId") - .HasDatabaseName("ix_pb_import_issues_tenant_id_collection_name_legacy_id"); - - b.ToTable("pb_import_issues", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportRun", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("SourceKind") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("source_kind"); - - b.Property("SourceName") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("source_name"); - - b.Property("StartedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at") - .HasDefaultValueSql("now()"); - - b.Property("Stats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("stats") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_pb_import_runs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_pb_import_runs_tenant_id_id"); - - b.HasIndex("TenantId", "Status", "StartedAt") - .HasDatabaseName("ix_pb_import_runs_tenant_id_status_started_at"); - - b.ToTable("pb_import_runs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseRawRecord", b => - { - b.Property("RunId") - .HasColumnType("uuid") - .HasColumnName("run_id"); - - b.Property("CollectionName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("collection_name"); - - b.Property("LegacyId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("legacy_id"); - - b.Property("Errors") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("errors") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("ImportedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("imported_at") - .HasDefaultValueSql("now()"); - - b.Property("Normalized") - .HasColumnType("boolean") - .HasColumnName("normalized"); - - b.Property("Record") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("record") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("RunId", "CollectionName", "LegacyId") - .HasName("pk_pb_raw_records"); - - b.HasIndex("TenantId", "RunId") - .HasDatabaseName("ix_pb_raw_records_tenant_id_run_id"); - - b.HasIndex("TenantId", "CollectionName", "LegacyId") - .HasDatabaseName("ix_pb_raw_records_tenant_id_collection_name_legacy_id"); - - b.ToTable("pb_raw_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.AnswerRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AnswerText") - .HasColumnType("text") - .HasColumnName("answer_text"); - - b.Property("AnsweredAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("answered_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsCorrect") - .HasColumnType("boolean") - .HasColumnName("is_correct"); - - b.Property("LegacyCategoryId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_category_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyQuestionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_question_id"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("SelectedOptions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("selected_options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("SessionQuestionId") - .HasColumnType("uuid") - .HasColumnName("session_question_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_answer_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_answer_records_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_answer_records_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_answer_records_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "PracticeSessionId", "SessionQuestionId") - .HasDatabaseName("ix_answer_records_tenant_id_practice_session_id_session_questi~"); - - b.HasIndex("TenantId", "UserId", "PracticeSessionId") - .HasDatabaseName("ix_answer_records_tenant_id_user_id_practice_session_id"); - - b.ToTable("answer_records", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.DashboardDailyStat", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActiveUsers") - .HasColumnType("integer") - .HasColumnName("active_users"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("NewOrders") - .HasColumnType("integer") - .HasColumnName("new_orders"); - - b.Property("NewQuestions") - .HasColumnType("integer") - .HasColumnName("new_questions"); - - b.Property("NewRevenueCents") - .HasColumnType("integer") - .HasColumnName("new_revenue_cents"); - - b.Property("NewUsers") - .HasColumnType("integer") - .HasColumnName("new_users"); - - b.Property("RebuiltAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rebuilt_at"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("StatDate") - .HasColumnType("date") - .HasColumnName("stat_date"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_dashboard_daily_stats"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_dashboard_daily_stats_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_region_id"); - - b.HasIndex("TenantId", "StatDate", "RegionId") - .IsUnique() - .HasDatabaseName("ix_dashboard_daily_stats_tenant_id_stat_date_region_id"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "StatDate", "RegionId"), false); - - b.ToTable("dashboard_daily_stats", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ExamDate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("ExamAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("exam_at"); - - b.Property("ExamName") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("exam_name"); - - b.Property("ExamType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("exam_type"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SchoolId") - .HasColumnType("uuid") - .HasColumnName("school_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_exam_dates"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_exam_dates_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_exam_dates_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_exam_dates_tenant_id_region_id"); - - b.HasIndex("TenantId", "SchoolId", "ExamAt") - .HasDatabaseName("ix_exam_dates_tenant_id_school_id_exam_at"); - - b.ToTable("exam_dates", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.FavoriteQuestion", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("Source") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("source"); - - b.HasKey("TenantId", "UserId", "QuestionReferenceId") - .HasName("pk_favorite_questions"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_favorite_questions_user_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_favorite_questions_question_owner_tenant_id_question_id"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_favorite_questions_tenant_id_question_reference_id"); - - b.ToTable("favorite_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeAccessEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessMode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("access_mode"); - - b.Property("ConsumedFreeQuota") - .HasColumnType("integer") - .HasColumnName("consumed_free_quota"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EntitlementId") - .HasColumnType("uuid") - .HasColumnName("entitlement_id"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("event_type"); - - b.Property("GrantedCount") - .HasColumnType("integer") - .HasColumnName("granted_count"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("Reason") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("reason"); - - b.Property("RequestedCount") - .HasColumnType("integer") - .HasColumnName("requested_count"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_access_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_access_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_access_events_user_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .HasDatabaseName("ix_practice_access_events_tenant_id_practice_session_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_practice_access_events_tenant_id_user_id_created_at"); - - b.ToTable("practice_access_events", null, t => - { - t.HasCheckConstraint("ck_practice_access_events_consumed_free_quota", "consumed_free_quota >= 0"); - - t.HasCheckConstraint("ck_practice_access_events_granted_count", "granted_count >= 0"); - - t.HasCheckConstraint("ck_practice_access_events_requested_count", "requested_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeDailyUsage", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FreeLimit") - .HasColumnType("integer") - .HasColumnName("free_limit"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ScopeId") - .HasColumnType("uuid") - .HasColumnName("scope_id"); - - b.Property("ScopeType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("scope_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UsageDate") - .ValueGeneratedOnAdd() - .HasColumnType("date") - .HasColumnName("usage_date") - .HasDefaultValueSql("current_date"); - - b.Property("UsedCount") - .HasColumnType("integer") - .HasColumnName("used_count"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_daily_usage"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_daily_usage_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_daily_usage_user_id"); - - b.HasIndex("TenantId", "UserId", "UsageDate") - .HasDatabaseName("ix_practice_daily_usage_tenant_id_user_id_usage_date"); - - b.HasIndex("TenantId", "UserId", "UsageDate", "ScopeType", "ScopeId") - .IsUnique() - .HasDatabaseName("ix_practice_daily_usage_tenant_id_user_id_usage_date_scope_typ~"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "UserId", "UsageDate", "ScopeType", "ScopeId"), false); - - b.ToTable("practice_daily_usage", null, t => - { - t.HasCheckConstraint("ck_practice_daily_usage_free_limit", "free_limit >= 0"); - - t.HasCheckConstraint("ck_practice_daily_usage_used_count", "used_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AccessEntitlementId") - .HasColumnType("uuid") - .HasColumnName("access_entitlement_id"); - - b.Property("AccessMode") - .IsRequired() - .ValueGeneratedOnAdd() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasDefaultValue("free") - .HasColumnName("access_mode"); - - b.Property("AccessSnapshot") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("access_snapshot") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("BlueprintId") - .HasColumnType("uuid") - .HasColumnName("blueprint_id"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("ConsumedFreeQuota") - .HasColumnType("integer") - .HasColumnName("consumed_free_quota"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("DurationMinutes") - .HasColumnType("integer") - .HasColumnName("duration_minutes"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("FinishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("finished_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("mode"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("StartedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at") - .HasDefaultValueSql("now()"); - - b.Property("TargetId") - .HasColumnType("uuid") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("total_score"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_practice_sessions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_sessions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "UserId", "Id") - .HasName("ak_practice_sessions_tenant_id_user_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_sessions_user_id"); - - b.HasIndex("TenantId", "BlueprintId") - .HasDatabaseName("ix_practice_sessions_tenant_id_blueprint_id"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_sessions_tenant_id_collection_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_practice_sessions_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_practice_sessions_tenant_id_entry_id"); - - b.HasIndex("TenantId", "UserId", "StartedAt") - .HasDatabaseName("ix_practice_sessions_tenant_id_user_id_started_at"); - - b.ToTable("practice_sessions", null, t => - { - t.HasCheckConstraint("ck_practice_sessions_consumed_free_quota", "consumed_free_quota >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionQuestion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Position") - .HasColumnType("integer") - .HasColumnName("position"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("QuestionVersionId") - .HasColumnType("uuid") - .HasColumnName("question_version_id"); - - b.Property("Score") - .HasPrecision(8, 2) - .HasColumnType("numeric(8,2)") - .HasColumnName("score"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_practice_session_questions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_questions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "PracticeSessionId", "Id") - .HasName("ak_practice_session_questions_tenant_id_practice_session_id_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId", "QuestionVersionId") - .HasDatabaseName("ix_practice_session_questions_question_owner_tenant_id_questio~"); - - b.HasIndex("TenantId", "PracticeSessionId", "Position") - .IsUnique() - .HasDatabaseName("ix_practice_session_questions_tenant_id_practice_session_id_po~"); - - b.HasIndex("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_practice_session_questions_tenant_id_question_owner_tenant_~"); - - b.ToTable("practice_session_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReport", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Accuracy") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("accuracy"); - - b.Property("AnsweredCount") - .HasColumnType("integer") - .HasColumnName("answered_count"); - - b.Property("BlueprintId") - .HasColumnType("uuid") - .HasColumnName("blueprint_id"); - - b.Property("CollectionId") - .HasColumnType("uuid") - .HasColumnName("collection_id"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DurationSeconds") - .HasColumnType("integer") - .HasColumnName("duration_seconds"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("mode"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionResults") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("question_results") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Score") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("score"); - - b.Property("SectionStats") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("section_stats") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("SubmittedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("submitted_at") - .HasDefaultValueSql("now()"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalQuestions") - .HasColumnType("integer") - .HasColumnName("total_questions"); - - b.Property("TotalScore") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("total_score"); - - b.Property("UnansweredCount") - .HasColumnType("integer") - .HasColumnName("unanswered_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.Property("WrongQuestionIds") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("wrong_question_ids") - .HasDefaultValueSql("'[]'::jsonb"); - - b.HasKey("Id") - .HasName("pk_practice_session_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_reports_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_practice_session_reports_user_id"); - - b.HasIndex("TenantId", "CollectionId") - .HasDatabaseName("ix_practice_session_reports_tenant_id_collection_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .IsUnique() - .HasDatabaseName("ix_practice_session_reports_tenant_id_practice_session_id"); - - b.HasIndex("TenantId", "BlueprintId", "SubmittedAt") - .HasDatabaseName("ix_practice_session_reports_tenant_id_blueprint_id_submitted_at") - .HasFilter("blueprint_id is not null"); - - b.HasIndex("TenantId", "UserId", "SubmittedAt") - .HasDatabaseName("ix_practice_session_reports_tenant_id_user_id_submitted_at"); - - b.ToTable("practice_session_reports", null, t => - { - t.HasCheckConstraint("ck_practice_session_reports_accuracy", "accuracy >= 0 and accuracy <= 1"); - - t.HasCheckConstraint("ck_practice_session_reports_counts", "total_questions >= 0 and answered_count >= 0 and correct_count >= 0 and wrong_count >= 0 and unanswered_count >= 0"); - - t.HasCheckConstraint("ck_practice_session_reports_duration", "duration_seconds >= 0"); - - t.HasCheckConstraint("ck_practice_session_reports_scores", "score >= 0 and total_score >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReportSection", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Accuracy") - .HasPrecision(6, 4) - .HasColumnType("numeric(6,4)") - .HasColumnName("accuracy"); - - b.Property("AnsweredCount") - .HasColumnType("integer") - .HasColumnName("answered_count"); - - b.Property("CorrectCount") - .HasColumnType("integer") - .HasColumnName("correct_count"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeSessionId") - .HasColumnType("uuid") - .HasColumnName("practice_session_id"); - - b.Property("QuestionCount") - .HasColumnType("integer") - .HasColumnName("question_count"); - - b.Property("QuestionType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("question_type"); - - b.Property("ReportId") - .HasColumnType("uuid") - .HasColumnName("report_id"); - - b.Property("Score") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("score"); - - b.Property("SectionKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("section_key"); - - b.Property("SectionName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("section_name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalScore") - .HasPrecision(10, 2) - .HasColumnType("numeric(10,2)") - .HasColumnName("total_score"); - - b.Property("UnansweredCount") - .HasColumnType("integer") - .HasColumnName("unanswered_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WrongCount") - .HasColumnType("integer") - .HasColumnName("wrong_count"); - - b.HasKey("Id") - .HasName("pk_practice_session_report_sections"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_practice_session_report_sections_tenant_id_id"); - - b.HasIndex("TenantId", "PracticeSessionId") - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_practice_session~"); - - b.HasIndex("TenantId", "ReportId", "SectionKey") - .IsUnique() - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_report_id_sectio~"); - - b.HasIndex("TenantId", "ReportId", "SortOrder") - .HasDatabaseName("ix_practice_session_report_sections_tenant_id_report_id_sort_o~"); - - b.ToTable("practice_session_report_sections", null, t => - { - t.HasCheckConstraint("ck_practice_session_report_sections_accuracy", "accuracy >= 0 and accuracy <= 1"); - - t.HasCheckConstraint("ck_practice_session_report_sections_counts", "question_count >= 0 and answered_count >= 0 and correct_count >= 0 and wrong_count >= 0 and unanswered_count >= 0"); - - t.HasCheckConstraint("ck_practice_session_report_sections_scores", "score >= 0 and total_score >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RecentPractice", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Color") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("color"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LastAccessAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_access_at"); - - b.Property("LastPracticeAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_practice_at"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PracticeType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("practice_type"); - - b.Property("Progress") - .HasColumnType("integer") - .HasColumnName("progress"); - - b.Property("TargetLegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("target_legacy_id"); - - b.Property("TargetName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("target_name"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_recent_practices"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_recent_practices_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_recent_practices_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_recent_practices_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UserId", "LastAccessAt") - .HasDatabaseName("ix_recent_practices_tenant_id_user_id_last_access_at"); - - b.ToTable("recent_practices", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.Report", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attachments") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("attachments") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("Contact") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("contact"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("HandledAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("handled_at"); - - b.Property("HandledBy") - .HasColumnType("uuid") - .HasColumnName("handled_by"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("priority"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("Resolution") - .HasColumnType("text") - .HasColumnName("resolution"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("Type") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_reports"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_reports_tenant_id_id"); - - b.HasIndex("HandledBy") - .HasDatabaseName("ix_reports_handled_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_reports_user_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_reports_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "QuestionId", "CreatedAt") - .HasDatabaseName("ix_reports_tenant_id_question_id_created_at") - .HasFilter("question_id is not null"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_reports_tenant_id_status_created_at"); - - b.ToTable("reports", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ReportStatusEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FromStatus") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("from_status"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("ReportId") - .HasColumnType("uuid") - .HasColumnName("report_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("ToStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("to_status"); - - b.HasKey("Id") - .HasName("pk_report_status_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_report_status_events_tenant_id_id"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_report_status_events_actor_user_id"); - - b.HasIndex("TenantId", "ReportId", "CreatedAt") - .HasDatabaseName("ix_report_status_events_tenant_id_report_id_created_at"); - - b.ToTable("report_status_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RevenueDailyStat", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CodeCount") - .HasColumnType("integer") - .HasColumnName("code_count"); - - b.Property("CodeEstimated") - .HasColumnType("integer") - .HasColumnName("code_estimated"); - - b.Property("CodeUsed") - .HasColumnType("integer") - .HasColumnName("code_used"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EstimatedRevenueCents") - .HasColumnType("integer") - .HasColumnName("estimated_revenue_cents"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyRegionId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_region_id"); - - b.Property("OrderCount") - .HasColumnType("integer") - .HasColumnName("order_count"); - - b.Property("RealRevenueCents") - .HasColumnType("integer") - .HasColumnName("real_revenue_cents"); - - b.Property("RebuiltAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rebuilt_at"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SaleType") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("sale_type"); - - b.Property("StatDate") - .HasColumnType("date") - .HasColumnName("stat_date"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_revenue_daily_stats"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_revenue_daily_stats_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_region_id"); - - b.HasIndex("TenantId", "StatDate", "RegionId", "SaleType") - .IsUnique() - .HasDatabaseName("ix_revenue_daily_stats_tenant_id_stat_date_region_id_sale_type"); - - NpgsqlIndexBuilderExtensions.AreNullsDistinct(b.HasIndex("TenantId", "StatDate", "RegionId", "SaleType"), false); - - b.ToTable("revenue_daily_stats", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.UserScoreEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BalanceAfter") - .HasColumnType("integer") - .HasColumnName("balance_after"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EventType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("event_type"); - - b.Property("IdempotencyKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("idempotency_key"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Points") - .HasColumnType("integer") - .HasColumnName("points"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_score_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_score_events_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_score_events_user_id"); - - b.HasIndex("TenantId", "IdempotencyKey") - .IsUnique() - .HasDatabaseName("ix_user_score_events_tenant_id_idempotency_key"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_user_score_events_tenant_id_user_id_created_at"); - - b.ToTable("user_score_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.WrongQuestion", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.Property("QuestionReferenceId") - .HasColumnType("uuid") - .HasColumnName("question_reference_id"); - - b.Property("LastWrongAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_wrong_at") - .HasDefaultValueSql("now()"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("QuestionOwnerTenantId") - .HasColumnType("uuid") - .HasColumnName("question_owner_tenant_id"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("WrongCount") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(1) - .HasColumnName("wrong_count"); - - b.HasKey("TenantId", "UserId", "QuestionReferenceId") - .HasName("pk_wrong_questions"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_wrong_questions_user_id"); - - b.HasIndex("QuestionOwnerTenantId", "QuestionId") - .HasDatabaseName("ix_wrong_questions_question_owner_tenant_id_question_id"); - - b.HasIndex("TenantId", "QuestionReferenceId") - .HasDatabaseName("ix_wrong_questions_tenant_id_question_reference_id"); - - b.ToTable("wrong_questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Announcement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BackgroundColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("background_color"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Link") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("link"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_announcements"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_announcements_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_announcements_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "IsActive", "SortOrder") - .HasDatabaseName("ix_announcements_tenant_id_is_active_sort_order"); - - b.ToTable("announcements", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.AuditLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Action") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("action"); - - b.Property("ActorUserId") - .HasColumnType("uuid") - .HasColumnName("actor_user_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("TargetId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_audit_logs"); - - b.HasIndex("ActorUserId") - .HasDatabaseName("ix_audit_logs_actor_user_id"); - - b.HasIndex("TenantId", "CreatedAt") - .HasDatabaseName("ix_audit_logs_tenant_id_created_at"); - - b.HasIndex("TenantId", "TargetType", "TargetId", "CreatedAt") - .HasDatabaseName("ix_audit_logs_tenant_id_target_type_target_id_created_at"); - - b.ToTable("audit_logs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Area") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("area"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Icon") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("icon"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("ParentCode") - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("parent_code"); - - b.Property("Path") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("path"); - - b.Property("PermissionCode") - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_backend_menus"); - - b.HasAlternateKey("Code") - .HasName("ak_backend_menus_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_backend_menus_code"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_backend_menus_permission_code"); - - b.HasIndex("Area", "ParentCode", "SortOrder") - .HasDatabaseName("ix_backend_menus_area_parent_code_sort_order"); - - b.ToTable("backend_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendPermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Area") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("area"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Module") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("module"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_backend_permissions"); - - b.HasAlternateKey("Code") - .HasName("ak_backend_permissions_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_backend_permissions_code"); - - b.HasIndex("Area", "Module", "SortOrder") - .HasDatabaseName("ix_backend_permissions_area_module_sort_order"); - - b.ToTable("backend_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("JobType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("job_type"); - - b.Property("LastError") - .HasMaxLength(4000) - .HasColumnType("character varying(4000)") - .HasColumnName("last_error"); - - b.Property("LockExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("lock_expires_at"); - - b.Property("LockedBy") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("locked_by"); - - b.Property("MaxRetries") - .HasColumnType("integer") - .HasColumnName("max_retries"); - - b.Property("OutputAssetId") - .HasColumnType("uuid") - .HasColumnName("output_asset_id"); - - b.Property("Payload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Result") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("result") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("RetryCount") - .HasColumnType("integer") - .HasColumnName("retry_count"); - - b.Property("RunAfter") - .HasColumnType("timestamp with time zone") - .HasColumnName("run_after"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("started_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_background_jobs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_background_jobs_tenant_id_id"); - - b.HasIndex("TenantId", "OutputAssetId") - .HasDatabaseName("ix_background_jobs_tenant_id_output_asset_id"); - - b.HasIndex("TenantId", "JobType", "Status", "CreatedAt") - .HasDatabaseName("ix_background_jobs_tenant_id_job_type_status_created_at"); - - b.HasIndex("TenantId", "Status", "RunAfter", "CreatedAt") - .HasDatabaseName("ix_background_jobs_tenant_id_status_run_after_created_at"); - - b.ToTable("background_jobs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Badge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Category") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("category"); - - b.Property("ConditionExtra") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("condition_extra") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ConditionField") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("condition_field"); - - b.Property("ConditionOperator") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("condition_operator"); - - b.Property("ConditionValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)") - .HasColumnName("condition_value"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("IconUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("icon_url"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Level") - .HasColumnType("integer") - .HasColumnName("level"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnlockType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("unlock_type"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_badges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_badges_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_badges_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "Category", "IsActive", "SortOrder") - .HasDatabaseName("ix_badges_tenant_id_category_is_active_sort_order"); - - b.ToTable("badges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Banner", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BackgroundColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("background_color"); - - b.Property("BorderColor") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("border_color"); - - b.Property("ButtonLink") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("button_link"); - - b.Property("ButtonText") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("button_text"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Subtitle") - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("subtitle"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_banners"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_banners_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_banners_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_banners_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("banners", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Faq", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Answer") - .HasColumnType("text") - .HasColumnName("answer"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("IsActive") - .HasColumnType("boolean") - .HasColumnName("is_active"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Question") - .HasColumnType("text") - .HasColumnName("question"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_faqs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_faqs_tenant_id_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_faqs_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId", "IsActive", "SortOrder") - .HasDatabaseName("ix_faqs_tenant_id_region_id_is_active_sort_order"); - - b.ToTable("faqs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_backend_roles"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_backend_roles_code"); - - b.HasIndex("Status", "Code") - .HasDatabaseName("ix_platform_backend_roles_status_code"); - - b.ToTable("platform_backend_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("MenuCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("menu_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_role_menus"); - - b.HasIndex("MenuCode") - .HasDatabaseName("ix_platform_backend_role_menus_menu_code"); - - b.HasIndex("RoleId", "MenuCode") - .IsUnique() - .HasDatabaseName("ix_platform_backend_role_menus_role_id_menu_code"); - - b.ToTable("platform_backend_role_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("PermissionCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_role_permissions"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_platform_backend_role_permissions_permission_code"); - - b.HasIndex("RoleId", "PermissionCode") - .IsUnique() - .HasDatabaseName("ix_platform_backend_role_permissions_role_id_permission_code"); - - b.ToTable("platform_backend_role_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_platform_backend_user_roles"); - - b.HasIndex("RoleId") - .HasDatabaseName("ix_platform_backend_user_roles_role_id"); - - b.HasIndex("UserId", "RoleId") - .IsUnique() - .HasDatabaseName("ix_platform_backend_user_roles_user_id_role_id"); - - b.ToTable("platform_backend_user_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DataScope") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("data_scope") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("IsSystem") - .HasColumnType("boolean") - .HasColumnName("is_system"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_roles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_roles_tenant_id_id"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_roles_tenant_id_code"); - - b.HasIndex("TenantId", "Status", "Code") - .HasDatabaseName("ix_tenant_backend_roles_tenant_id_status_code"); - - b.ToTable("tenant_backend_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("MenuCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("menu_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_role_menus"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_role_menus_tenant_id_id"); - - b.HasIndex("MenuCode") - .HasDatabaseName("ix_tenant_backend_role_menus_menu_code"); - - b.HasIndex("TenantId", "RoleId", "MenuCode") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_role_menus_tenant_id_role_id_menu_code"); - - b.ToTable("tenant_backend_role_menus", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("PermissionCode") - .IsRequired() - .HasMaxLength(160) - .HasColumnType("character varying(160)") - .HasColumnName("permission_code"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_role_permissions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_role_permissions_tenant_id_id"); - - b.HasIndex("PermissionCode") - .HasDatabaseName("ix_tenant_backend_role_permissions_permission_code"); - - b.HasIndex("TenantId", "RoleId", "PermissionCode") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_role_permissions_tenant_id_role_id_permissio~"); - - b.ToTable("tenant_backend_role_permissions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("RoleId") - .HasColumnType("uuid") - .HasColumnName("role_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_backend_user_roles"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_backend_user_roles_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_backend_user_roles_user_id"); - - b.HasIndex("TenantId", "RoleId") - .HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_role_id"); - - b.HasIndex("TenantId", "UserId", "RoleId") - .IsUnique() - .HasDatabaseName("ix_tenant_backend_user_roles_tenant_id_user_id_role_id"); - - b.ToTable("tenant_backend_user_roles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActionLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("action_label"); - - b.Property("ActionPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("action_path"); - - b.Property("AdoptionId") - .HasColumnType("uuid") - .HasColumnName("adoption_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DedupeKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("dedupe_key"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NotificationType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("notification_type"); - - b.Property("ReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("read_at"); - - b.Property("ReadBy") - .HasColumnType("uuid") - .HasColumnName("read_by"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("SourceQuestionBankId") - .HasColumnType("uuid") - .HasColumnName("source_question_bank_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_content_notifications"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_content_notifications_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_content_notifications_created_by"); - - b.HasIndex("ReadBy") - .HasDatabaseName("ix_tenant_content_notifications_read_by"); - - b.HasIndex("TenantId", "SourceQuestionBankId") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_source_question_bank~"); - - b.HasIndex("TenantId", "NotificationType", "DedupeKey") - .IsUnique() - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_notification_type_de~") - .HasFilter("dedupe_key is not null"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_status_created_at"); - - b.HasIndex("TenantId", "AdoptionId", "NotificationType", "Status") - .HasDatabaseName("ix_tenant_content_notifications_tenant_id_adoption_id_notifica~"); - - b.ToTable("tenant_content_notifications", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActivePublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("active_public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ActiveTemplateCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("active_template_code"); - - b.Property("ActiveTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("active_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DraftPublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftTemplateCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("draft_template_code"); - - b.Property("DraftTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftUpdatedBy") - .HasColumnType("uuid") - .HasColumnName("draft_updated_by"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublishedBy") - .HasColumnType("uuid") - .HasColumnName("published_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_theme_configs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_theme_configs_tenant_id_id"); - - b.HasIndex("ActiveTemplateCode") - .HasDatabaseName("ix_tenant_theme_configs_active_template_code"); - - b.HasIndex("DraftTemplateCode") - .HasDatabaseName("ix_tenant_theme_configs_draft_template_code"); - - b.HasIndex("DraftUpdatedBy") - .HasDatabaseName("ix_tenant_theme_configs_draft_updated_by"); - - b.HasIndex("PublishedBy") - .HasDatabaseName("ix_tenant_theme_configs_published_by"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_theme_configs_tenant_id"); - - b.ToTable("tenant_theme_configs", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("PreviewImageUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("preview_image_url"); - - b.Property("PublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Theme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_theme_templates"); - - b.HasAlternateKey("Code") - .HasName("ak_tenant_theme_templates_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_tenant_theme_templates_code"); - - b.HasIndex("Status", "SortOrder", "Code") - .HasDatabaseName("ix_tenant_theme_templates_status_sort_order_code"); - - b.ToTable("tenant_theme_templates", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserBadge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BadgeId") - .HasColumnType("uuid") - .HasColumnName("badge_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("GrantedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("granted_at"); - - b.Property("GrantedBy") - .HasColumnType("uuid") - .HasColumnName("granted_by"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_badges"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_badges_tenant_id_id"); - - b.HasIndex("GrantedBy") - .HasDatabaseName("ix_user_badges_granted_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_badges_user_id"); - - b.HasIndex("TenantId", "BadgeId") - .HasDatabaseName("ix_user_badges_tenant_id_badge_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_user_badges_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "UserId", "BadgeId") - .IsUnique() - .HasDatabaseName("ix_user_badges_tenant_id_user_id_badge_id"); - - b.ToTable("user_badges", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserNotification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ActionLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("action_label"); - - b.Property("ActionPath") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("action_path"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DedupeKey") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("dedupe_key"); - - b.Property("Message") - .IsRequired() - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NotificationType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("notification_type"); - - b.Property("ReadAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("read_at"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("SourceId") - .HasColumnType("uuid") - .HasColumnName("source_id"); - - b.Property("SourceType") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("source_type"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_user_notifications"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_user_notifications_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_user_notifications_created_by"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_user_notifications_user_id"); - - b.HasIndex("TenantId", "SourceType", "SourceId") - .HasDatabaseName("ix_user_notifications_tenant_id_source_type_source_id"); - - b.HasIndex("TenantId", "UserId", "NotificationType", "DedupeKey") - .IsUnique() - .HasDatabaseName("ix_user_notifications_tenant_id_user_id_notification_type_dedu~") - .HasFilter("dedupe_key is not null"); - - b.HasIndex("TenantId", "UserId", "Status", "CreatedAt") - .HasDatabaseName("ix_user_notifications_tenant_id_user_id_status_created_at"); - - b.ToTable("user_notifications", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlanModuleEntitlement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("ModuleCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("module_code"); - - b.Property("PlanCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("plan_code"); - - b.HasKey("Id") - .HasName("pk_plan_module_entitlements"); - - b.HasIndex("ModuleCode") - .HasDatabaseName("ix_plan_module_entitlements_module_code"); - - b.HasIndex("PlanCode", "ModuleCode") - .IsUnique() - .HasDatabaseName("ix_plan_module_entitlements_plan_code_module_code"); - - b.ToTable("plan_module_entitlements", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AcknowledgedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("acknowledged_at"); - - b.Property("AcknowledgedBy") - .HasColumnType("uuid") - .HasColumnName("acknowledged_by"); - - b.Property("Action") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("action"); - - b.Property("AuditLogId") - .HasColumnType("uuid") - .HasColumnName("audit_log_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Details") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("details") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("FirstSeenAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("first_seen_at") - .HasDefaultValueSql("now()"); - - b.Property("LastSeenAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("last_seen_at") - .HasDefaultValueSql("now()"); - - b.Property("ResolutionNote") - .HasColumnType("text") - .HasColumnName("resolution_note"); - - b.Property("ResolvedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("resolved_at"); - - b.Property("ResolvedBy") - .HasColumnType("uuid") - .HasColumnName("resolved_by"); - - b.Property("RuleId") - .HasColumnType("uuid") - .HasColumnName("rule_id"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("Summary") - .HasColumnType("text") - .HasColumnName("summary"); - - b.Property("TargetId") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_id"); - - b.Property("TargetType") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("target_type"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_audit_alerts"); - - b.HasIndex("AcknowledgedBy") - .HasDatabaseName("ix_platform_audit_alerts_acknowledged_by"); - - b.HasIndex("AuditLogId") - .HasDatabaseName("ix_platform_audit_alerts_audit_log_id"); - - b.HasIndex("ResolvedBy") - .HasDatabaseName("ix_platform_audit_alerts_resolved_by"); - - b.HasIndex("RuleId", "AuditLogId") - .IsUnique() - .HasDatabaseName("ix_platform_audit_alerts_rule_id_audit_log_id"); - - b.HasIndex("Status", "Severity", "CreatedAt") - .HasDatabaseName("ix_platform_audit_alerts_status_severity_created_at"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_audit_alerts_tenant_id_status_created_at"); - - b.ToTable("platform_audit_alerts", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlertRule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.PrimitiveCollection("ActionPatterns") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("action_patterns") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("Conditions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("conditions") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Severity") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("severity"); - - b.PrimitiveCollection("TargetTypes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("target_types") - .HasDefaultValueSql("'{}'::text[]"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_audit_alert_rules"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_audit_alert_rules_code"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_platform_audit_alert_rules_tenant_id"); - - b.HasIndex("Enabled", "Severity", "Code") - .HasDatabaseName("ix_platform_audit_alert_rules_enabled_severity_code"); - - b.ToTable("platform_audit_alert_rules", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationChannel", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ChannelCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("channel_code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MinReminderLevel") - .HasColumnType("integer") - .HasColumnName("min_reminder_level"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("provider"); - - b.PrimitiveCollection("ReminderChannels") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("reminder_channels") - .HasDefaultValueSql("array['internal']::text[]"); - - b.PrimitiveCollection("ReminderTypes") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("text[]") - .HasColumnName("reminder_types") - .HasDefaultValueSql("array['overdue', 'final_notice']::text[]"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.PrimitiveCollection("TenantIds") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("uuid[]") - .HasColumnName("tenant_ids") - .HasDefaultValueSql("'{}'::uuid[]"); - - b.Property("TimeoutSeconds") - .HasColumnType("integer") - .HasColumnName("timeout_seconds"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("WebhookUrl") - .IsRequired() - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("webhook_url"); - - b.HasKey("Id") - .HasName("pk_platform_dunning_notification_channels"); - - b.HasIndex("ChannelCode") - .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_channels_channel_code"); - - b.HasIndex("Enabled", "MinReminderLevel", "ChannelCode") - .HasDatabaseName("ix_platform_dunning_notification_channels_enabled_min_reminder~"); - - b.ToTable("platform_dunning_notification_channels", null, t => - { - t.HasCheckConstraint("ck_platform_dunning_channels_level", "min_reminder_level between 1 and 20"); - - t.HasCheckConstraint("ck_platform_dunning_channels_timeout", "timeout_seconds between 1 and 60"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("ChannelId") - .HasColumnType("uuid") - .HasColumnName("channel_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("LastAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_attempt_at"); - - b.Property("LastError") - .HasColumnType("text") - .HasColumnName("last_error"); - - b.Property("LastHttpCode") - .HasColumnType("integer") - .HasColumnName("last_http_code"); - - b.Property("LastResponseSummary") - .HasColumnType("text") - .HasColumnName("last_response_summary"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NextAttemptAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("next_attempt_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("provider"); - - b.Property("ReminderId") - .HasColumnType("uuid") - .HasColumnName("reminder_id"); - - b.Property("RequestPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("request_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ScheduledAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("scheduled_at") - .HasDefaultValueSql("now()"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_dunning_notification_events"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_platform_dunning_notification_events_tenant_id_id"); - - b.HasIndex("ChannelId") - .HasDatabaseName("ix_platform_dunning_notification_events_channel_id"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_invoice_id"); - - b.HasIndex("TenantId", "ReminderId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_reminder_id"); - - b.HasIndex("InvoiceId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_invoice_id_status_crea~"); - - b.HasIndex("ReminderId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_reminder_id_status_cre~"); - - b.HasIndex("TenantId", "ChannelId", "ReminderId") - .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_channel_id_r~"); - - b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_status_creat~"); - - b.HasIndex("Status", "NextAttemptAt", "ScheduledAt", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_status_next_attempt_at~"); - - b.ToTable("platform_dunning_notification_events", null, t => - { - t.HasCheckConstraint("ck_platform_dunning_events_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformSaasPlan", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BaseAmountCents") - .HasColumnType("integer") - .HasColumnName("base_amount_cents"); - - b.Property("BillingCycle") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("billing_cycle"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("FeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("IncludedQuotas") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("included_quotas") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OveragePrices") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("overage_prices") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_saas_plans"); - - b.HasAlternateKey("Code") - .HasName("ak_platform_saas_plans_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_saas_plans_code"); - - b.HasIndex("Status", "SortOrder") - .HasDatabaseName("ix_platform_saas_plans_status_sort_order"); - - b.ToTable("platform_saas_plans", null, t => - { - t.HasCheckConstraint("ck_platform_saas_plans_amount", "base_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.ProductModule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_product_modules"); - - b.HasAlternateKey("Code") - .HasName("ak_product_modules_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_product_modules_code"); - - b.ToTable("product_modules", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("BankAccountMasked") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("bank_account_masked"); - - b.Property("BankName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("bank_name"); - - b.Property("BillingAddress") - .HasColumnType("text") - .HasColumnName("billing_address"); - - b.Property("BillingName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("billing_name"); - - b.Property("ContactEmail") - .HasMaxLength(300) - .HasColumnType("citext") - .HasColumnName("contact_email"); - - b.Property("ContactName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("contact_name"); - - b.Property("ContactPhone") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("contact_phone"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceTitle") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("invoice_title"); - - b.Property("InvoiceType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TaxId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("tax_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_billing_profiles"); - - b.ToTable("tenant_billing_profiles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BalanceCents") - .HasColumnType("integer") - .HasColumnName("balance_cents"); - - b.Property("BillingPeriodEnd") - .HasColumnType("date") - .HasColumnName("billing_period_end"); - - b.Property("BillingPeriodStart") - .HasColumnType("date") - .HasColumnName("billing_period_start"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("DiscountCents") - .HasColumnType("integer") - .HasColumnName("discount_cents"); - - b.Property("DueDate") - .HasColumnType("date") - .HasColumnName("due_date"); - - b.Property("InvoiceNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("invoice_no"); - - b.Property("InvoiceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - - b.Property("IssuedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("issued_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaidCents") - .HasColumnType("integer") - .HasColumnName("paid_cents"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubtotalCents") - .HasColumnType("integer") - .HasColumnName("subtotal_cents"); - - b.Property("TaxCents") - .HasColumnType("integer") - .HasColumnName("tax_cents"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TotalCents") - .HasColumnType("integer") - .HasColumnName("total_cents"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoices"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoices_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoices_created_by"); - - b.HasIndex("TenantId", "InvoiceNo") - .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_invoice_no"); - - b.HasIndex("TenantId", "BillingPeriodStart", "BillingPeriodEnd") - .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_billing_period_start_billing_peri~") - .HasFilter("invoice_type = 'usage_overage' and status <> 'void' and metadata->>'source' = 'usage_overage_auto'"); - - b.HasIndex("TenantId", "Status", "DueDate") - .HasDatabaseName("ix_tenant_invoices_tenant_id_status_due_date"); - - b.ToTable("tenant_invoices", null, t => - { - t.HasCheckConstraint("ck_tenant_invoices_amounts", "subtotal_cents >= 0 and discount_cents >= 0 and tax_cents >= 0 and total_cents >= 0 and paid_cents >= 0 and balance_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoices_period", "billing_period_end is null or billing_period_start is null or billing_period_end >= billing_period_start"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("description"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("ItemRefId") - .HasColumnType("uuid") - .HasColumnName("item_ref_id"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Quantity") - .HasPrecision(12, 2) - .HasColumnType("numeric(12,2)") - .HasColumnName("quantity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitAmountCents") - .HasColumnType("integer") - .HasColumnName("unit_amount_cents"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_items_tenant_id_id"); - - b.HasIndex("InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_invoice_id"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_tenant_id_invoice_id"); - - b.ToTable("tenant_invoice_items", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_items_amounts", "unit_amount_cents >= 0 and amount_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoice_items_quantity", "quantity > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("Method") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("method"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_no"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ReceivedBy") - .HasColumnType("uuid") - .HasColumnName("received_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_payments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_payments_tenant_id_id"); - - b.HasIndex("ReceivedBy") - .HasDatabaseName("ix_tenant_invoice_payments_received_by"); - - b.HasIndex("InvoiceId", "Status") - .HasDatabaseName("ix_tenant_invoice_payments_invoice_id_status"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_invoice_id"); - - b.HasIndex("TenantId", "PaymentNo") - .IsUnique() - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_payment_no"); - - b.ToTable("tenant_invoice_payments", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_payments_amount", "amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AcknowledgedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("acknowledged_at"); - - b.Property("BalanceCentsSnapshot") - .HasColumnType("integer") - .HasColumnName("balance_cents_snapshot"); - - b.Property("Channel") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("channel"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("DueDate") - .HasColumnType("date") - .HasColumnName("due_date"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("Message") - .HasColumnType("text") - .HasColumnName("message"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ReminderDate") - .HasColumnType("date") - .HasColumnName("reminder_date"); - - b.Property("ReminderLevel") - .HasColumnType("integer") - .HasColumnName("reminder_level"); - - b.Property("ReminderType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("reminder_type"); - - b.Property("SentAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("sent_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_reminders"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_reminders_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoice_reminders_created_by"); - - b.HasIndex("InvoiceId", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_invoice_id_reminder_date"); - - b.HasIndex("TenantId", "Status", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_status_reminder_date"); - - b.HasIndex("TenantId", "InvoiceId", "ReminderType", "Channel", "ReminderDate") - .IsUnique() - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_invoice_id_reminder_type~"); - - b.ToTable("tenant_invoice_reminders", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_reminders_balance", "balance_cents_snapshot >= 0"); - - t.HasCheckConstraint("ck_tenant_invoice_reminders_level", "reminder_level between 1 and 20"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantModuleOverride", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("mode"); - - b.Property("ModuleCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("module_code"); - - b.Property("Reason") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("reason"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_module_overrides"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_module_overrides_tenant_id_id"); - - b.HasIndex("ModuleCode") - .HasDatabaseName("ix_tenant_module_overrides_module_code"); - - b.HasIndex("TenantId", "ModuleCode") - .IsUnique() - .HasDatabaseName("ix_tenant_module_overrides_tenant_id_module_code"); - - b.ToTable("tenant_module_overrides", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property("ContentNodeId") - .HasColumnType("uuid") - .HasColumnName("content_node_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CurrentVersionId") - .HasColumnType("uuid") - .HasColumnName("current_version_id"); - - b.Property("Difficulty") - .HasColumnType("integer") - .HasColumnName("difficulty"); - - b.Property("EntryId") - .HasColumnType("uuid") - .HasColumnName("entry_id"); - - b.Property("ExamMarkers") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("exam_markers") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("HasVideoExplanation") - .HasColumnType("boolean") - .HasColumnName("has_video_explanation"); - - b.Property("LegacyCategoryId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_category_id"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegacyNodeId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_node_id"); - - b.Property("LegacySubjectId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_subject_id"); - - b.Property("MediaUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("media_url"); - - b.Property("NodeId") - .HasColumnType("uuid") - .HasColumnName("node_id"); - - b.Property("PrimaryCollectionId") - .HasColumnType("uuid") - .HasColumnName("primary_collection_id"); - - b.Property("QuestionBankId") - .HasColumnType("uuid") - .HasColumnName("question_bank_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("SubjectId") - .HasColumnType("uuid") - .HasColumnName("subject_id"); - - b.Property("Tags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("tags") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Type") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("type"); - - b.Property("TypeLabel") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("type_label"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_questions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_questions_tenant_id_id"); - - b.HasIndex("TenantId", "CategoryId") - .HasDatabaseName("ix_questions_tenant_id_category_id"); - - b.HasIndex("TenantId", "ContentNodeId") - .HasDatabaseName("ix_questions_tenant_id_content_node_id"); - - b.HasIndex("TenantId", "EntryId") - .HasDatabaseName("ix_questions_tenant_id_entry_id"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_questions_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "NodeId") - .HasDatabaseName("ix_questions_tenant_id_node_id"); - - b.HasIndex("TenantId", "PrimaryCollectionId") - .HasDatabaseName("ix_questions_tenant_id_primary_collection_id"); - - b.HasIndex("TenantId", "QuestionBankId") - .HasDatabaseName("ix_questions_tenant_id_question_bank_id"); - - b.HasIndex("TenantId", "SubjectId") - .HasDatabaseName("ix_questions_tenant_id_subject_id"); - - b.HasIndex("TenantId", "Id", "CurrentVersionId") - .HasDatabaseName("ix_questions_tenant_id_id_current_version_id"); - - b.ToTable("questions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionBank", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_question_banks"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_banks_tenant_id_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_question_banks_tenant_id_region_id"); - - b.ToTable("question_banks", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AnswerText") - .HasColumnType("text") - .HasColumnName("answer_text"); - - b.Property("CodeLang") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("code_lang"); - - b.Property("CodeTemplate") - .HasColumnType("text") - .HasColumnName("code_template"); - - b.Property("Content") - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CorrectOptionIndex") - .HasColumnType("integer") - .HasColumnName("correct_option_index"); - - b.Property("CorrectOptionIndices") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("correct_option_indices") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Explanation") - .HasColumnType("text") - .HasColumnName("explanation"); - - b.Property("Options") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("options") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("QuestionId") - .HasColumnType("uuid") - .HasColumnName("question_id"); - - b.Property("SourceHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("source_hash"); - - b.Property("SubQuestions") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("sub_questions") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("VersionNo") - .HasColumnType("integer") - .HasColumnName("version_no"); - - b.HasKey("Id") - .HasName("pk_question_versions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_question_versions_tenant_id_id"); - - b.HasAlternateKey("TenantId", "QuestionId", "Id") - .HasName("ak_question_versions_tenant_id_question_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_question_versions_created_by"); - - b.HasIndex("TenantId", "QuestionId", "VersionNo") - .IsUnique() - .HasDatabaseName("ix_question_versions_tenant_id_question_id_version_no"); - - b.ToTable("question_versions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ConsumedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("consumed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("ip_address"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("purpose"); - - b.Property("Realm") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("realm"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("security_stamp"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("token_hash"); - - b.Property("UserAgent") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_challenges"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_auth_challenges_tenant_id"); - - b.HasIndex("TokenHash") - .IsUnique() - .HasDatabaseName("ix_auth_challenges_token_hash"); - - b.HasIndex("UserId", "Purpose", "ExpiresAt") - .HasDatabaseName("ix_auth_challenges_user_id_purpose_expires_at"); - - b.ToTable("auth_challenges", null, t => - { - t.HasCheckConstraint("ck_auth_challenges_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FailureCode") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("failure_code"); - - b.Property("Identifier") - .HasMaxLength(320) - .HasColumnType("character varying(320)") - .HasColumnName("identifier"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Result") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("result"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_login_events"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_auth_login_events_user_id"); - - b.HasIndex("TenantId", "UserId", "CreatedAt") - .HasDatabaseName("ix_auth_login_events_tenant_id_user_id_created_at"); - - b.ToTable("auth_login_events", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthSession", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ParentSessionId") - .HasColumnType("uuid") - .HasColumnName("parent_session_id"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Realm") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("realm"); - - b.Property("ReplacedBySessionId") - .HasColumnType("uuid") - .HasColumnName("replaced_by_session_id"); - - b.Property("RevokedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("revoked_at"); - - b.Property("RevokedReason") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("revoked_reason"); - - b.Property("SecurityStamp") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("security_stamp"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TokenFamilyId") - .HasColumnType("uuid") - .HasColumnName("token_family_id"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("token_hash"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_auth_sessions"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_auth_sessions_tenant_id"); - - b.HasIndex("TokenHash") - .IsUnique() - .HasDatabaseName("ix_auth_sessions_token_hash") - .HasAnnotation("Tiku:GlobalUnique", true); - - b.HasIndex("UserId") - .HasDatabaseName("ix_auth_sessions_user_id"); - - b.HasIndex("TokenFamilyId", "RevokedAt") - .HasDatabaseName("ix_auth_sessions_token_family_id_revoked_at"); - - b.HasIndex("Realm", "TenantId", "UserId", "ExpiresAt") - .HasDatabaseName("ix_auth_sessions_realm_tenant_id_user_id_expires_at") - .HasFilter("revoked_at is null"); - - b.ToTable("auth_sessions", null, t => - { - t.HasCheckConstraint("ck_auth_sessions_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsSendRateLimit", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Dimension") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("dimension"); - - b.Property("ScopeHash") - .HasMaxLength(128) - .HasColumnType("character varying(128)") - .HasColumnName("scope_hash"); - - b.Property("BucketStart") - .HasColumnType("timestamp with time zone") - .HasColumnName("bucket_start"); - - b.Property("RequestCount") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(0) - .HasColumnName("request_count"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId", "Dimension", "ScopeHash", "BucketStart") - .HasName("pk_sms_send_rate_limits"); - - b.HasIndex("UpdatedAt") - .HasDatabaseName("ix_sms_send_rate_limits_updated_at"); - - b.ToTable("sms_send_rate_limits", null, t => - { - t.HasCheckConstraint("ck_sms_send_rate_limits_request_count", "request_count >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsVerificationCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Attempts") - .HasColumnType("integer") - .HasColumnName("attempts"); - - b.Property("CodeHash") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("code_hash"); - - b.Property("ConsumedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("consumed_at"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("IpAddress") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("ip_address"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Phone") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("phone"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("purpose"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UserAgent") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("user_agent"); - - b.HasKey("Id") - .HasName("pk_sms_verification_codes"); - - b.HasIndex("TenantId", "Phone", "Purpose") - .IsUnique() - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose") - .HasFilter("consumed_at is null and status in ('pending', 'sent')"); - - b.HasIndex("TenantId", "Phone", "Purpose", "CreatedAt") - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose_created_at") - .HasFilter("consumed_at is null and status in ('pending', 'sent')"); - - b.HasIndex("TenantId", "Phone", "Purpose", "ExpiresAt") - .HasDatabaseName("ix_sms_verification_codes_tenant_id_phone_purpose_expires_at"); - - b.ToTable("sms_verification_codes", null, t => - { - t.HasCheckConstraint("ck_sms_verification_codes_attempts", "attempts >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.Tenant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BillingStatus") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("billing_status"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("LegalName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("legal_name"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Mode") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("mode"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OwnerUserId") - .HasColumnType("uuid") - .HasColumnName("owner_user_id"); - - b.Property("Slug") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("citext") - .HasColumnName("slug"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenants"); - - b.HasIndex("LegacyId") - .IsUnique() - .HasDatabaseName("ix_tenants_legacy_id"); - - b.HasIndex("Mode") - .IsUnique() - .HasDatabaseName("ix_tenants_mode") - .HasFilter("mode = 'platform_owned'"); - - b.HasIndex("OwnerUserId") - .HasDatabaseName("ix_tenants_owner_user_id"); - - b.HasIndex("Slug") - .IsUnique() - .HasDatabaseName("ix_tenants_slug"); - - b.ToTable("tenants", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantAuthPolicy", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("AllowExternalStudentSelfRegistration") - .ValueGeneratedOnAdd() - .HasColumnType("boolean") - .HasDefaultValue(false) - .HasColumnName("allow_external_student_self_registration"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_auth_policies"); - - b.ToTable("tenant_auth_policies", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantBranding", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("BrandName") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("brand_name"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FaviconUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("favicon_url"); - - b.Property("LogoUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)") - .HasColumnName("logo_url"); - - b.Property("OrganizationName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("organization_name"); - - b.Property("PublicAssets") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_assets") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ServiceAccountName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("service_account_name"); - - b.Property("ServiceWechat") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("service_wechat"); - - b.Property("ShortName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("short_name"); - - b.Property("Slogan") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("slogan"); - - b.Property("Theme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_branding"); - - b.ToTable("tenant_branding", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClass", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("LegacyId") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("legacy_id"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("RegionId") - .HasColumnType("uuid") - .HasColumnName("region_id"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_classes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_classes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_classes_created_by"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_classes_updated_by"); - - b.HasIndex("TenantId", "Code") - .IsUnique() - .HasDatabaseName("ix_tenant_classes_tenant_id_code") - .HasFilter("code is not null and code <> ''"); - - b.HasIndex("TenantId", "LegacyId") - .IsUnique() - .HasDatabaseName("ix_tenant_classes_tenant_id_legacy_id"); - - b.HasIndex("TenantId", "RegionId") - .HasDatabaseName("ix_tenant_classes_tenant_id_region_id") - .HasFilter("region_id is not null"); - - b.HasIndex("TenantId", "Status", "SortOrder", "CreatedAt") - .HasDatabaseName("ix_tenant_classes_tenant_id_status_sort_order_created_at"); - - b.ToTable("tenant_classes", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClassMember", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ClassId") - .HasColumnType("uuid") - .HasColumnName("class_id"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("JoinedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("joined_at") - .HasDefaultValueSql("now()"); - - b.Property("LeftAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("left_at"); - - b.Property("MemberType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("member_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_class_members"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_class_members_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_class_members_user_id"); - - b.HasIndex("TenantId", "ClassId", "Status", "MemberType") - .HasDatabaseName("ix_tenant_class_members_tenant_id_class_id_status_member_type"); - - b.HasIndex("TenantId", "ClassId", "UserId", "MemberType") - .IsUnique() - .HasDatabaseName("ix_tenant_class_members_tenant_id_class_id_user_id_member_type"); - - b.HasIndex("TenantId", "UserId", "Status", "MemberType") - .HasDatabaseName("ix_tenant_class_members_tenant_id_user_id_status_member_type"); - - b.ToTable("tenant_class_members", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantDomain", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DnsVerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("dns_verified_at"); - - b.Property("DomainType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("domain_type"); - - b.Property("Host") - .IsRequired() - .HasMaxLength(253) - .HasColumnType("citext") - .HasColumnName("host"); - - b.Property("IsPrimary") - .HasColumnType("boolean") - .HasColumnName("is_primary"); - - b.Property("LastCheckedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("last_checked_at"); - - b.Property("LastFailureReason") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)") - .HasColumnName("last_failure_reason"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("TlsReadyAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("tls_ready_at"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("VerificationToken") - .HasMaxLength(256) - .HasColumnType("character varying(256)") - .HasColumnName("verification_token"); - - b.Property("VerifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("verified_at"); - - b.HasKey("Id") - .HasName("pk_tenant_domains"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_domains_tenant_id_id"); - - b.HasIndex("Host") - .IsUnique() - .HasDatabaseName("ix_tenant_domains_host") - .HasAnnotation("Tiku:GlobalUnique", true); - - b.HasIndex("TenantId", "IsPrimary") - .IsUnique() - .HasDatabaseName("ix_tenant_domains_tenant_id_is_primary") - .HasFilter("is_primary"); - - b.ToTable("tenant_domains", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantExternalProvider", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Capability") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("capability"); - - b.Property("ConfigPublic") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("config_public") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DisplayName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("display_name"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .HasColumnType("integer") - .HasColumnName("priority"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("SecretRef") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_external_providers"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_external_providers_tenant_id_id"); - - b.HasIndex("TenantId", "Capability", "Provider") - .IsUnique() - .HasDatabaseName("ix_tenant_external_providers_tenant_id_capability_provider"); - - b.HasIndex("TenantId", "Capability", "Status", "Priority") - .HasDatabaseName("ix_tenant_external_providers_tenant_id_capability_status_prior~"); - - b.ToTable("tenant_external_providers", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantFrontendConfig", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("ConfigVersion") - .IsConcurrencyToken() - .HasColumnType("integer") - .HasColumnName("config_version"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("DraftBranding") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_branding") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftFeatures") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_features") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("DraftHomeModules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_home_modules") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("DraftNavigation") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_navigation") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("DraftTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("draft_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("published_at"); - - b.Property("PublishedBranding") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_branding") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedFeatures") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_features") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublishedHomeModules") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_home_modules") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("PublishedNavigation") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_navigation") - .HasDefaultValueSql("'[]'::jsonb"); - - b.Property("PublishedTheme") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("published_theme") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SchemaVersion") - .HasColumnType("integer") - .HasColumnName("schema_version"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_frontend_configs"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_frontend_configs_tenant_id_id"); - - b.HasIndex("TenantId") - .IsUnique() - .HasDatabaseName("ix_tenant_frontend_configs_tenant_id"); - - b.ToTable("tenant_frontend_configs", null, t => - { - t.HasCheckConstraint("ck_tenant_frontend_configs_config_version", "config_version > 0"); - - t.HasCheckConstraint("ck_tenant_frontend_configs_schema_version", "schema_version > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantMembership", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("LegacyRole") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("legacy_role"); - - b.Property("Role") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("role"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_tenant_memberships"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_memberships_tenant_id_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_tenant_memberships_user_id"); - - b.HasIndex("TenantId", "UserId", "Role") - .IsUnique() - .HasDatabaseName("ix_tenant_memberships_tenant_id_user_id_role"); - - b.ToTable("tenant_memberships", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("EncryptedPayload") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encrypted_payload"); - - b.Property("EncryptionKeyId") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("encryption_key_id"); - - b.Property("EncryptionNonce") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encryption_nonce"); - - b.Property("EncryptionTag") - .IsRequired() - .HasColumnType("bytea") - .HasColumnName("encryption_tag"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("Purpose") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("character varying(80)") - .HasColumnName("purpose"); - - b.Property("RotatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("rotated_at"); - - b.Property("SecretKey") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("character varying(120)") - .HasColumnName("secret_key"); - - b.Property("SecretRef") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("secret_ref"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_secrets"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_secrets_tenant_id_id"); - - b.HasIndex("TenantId", "SecretRef") - .IsUnique() - .HasDatabaseName("ix_tenant_secrets_tenant_id_secret_ref"); - - b.HasIndex("TenantId", "Purpose", "Provider", "SecretKey") - .IsUnique() - .HasDatabaseName("ix_tenant_secrets_tenant_id_purpose_provider_secret_key"); - - b.HasIndex("TenantId", "Purpose", "Provider", "Status") - .HasDatabaseName("ix_tenant_secrets_tenant_id_purpose_provider_status"); - - b.ToTable("tenant_secrets", null, t => - { - t.HasCheckConstraint("ck_tenant_secrets_encryption_envelope", "octet_length(encrypted_payload) > 0 and octet_length(encryption_nonce) = 12 and octet_length(encryption_tag) = 16 and encryption_key_id <> ''"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSettings", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("AdminFeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("admin_feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("FeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PublicConfig") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("public_config") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_settings"); - - b.ToTable("tenant_settings", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentFollowup", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AssignedToUserId") - .HasColumnType("uuid") - .HasColumnName("assigned_to_user_id"); - - b.Property("ClassId") - .HasColumnType("uuid") - .HasColumnName("class_id"); - - b.Property("CompletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("completed_at"); - - b.Property("CompletedBy") - .HasColumnType("uuid") - .HasColumnName("completed_by"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("DueAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("due_at"); - - b.Property("FollowupType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("followup_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Priority") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("priority"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("title"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.HasKey("Id") - .HasName("pk_tenant_student_followups"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_student_followups_tenant_id_id"); - - b.HasIndex("AssignedToUserId") - .HasDatabaseName("ix_tenant_student_followups_assigned_to_user_id"); - - b.HasIndex("CompletedBy") - .HasDatabaseName("ix_tenant_student_followups_completed_by"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_tenant_student_followups_student_user_id"); - - b.HasIndex("TenantId", "AssignedToUserId", "Status", "DueAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_assigned_to_user_id_stat~") - .HasFilter("assigned_to_user_id is not null"); - - b.HasIndex("TenantId", "ClassId", "Status", "DueAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_class_id_status_due_at") - .HasFilter("class_id is not null"); - - b.HasIndex("TenantId", "StudentUserId", "Status", "DueAt", "CreatedAt") - .HasDatabaseName("ix_tenant_student_followups_tenant_id_student_user_id_status_d~"); - - b.ToTable("tenant_student_followups", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentNote", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Content") - .IsRequired() - .HasColumnType("text") - .HasColumnName("content"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("IsPinned") - .HasColumnType("boolean") - .HasColumnName("is_pinned"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("NoteType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("note_type"); - - b.Property("StudentUserId") - .HasColumnType("uuid") - .HasColumnName("student_user_id"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.Property("UpdatedBy") - .HasColumnType("uuid") - .HasColumnName("updated_by"); - - b.Property("Visibility") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("visibility"); - - b.HasKey("Id") - .HasName("pk_tenant_student_notes"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_student_notes_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_student_notes_created_by"); - - b.HasIndex("StudentUserId") - .HasDatabaseName("ix_tenant_student_notes_student_user_id"); - - b.HasIndex("UpdatedBy") - .HasDatabaseName("ix_tenant_student_notes_updated_by"); - - b.HasIndex("TenantId", "CreatedBy", "CreatedAt") - .HasDatabaseName("ix_tenant_student_notes_tenant_id_created_by_created_at") - .HasFilter("created_by is not null"); - - b.HasIndex("TenantId", "StudentUserId", "IsPinned", "CreatedAt") - .HasDatabaseName("ix_tenant_student_notes_tenant_id_student_user_id_is_pinned_cr~"); - - b.ToTable("tenant_student_notes", (string)null); - }); - - modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b => - { - b.HasOne("MassTransit.EntityFrameworkCoreIntegration.OutboxState", null) - .WithMany() - .HasForeignKey("OutboxId") - .HasConstraintName("fk_outbox_message_outbox_state_outbox_id"); - - b.HasOne("MassTransit.EntityFrameworkCoreIntegration.InboxState", null) - .WithMany() - .HasForeignKey("InboxMessageId", "InboxConsumerId") - .HasPrincipalKey("MessageId", "ConsumerId") - .HasConstraintName("fk_outbox_message_inbox_state_inbox_message_id_inbox_consumer_~"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_claims_users_user_id"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_logins_users_user_id"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_tokens_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Category", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_categories_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_categories_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_categories_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Major", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_majors_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_majors_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_majors_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ModuleNode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_module_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_module_nodes_tenant_id_parent_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_module_nodes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.QuestionTaxonomyAssignment", b => - { - b.HasOne("Tiku.Domain.Catalog.TaxonomyNode", null) - .WithMany() - .HasForeignKey("TaxonomyOwnerTenantId", "TaxonomyNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_question_taxonomy_assignments_taxonomy_nodes_taxonomy_owner~"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_taxonomy_assignments_questions_tenant_id_question_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Region", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_regions_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.RegionModule", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_region_modules_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_region_modules_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.School", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_schools_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_schools_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_schools_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineField", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_scoreline_fields_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_fields_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.ScorelineRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_scoreline_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_scoreline_records_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.Subject", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subjects_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.RegionModule", null) - .WithMany() - .HasForeignKey("TenantId", "ModuleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_region_modules_tenant_id_module_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_subjects_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Catalog.TaxonomyNode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_taxonomy_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.TaxonomyNode", null) - .WithMany() - .HasForeignKey("ParentOwnerTenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_taxonomy_nodes_taxonomy_nodes_parent_owner_tenant_id_parent~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.ActivationCode", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AgentUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_users_agent_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_activation_codes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UsedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_users_used_by"); - - b.HasOne("Tiku.Domain.Commerce.CodeBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_code_batches_tenant_id_batch_id"); - - b.HasOne("Tiku.Domain.Commerce.CouponRedemption", null) - .WithMany() - .HasForeignKey("TenantId", "CouponRedemptionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_coupon_redemptions_tenant_id_coupon_redemp~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "UsedRegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_activation_codes_regions_tenant_id_used_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CodeBatch", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_code_batches_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucher", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_adjustment_vouchers_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_reconciliation_batche~"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationIssue", null) - .WithMany() - .HasForeignKey("TenantId", "IssueId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_reconciliation_issues~"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_reconciliation_items_~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_vouchers_commerce_refund_requests_tenan~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceAdjustmentVoucherEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_adjustment_voucher_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_adjustment_voucher_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceAdjustmentVoucher", null) - .WithMany() - .HasForeignKey("TenantId", "VoucherId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_adjustment_voucher_events_commerce_adjustment_vouc~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationBatch", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_batches_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_batches_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssue", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedTo") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_assigned_to"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ResolvedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issues_users_resolved_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_reconciliation_batc~"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_reconciliation_item~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_issues_commerce_refund_requests_ten~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationIssueEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_reconciliation_issue_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issue_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationIssue", null) - .WithMany() - .HasForeignKey("TenantId", "IssueId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_issue_events_commerce_reconciliatio~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceReconciliationItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceReconciliationBatch", null) - .WithMany() - .HasForeignKey("TenantId", "BatchId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_reconciliation_items_commerce_reconciliation_batch~"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_payments_tenant_id_payment_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_reconciliation_items_commerce_refund_requests_tena~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.CommerceRefundRequest", null) - .WithMany() - .HasForeignKey("TenantId", "RefundRequestId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_events_commerce_refund_requests_tenant_id_r~"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CommerceRefundRequest", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ProcessedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_processed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("RequestedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_requested_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commerce_refund_requests_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_requests_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commerce_refund_requests_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_commerce_refund_requests_payments_tenant_id_payment_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Coupon", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_coupons_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_coupons_svip_plans_tenant_id_plan_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.CouponRedemption", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_coupon_redemptions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.Coupon", null) - .WithMany() - .HasForeignKey("TenantId", "CouponId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_coupons_tenant_id_coupon_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_orders_tenant_id_order_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_svip_plans_tenant_id_plan_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_coupon_redemptions_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Entitlement", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("RevokedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_entitlements_users_revoked_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entitlements_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_entitlements_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Order", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_orders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_orders_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.SvipPlan", null) - .WithMany() - .HasForeignKey("TenantId", "PlanId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_orders_svip_plans_tenant_id_plan_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_orders_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.OrderItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_order_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_order_items_orders_tenant_id_order_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Payment", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payments_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Order", null) - .WithMany() - .HasForeignKey("TenantId", "OrderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payments_orders_tenant_id_order_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PaymentEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_payment_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Commerce.Payment", null) - .WithMany() - .HasForeignKey("TenantId", "PaymentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_payment_events_payments_tenant_id_payment_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityClaim", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.PointActivityTask", null) - .WithMany() - .HasForeignKey("TenantId", "TaskId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_point_activity_claims_point_activity_tasks_tenant_id_task_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointActivityTask", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_activity_tasks_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_point_exchange_items_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.PointExchangeOrder", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_users_user_id"); - - b.HasOne("Tiku.Domain.Commerce.PointExchangeItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_point_exchange_orders_point_exchange_items_tenant_id_item_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.Product", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_products_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_products_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.SvipPlan", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_svip_plans_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_svip_plans_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_subscriptions_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_usage_records_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_ai_recommendation_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_ai_recommendation_reports_users_user_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_ai_recommendation_reports_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.AppAsset", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_app_assets_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAsset", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_assets_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_updated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("VerifiedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_assets_users_verified_by"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_assets_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetAccessEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_asset_access_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_asset_access_events_users_user_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_asset_access_events_content_assets_tenant_id_asset_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentAssetSecurityScanEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_asset_security_scan_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_asset_security_scan_events_content_assets_tenant_id~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentEntry", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_entries_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_entries_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_entries_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_entries_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportIssue", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportItem", null) - .WithMany() - .HasForeignKey("TenantId", "ItemId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_content_import_issues_content_import_items_tenant_id_item_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportJob", null) - .WithMany() - .HasForeignKey("TenantId", "JobId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_issues_content_import_jobs_tenant_id_job_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentImportJob", null) - .WithMany() - .HasForeignKey("TenantId", "JobId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_items_content_import_jobs_tenant_id_job_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentImportJob", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_import_jobs_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_import_jobs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "TargetCategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_categories_tenant_id_target_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "TargetContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_content_nodes_tenant_id_target_content_~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "TargetQuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_question_banks_tenant_id_target_questio~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "TargetRegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_regions_tenant_id_target_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "TargetSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_import_jobs_subjects_tenant_id_target_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ContentNode", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_nodes_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_nodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_content_nodes_users_updated_by"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_content_nodes_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ParentId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_content_nodes_content_nodes_tenant_id_parent_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_content_nodes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookChapter", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_chapters_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_chapters_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_chapters_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.HandbookSubject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_handbook_chapters_handbook_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookEntry", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_entries_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.HandbookChapter", null) - .WithMany() - .HasForeignKey("TenantId", "ChapterId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_handbook_entries_handbook_chapters_tenant_id_chapter_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_entries_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_entries_content_entries_tenant_id_entry_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.HandbookSubject", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_handbook_subjects_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "MajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_majors_tenant_id_major_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_handbook_subjects_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.ImageAsset", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_images_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.PracticeBlueprint", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_blueprints_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_blueprints_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_blueprints_users_updated_by"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_question_collections_tenant_id_collecti~"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_content_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_blueprints_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollection", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_collections_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collections_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_collections_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_content_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_question_banks_tenant_id_question_bank~"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_collections_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionCollectionItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_question_collection_items_questions_question_owner_tenant_i~"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_question_collections_tenant_id_co~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_collection_items_tenant_question_references_tenant~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionTypeGroup", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_type_groups_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_type_groups_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.QuestionVideo", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_videos_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_videos_questions_tenant_id_question_id"); - - b.HasOne("Tiku.Domain.Content.VideoExplanation", null) - .WithMany() - .HasForeignKey("TenantId", "VideoId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_question_videos_video_explanations_tenant_id_video_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.SubjectShare", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SourceSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_subjects_tenant_id_source_subject_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "TargetSubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_subject_shares_subjects_tenant_id_target_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionBankPreference", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_bank_preferences_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_question_bank_preferences_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_bank_preferences_users_updated_by"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("QuestionBankOwnerTenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_question_bank_preferences_question_banks_question_ba~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.TenantQuestionReference", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_question_references_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_question_references_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_question_references_questions_question_owner_tenant_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordFavorite", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_users_user_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyWord", null) - .WithMany() - .HasForeignKey("TenantId", "WordId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_favorites_vocabulary_words_tenant_id_word_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.UserWordProgress", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_users_user_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyWord", null) - .WithMany() - .HasForeignKey("TenantId", "WordId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_word_progress_vocabulary_words_tenant_id_word_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoExplanation", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_explanations_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_video_explanations_subjects_tenant_id_subject_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VideoPlaybackProgress", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_playback_progress_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_playback_progress_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_video_playback_progress_questions_tenant_id_question_id"); - - b.HasOne("Tiku.Domain.Content.VideoExplanation", null) - .WithMany() - .HasForeignKey("TenantId", "VideoId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_video_playback_progress_video_explanations_tenant_id_video_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyUnit", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_vocabulary_units_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_units_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Content.VocabularyWord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_vocabulary_words_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Content.VocabularyUnit", null) - .WithMany() - .HasForeignKey("TenantId", "UnitId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_vocabulary_words_vocabulary_units_tenant_id_unit_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlement", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("GeneratedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_generated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("PaidBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_paid_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlements_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlements_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlements_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementExportEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ExportedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_export_events_users_exported_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_export_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_export_events_commission_settlements_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementItem", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_items_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_items_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_items_commission_settlements_tenant_i~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CommissionSettlementProof", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReviewedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_users_reviewed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("SubmittedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_users_submitted_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_proofs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "AssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_commission_settlement_proofs_content_assets_tenant_id_asset~"); - - b.HasOne("Tiku.Domain.Growth.CommissionSettlement", null) - .WithMany() - .HasForeignKey("TenantId", "SettlementId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_commission_settlement_proofs_commission_settlements_tenant_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmConfig", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_config_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookLog", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_webhook_log_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.CrmWebhookQueueItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_crm_webhook_queue_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralCode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_codes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_codes_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralLead", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_assigned_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedToUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_assigned_to_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_leads_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_leads_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.ReferralTrack", null) - .WithMany() - .HasForeignKey("TenantId", "FirstTrackId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_leads_referral_tracks_tenant_id_first_track_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralQrcode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_qrcodes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_qrcodes_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTeamEdge", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("LeaderUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_team_edges_users_leader_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("MemberUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_team_edges_users_member_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_team_edges_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.ReferralTrack", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReferrerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_users_referrer_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("TargetUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_users_target_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_referral_tracks_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Growth.ReferralLead", null) - .WithMany() - .HasForeignKey("TenantId", "LeadId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_referral_tracks_referral_leads_tenant_id_lead_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Growth.TenantCommissionSetting", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_commission_settings_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_commission_settings_users_updated_by"); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.StudentProfile", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_student_profiles_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_student_profiles_users_user_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.Major", null) - .WithMany() - .HasForeignKey("TenantId", "SelectedMajorId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_majors_tenant_id_selected_major_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SelectedSchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_student_profiles_schools_tenant_id_selected_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Identity.UserIdentity", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_identities_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportIssue", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_import_issues_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Import.PocketBaseImportRun", null) - .WithMany() - .HasForeignKey("TenantId", "RunId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_pb_import_issues_pb_import_runs_tenant_id_run_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseImportRun", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_import_runs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Import.PocketBaseRawRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_raw_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Import.PocketBaseImportRun", null) - .WithMany() - .HasForeignKey("TenantId", "RunId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_pb_raw_records_pb_import_runs_tenant_id_run_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.AnswerRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_answer_records_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_answer_records_users_user_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSessionQuestion", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId", "SessionQuestionId") - .HasPrincipalKey("TenantId", "PracticeSessionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_answer_records_practice_session_questions_tenant_id_practic~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "UserId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "UserId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_answer_records_practice_sessions_tenant_id_user_id_practice~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.DashboardDailyStat", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_dashboard_daily_stats_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_dashboard_daily_stats_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ExamDate", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_exam_dates_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_exam_dates_regions_tenant_id_region_id"); - - b.HasOne("Tiku.Domain.Catalog.School", null) - .WithMany() - .HasForeignKey("TenantId", "SchoolId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_exam_dates_schools_tenant_id_school_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.FavoriteQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_favorite_questions_questions_question_owner_tenant_id_quest~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_favorite_questions_tenant_question_references_tenant_id_que~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeAccessEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_access_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_access_events_users_user_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_practice_access_events_practice_sessions_tenant_id_practice~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeDailyUsage", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_daily_usage_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_daily_usage_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSession", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_sessions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_sessions_users_user_id"); - - b.HasOne("Tiku.Domain.Content.PracticeBlueprint", null) - .WithMany() - .HasForeignKey("TenantId", "BlueprintId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_practice_blueprints_tenant_id_blueprint_id"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_question_collections_tenant_id_collection~"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_sessions_content_entries_tenant_id_entry_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_practice_sessions_tenant_id_prac~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionVersion", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId", "QuestionVersionId") - .HasPrincipalKey("TenantId", "QuestionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_question_versions_question_owner~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "QuestionOwnerTenantId", "QuestionId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_practice_session_questions_tenant_question_references_tenan~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReport", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_users_user_id"); - - b.HasOne("Tiku.Domain.Content.PracticeBlueprint", null) - .WithMany() - .HasForeignKey("TenantId", "BlueprintId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_session_reports_practice_blueprints_tenant_id_blue~"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "CollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_practice_session_reports_question_collections_tenant_id_col~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_reports_practice_sessions_tenant_id_practi~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.PracticeSessionReportSection", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.PracticeSession", null) - .WithMany() - .HasForeignKey("TenantId", "PracticeSessionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_practice_sessions_tenant_i~"); - - b.HasOne("Tiku.Domain.Learning.PracticeSessionReport", null) - .WithMany() - .HasForeignKey("TenantId", "ReportId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_practice_session_report_sections_practice_session_reports_t~"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RecentPractice", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_recent_practices_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_recent_practices_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.Report", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("HandledBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_reports_users_handled_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_reports_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_reports_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_reports_questions_tenant_id_question_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.ReportStatusEvent", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_report_status_events_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_report_status_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Learning.Report", null) - .WithMany() - .HasForeignKey("TenantId", "ReportId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_report_status_events_reports_tenant_id_report_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.RevenueDailyStat", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_revenue_daily_stats_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_revenue_daily_stats_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.UserScoreEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_score_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_score_events_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Learning.WrongQuestion", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_users_user_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("QuestionOwnerTenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_wrong_questions_questions_question_owner_tenant_id_question~"); - - b.HasOne("Tiku.Domain.Content.TenantQuestionReference", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionReferenceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_wrong_questions_tenant_question_references_tenant_id_questi~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Announcement", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_announcements_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.AuditLog", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ActorUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_audit_logs_users_actor_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_audit_logs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackendMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_backend_menus_backend_permissions_permission_code"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_background_jobs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Content.ContentAsset", null) - .WithMany() - .HasForeignKey("TenantId", "OutputAssetId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_background_jobs_content_assets_tenant_id_output_asset_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Badge", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_badges_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Banner", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_banners_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_banners_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.Faq", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_faqs_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_faqs_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRoleMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendMenu", null) - .WithMany() - .HasForeignKey("MenuCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_menus_backend_menus_menu_code"); - - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_menus_platform_backend_roles_role_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendRolePermission", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_permissions_backend_permissions_permi~"); - - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_role_permissions_platform_backend_roles_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.PlatformBackendUserRole", b => - { - b.HasOne("Tiku.Domain.Operations.PlatformBackendRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_user_roles_platform_backend_roles_role_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_backend_user_roles_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRole", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_roles_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRoleMenu", b => - { - b.HasOne("Tiku.Domain.Operations.BackendMenu", null) - .WithMany() - .HasForeignKey("MenuCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_menus_backend_menus_menu_code"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_menus_tenant_backend_roles_tenant_id_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendRolePermission", b => - { - b.HasOne("Tiku.Domain.Operations.BackendPermission", null) - .WithMany() - .HasForeignKey("PermissionCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_permissions_backend_permissions_permiss~"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_role_permissions_tenant_backend_roles_tenant~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantBackendUserRole", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_user_roles_users_user_id"); - - b.HasOne("Tiku.Domain.Operations.TenantBackendRole", null) - .WithMany() - .HasForeignKey("TenantId", "RoleId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_backend_user_roles_tenant_backend_roles_tenant_id_ro~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantContentNotification", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_content_notifications_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReadBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_content_notifications_users_read_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_content_notifications_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "SourceQuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_tenant_content_notifications_question_banks_tenant_id_sourc~"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.TenantThemeConfig", b => - { - b.HasOne("Tiku.Domain.Operations.TenantThemeTemplate", null) - .WithMany() - .HasForeignKey("ActiveTemplateCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_tenant_theme_templates_active_template~"); - - b.HasOne("Tiku.Domain.Operations.TenantThemeTemplate", null) - .WithMany() - .HasForeignKey("DraftTemplateCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_tenant_theme_templates_draft_template_~"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("DraftUpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_users_draft_updated_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("PublishedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_theme_configs_users_published_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_theme_configs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserBadge", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("GrantedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_user_badges_users_granted_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_badges_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_user_badges_users_user_id"); - - b.HasOne("Tiku.Domain.Operations.Badge", null) - .WithMany() - .HasForeignKey("TenantId", "BadgeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_user_badges_badges_tenant_id_badge_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Operations.UserNotification", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_user_notifications_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_notifications_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_user_notifications_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlanModuleEntitlement", b => - { - b.HasOne("Tiku.Domain.Platform.ProductModule", null) - .WithMany() - .HasForeignKey("ModuleCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_plan_module_entitlements_product_modules_module_code"); - - b.HasOne("Tiku.Domain.Platform.PlatformSaasPlan", null) - .WithMany() - .HasForeignKey("PlanCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_plan_module_entitlements_platform_saas_plans_plan_code"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AcknowledgedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_platform_audit_alerts_users_acknowledged_by"); - - b.HasOne("Tiku.Domain.Operations.AuditLog", null) - .WithMany() - .HasForeignKey("AuditLogId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_audit_alerts_audit_logs_audit_log_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ResolvedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_platform_audit_alerts_users_resolved_by"); - - b.HasOne("Tiku.Domain.Platform.PlatformAuditAlertRule", null) - .WithMany() - .HasForeignKey("RuleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_audit_alerts_platform_audit_alert_rules_rule_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_platform_audit_alerts_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlertRule", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_platform_audit_alert_rules_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => - { - b.HasOne("Tiku.Domain.Platform.PlatformDunningNotificationChannel", null) - .WithMany() - .HasForeignKey("ChannelId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_platform_dunning_notif~"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoices_tenant~"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoiceReminder", null) - .WithMany() - .HasForeignKey("TenantId", "ReminderId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoice_reminde~"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Platform.TenantBillingProfile", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_billing_profiles_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoices_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoices_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReceivedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_payments_users_received_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_reminders_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenant_invoices_tenant_id_invoice_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantModuleOverride", b => - { - b.HasOne("Tiku.Domain.Platform.ProductModule", null) - .WithMany() - .HasForeignKey("ModuleCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired() - .HasConstraintName("fk_tenant_module_overrides_product_modules_module_code"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_module_overrides_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_questions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Category", null) - .WithMany() - .HasForeignKey("TenantId", "CategoryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_categories_tenant_id_category_id"); - - b.HasOne("Tiku.Domain.Content.ContentNode", null) - .WithMany() - .HasForeignKey("TenantId", "ContentNodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_content_nodes_tenant_id_content_node_id"); - - b.HasOne("Tiku.Domain.Content.ContentEntry", null) - .WithMany() - .HasForeignKey("TenantId", "EntryId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_content_entries_tenant_id_entry_id"); - - b.HasOne("Tiku.Domain.Catalog.ModuleNode", null) - .WithMany() - .HasForeignKey("TenantId", "NodeId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_module_nodes_tenant_id_node_id"); - - b.HasOne("Tiku.Domain.Content.QuestionCollection", null) - .WithMany() - .HasForeignKey("TenantId", "PrimaryCollectionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_collections_tenant_id_primary_collection~"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionBank", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionBankId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_banks_tenant_id_question_bank_id"); - - b.HasOne("Tiku.Domain.Catalog.Subject", null) - .WithMany() - .HasForeignKey("TenantId", "SubjectId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_subjects_tenant_id_subject_id"); - - b.HasOne("Tiku.Domain.QuestionBanks.QuestionVersion", null) - .WithMany() - .HasForeignKey("TenantId", "Id", "CurrentVersionId") - .HasPrincipalKey("TenantId", "QuestionId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_questions_question_versions_tenant_id_id_current_version_id"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionBank", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_banks_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_question_banks_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.QuestionBanks.QuestionVersion", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_question_versions_users_created_by"); - - b.HasOne("Tiku.Domain.QuestionBanks.Question", null) - .WithMany() - .HasForeignKey("TenantId", "QuestionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_question_versions_questions_tenant_id_question_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthChallenge", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_auth_challenges_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_challenges_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthLoginEvent", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_auth_login_events_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_auth_login_events_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.AuthSession", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .HasConstraintName("fk_auth_sessions_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_auth_sessions_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsSendRateLimit", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sms_send_rate_limits_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.SmsVerificationCode", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_sms_verification_codes_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.Tenant", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("OwnerUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenants_users_owner_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantAuthPolicy", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Tenancy.TenantAuthPolicy", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_auth_policies_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantBranding", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Tenancy.TenantBranding", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_branding_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClass", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_classes_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_classes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_classes_users_updated_by"); - - b.HasOne("Tiku.Domain.Catalog.Region", null) - .WithMany() - .HasForeignKey("TenantId", "RegionId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Restrict) - .HasConstraintName("fk_tenant_classes_regions_tenant_id_region_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantClassMember", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_users_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.TenantClass", null) - .WithMany() - .HasForeignKey("TenantId", "ClassId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_class_members_tenant_classes_tenant_id_class_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantDomain", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_domains_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantExternalProvider", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_external_providers_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantFrontendConfig", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_frontend_configs_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantMembership", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_memberships_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_memberships_users_user_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSecret", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_secrets_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantSettings", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithOne() - .HasForeignKey("Tiku.Domain.Tenancy.TenantSettings", "TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_settings_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentFollowup", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("AssignedToUserId") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_users_assigned_to_user_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CompletedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_users_completed_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_followups_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_followups_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Tenancy.TenantClass", null) - .WithMany() - .HasForeignKey("TenantId", "ClassId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_followups_tenant_classes_tenant_id_class_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Tenancy.TenantStudentNote", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_notes_users_created_by"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("StudentUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_notes_users_student_user_id"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_student_notes_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("UpdatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_student_notes_users_updated_by"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260729012205_AddDistributedSecurityFoundation.cs b/Tiku.Infrastructure/Persistence/Migrations/20260729012205_AddDistributedSecurityFoundation.cs deleted file mode 100644 index 92e999e..0000000 --- a/Tiku.Infrastructure/Persistence/Migrations/20260729012205_AddDistributedSecurityFoundation.cs +++ /dev/null @@ -1,297 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Tiku.Infrastructure.Persistence.Migrations -{ - /// - public partial class AddDistributedSecurityFoundation : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddUniqueConstraint( - name: "ak_platform_saas_plans_code", - table: "platform_saas_plans", - column: "code"); - - migrationBuilder.CreateTable( - name: "inbox_state", - columns: table => new - { - id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - message_id = table.Column(type: "uuid", nullable: false), - consumer_id = table.Column(type: "uuid", nullable: false), - lock_id = table.Column(type: "uuid", nullable: false), - row_version = table.Column(type: "bytea", rowVersion: true, nullable: true), - received = table.Column(type: "timestamp with time zone", nullable: false), - receive_count = table.Column(type: "integer", nullable: false), - expiration_time = table.Column(type: "timestamp with time zone", nullable: true), - consumed = table.Column(type: "timestamp with time zone", nullable: true), - delivered = table.Column(type: "timestamp with time zone", nullable: true), - last_sequence_number = table.Column(type: "bigint", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_inbox_state", x => x.id); - table.UniqueConstraint("ak_inbox_state_message_id_consumer_id", x => new { x.message_id, x.consumer_id }); - }); - - migrationBuilder.CreateTable( - name: "outbox_state", - columns: table => new - { - outbox_id = table.Column(type: "uuid", nullable: false), - lock_id = table.Column(type: "uuid", nullable: false), - row_version = table.Column(type: "bytea", rowVersion: true, nullable: true), - created = table.Column(type: "timestamp with time zone", nullable: false), - delivered = table.Column(type: "timestamp with time zone", nullable: true), - last_sequence_number = table.Column(type: "bigint", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_outbox_state", x => x.outbox_id); - }); - - migrationBuilder.CreateTable( - name: "product_modules", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - code = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), - status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - sort_order = table.Column(type: "integer", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), - updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("pk_product_modules", x => x.id); - table.UniqueConstraint("ak_product_modules_code", x => x.code); - }); - - migrationBuilder.CreateTable( - name: "tenant_auth_policies", - columns: table => new - { - tenant_id = table.Column(type: "uuid", nullable: false), - allow_external_student_self_registration = table.Column(type: "boolean", nullable: false, defaultValue: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), - updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("pk_tenant_auth_policies", x => x.tenant_id); - table.ForeignKey( - name: "fk_tenant_auth_policies_tenants_tenant_id", - column: x => x.tenant_id, - principalTable: "tenants", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - // Existing tenants retain the historical external-login behavior. New tenants - // receive an explicit fail-closed policy when created by PlatformAdminService. - migrationBuilder.Sql(""" - INSERT INTO tenant_auth_policies - (tenant_id, allow_external_student_self_registration, created_at, updated_at) - SELECT id, TRUE, now(), now() - FROM tenants - ON CONFLICT (tenant_id) DO NOTHING; - """); - - migrationBuilder.CreateTable( - name: "outbox_message", - columns: table => new - { - sequence_number = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - enqueue_time = table.Column(type: "timestamp with time zone", nullable: true), - sent_time = table.Column(type: "timestamp with time zone", nullable: false), - headers = table.Column(type: "text", nullable: true), - properties = table.Column(type: "text", nullable: true), - inbox_message_id = table.Column(type: "uuid", nullable: true), - inbox_consumer_id = table.Column(type: "uuid", nullable: true), - outbox_id = table.Column(type: "uuid", nullable: true), - message_id = table.Column(type: "uuid", nullable: false), - content_type = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - message_type = table.Column(type: "text", nullable: false), - body = table.Column(type: "text", nullable: false), - conversation_id = table.Column(type: "uuid", nullable: true), - correlation_id = table.Column(type: "uuid", nullable: true), - initiator_id = table.Column(type: "uuid", nullable: true), - request_id = table.Column(type: "uuid", nullable: true), - source_address = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - destination_address = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - response_address = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - fault_address = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - expiration_time = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("pk_outbox_message", x => x.sequence_number); - table.ForeignKey( - name: "fk_outbox_message_inbox_state_inbox_message_id_inbox_consumer_~", - columns: x => new { x.inbox_message_id, x.inbox_consumer_id }, - principalTable: "inbox_state", - principalColumns: new[] { "message_id", "consumer_id" }); - table.ForeignKey( - name: "fk_outbox_message_outbox_state_outbox_id", - column: x => x.outbox_id, - principalTable: "outbox_state", - principalColumn: "outbox_id"); - }); - - migrationBuilder.CreateTable( - name: "plan_module_entitlements", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - plan_code = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - module_code = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - enabled = table.Column(type: "boolean", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("pk_plan_module_entitlements", x => x.id); - table.ForeignKey( - name: "fk_plan_module_entitlements_platform_saas_plans_plan_code", - column: x => x.plan_code, - principalTable: "platform_saas_plans", - principalColumn: "code", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_plan_module_entitlements_product_modules_module_code", - column: x => x.module_code, - principalTable: "product_modules", - principalColumn: "code", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "tenant_module_overrides", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - module_code = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - mode = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - expires_at = table.Column(type: "timestamp with time zone", nullable: true), - reason = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), - tenant_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), - updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("pk_tenant_module_overrides", x => x.id); - table.UniqueConstraint("ak_tenant_module_overrides_tenant_id_id", x => new { x.tenant_id, x.id }); - table.ForeignKey( - name: "fk_tenant_module_overrides_product_modules_module_code", - column: x => x.module_code, - principalTable: "product_modules", - principalColumn: "code", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "fk_tenant_module_overrides_tenants_tenant_id", - column: x => x.tenant_id, - principalTable: "tenants", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "ix_inbox_state_delivered", - table: "inbox_state", - column: "delivered"); - - migrationBuilder.CreateIndex( - name: "ix_outbox_message_enqueue_time", - table: "outbox_message", - column: "enqueue_time"); - - migrationBuilder.CreateIndex( - name: "ix_outbox_message_expiration_time", - table: "outbox_message", - column: "expiration_time"); - - migrationBuilder.CreateIndex( - name: "ix_outbox_message_inbox_message_id_inbox_consumer_id_sequence_~", - table: "outbox_message", - columns: new[] { "inbox_message_id", "inbox_consumer_id", "sequence_number" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_outbox_message_outbox_id_sequence_number", - table: "outbox_message", - columns: new[] { "outbox_id", "sequence_number" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_outbox_state_created", - table: "outbox_state", - column: "created"); - - migrationBuilder.CreateIndex( - name: "ix_plan_module_entitlements_module_code", - table: "plan_module_entitlements", - column: "module_code"); - - migrationBuilder.CreateIndex( - name: "ix_plan_module_entitlements_plan_code_module_code", - table: "plan_module_entitlements", - columns: new[] { "plan_code", "module_code" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_product_modules_code", - table: "product_modules", - column: "code", - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_tenant_module_overrides_module_code", - table: "tenant_module_overrides", - column: "module_code"); - - migrationBuilder.CreateIndex( - name: "ix_tenant_module_overrides_tenant_id_module_code", - table: "tenant_module_overrides", - columns: new[] { "tenant_id", "module_code" }, - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "outbox_message"); - - migrationBuilder.DropTable( - name: "plan_module_entitlements"); - - migrationBuilder.DropTable( - name: "tenant_auth_policies"); - - migrationBuilder.DropTable( - name: "tenant_module_overrides"); - - migrationBuilder.DropTable( - name: "inbox_state"); - - migrationBuilder.DropTable( - name: "outbox_state"); - - migrationBuilder.DropTable( - name: "product_modules"); - - migrationBuilder.DropUniqueConstraint( - name: "ak_platform_saas_plans_code", - table: "platform_saas_plans"); - } - } -} diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260729025218_EnforceProductModuleCatalog.cs b/Tiku.Infrastructure/Persistence/Migrations/20260729025218_EnforceProductModuleCatalog.cs deleted file mode 100644 index e56c8f5..0000000 --- a/Tiku.Infrastructure/Persistence/Migrations/20260729025218_EnforceProductModuleCatalog.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Tiku.Infrastructure.Persistence.Migrations -{ - /// - public partial class EnforceProductModuleCatalog : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(""" - INSERT INTO product_modules - (id, code, name, status, sort_order, created_at, updated_at) - VALUES - ('10000000-0000-0000-0000-000000000001', 'dashboard', 'Dashboard', 'active', 10, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('10000000-0000-0000-0000-000000000002', 'staff', 'Staff', 'active', 20, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('10000000-0000-0000-0000-000000000003', 'role', 'Roles', 'active', 30, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('10000000-0000-0000-0000-000000000004', 'student', 'Students', 'active', 40, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('10000000-0000-0000-0000-000000000005', 'content', 'Content', 'active', 50, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('10000000-0000-0000-0000-000000000006', 'settings', 'Settings', 'active', 60, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('10000000-0000-0000-0000-000000000007', 'provider', 'Providers', 'active', 70, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('10000000-0000-0000-0000-000000000008', 'commerce', 'Commerce', 'active', 80, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('10000000-0000-0000-0000-000000000009', 'crm', 'CRM', 'active', 90, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('10000000-0000-0000-0000-00000000000a', 'commission', 'Commission', 'active', 100, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP), - ('10000000-0000-0000-0000-00000000000b', 'job', 'Background Jobs', 'active', 110, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) - ON CONFLICT (code) DO UPDATE SET - name = EXCLUDED.name, - status = 'active', - sort_order = EXCLUDED.sort_order, - updated_at = CURRENT_TIMESTAMP; - - INSERT INTO plan_module_entitlements (id, plan_code, module_code, enabled) - SELECT - md5('plan-module:' || plan.code || ':' || module.code)::uuid, - plan.code, - module.code, - TRUE - FROM platform_saas_plans AS plan - CROSS JOIN product_modules AS module - WHERE module.code IN - ('dashboard', 'staff', 'role', 'student', 'content', 'settings', - 'provider', 'commerce', 'crm', 'commission', 'job') - AND NOT EXISTS ( - SELECT 1 - FROM plan_module_entitlements AS existing - WHERE existing.plan_code = plan.code) - ON CONFLICT (plan_code, module_code) DO NOTHING; - """); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(""" - DELETE FROM product_modules - WHERE code IN - ('dashboard', 'staff', 'role', 'student', 'content', 'settings', - 'provider', 'commerce', 'crm', 'commission', 'job'); - """); - } - } -} diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260729025218_EnforceProductModuleCatalog.Designer.cs b/Tiku.Infrastructure/Persistence/Migrations/20260729053904_InitialSchema.Designer.cs similarity index 92% rename from Tiku.Infrastructure/Persistence/Migrations/20260729025218_EnforceProductModuleCatalog.Designer.cs rename to Tiku.Infrastructure/Persistence/Migrations/20260729053904_InitialSchema.Designer.cs index e28e3f8..4bfd9fd 100644 --- a/Tiku.Infrastructure/Persistence/Migrations/20260729025218_EnforceProductModuleCatalog.Designer.cs +++ b/Tiku.Infrastructure/Persistence/Migrations/20260729053904_InitialSchema.Designer.cs @@ -13,8 +13,8 @@ using Tiku.Infrastructure.Persistence; namespace Tiku.Infrastructure.Persistence.Migrations { [DbContext(typeof(TikuDbContext))] - [Migration("20260729025218_EnforceProductModuleCatalog")] - partial class EnforceProductModuleCatalog + [Migration("20260729053904_InitialSchema")] + partial class InitialSchema { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -3937,132 +3937,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations }); }); - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BillingCycle") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("billing_cycle"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PlanCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("plan_code"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_subscriptions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_subscriptions_tenant_id_id"); - - b.HasIndex("TenantId", "Status", "ExpiresAt") - .HasDatabaseName("ix_tenant_subscriptions_tenant_id_status_expires_at"); - - b.ToTable("tenant_subscriptions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MetricKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("metric_key"); - - b.Property("MetricValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)") - .HasColumnName("metric_value"); - - b.Property("PeriodEnd") - .HasColumnType("date") - .HasColumnName("period_end"); - - b.Property("PeriodStart") - .HasColumnType("date") - .HasColumnName("period_start"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_usage_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_usage_records_tenant_id_id"); - - b.HasIndex("TenantId", "MetricKey", "PeriodStart", "PeriodEnd") - .HasDatabaseName("ix_tenant_usage_records_tenant_id_metric_key_period_start_peri~"); - - b.ToTable("tenant_usage_records", (string)null); - }); - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => { b.Property("Id") @@ -10697,18 +10571,18 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnType("boolean") .HasColumnName("is_system"); - b.Property("Module") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("module"); - b.Property("Name") .IsRequired() .HasMaxLength(200) .HasColumnType("character varying(200)") .HasColumnName("name"); + b.Property("PermissionModuleCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("permission_module_code"); + b.Property("SortOrder") .HasColumnType("integer") .HasColumnName("sort_order"); @@ -10729,8 +10603,11 @@ namespace Tiku.Infrastructure.Persistence.Migrations .IsUnique() .HasDatabaseName("ix_backend_permissions_code"); - b.HasIndex("Area", "Module", "SortOrder") - .HasDatabaseName("ix_backend_permissions_area_module_sort_order"); + b.HasIndex("PermissionModuleCode") + .HasDatabaseName("ix_backend_permissions_permission_module_code"); + + b.HasIndex("Area", "PermissionModuleCode", "SortOrder") + .HasDatabaseName("ix_backend_permissions_area_permission_module_code_sort_order"); b.ToTable("backend_permissions", (string)null); }); @@ -11976,7 +11853,7 @@ namespace Tiku.Infrastructure.Persistence.Migrations b.ToTable("user_notifications", (string)null); }); - modelBuilder.Entity("Tiku.Domain.Platform.PlanModuleEntitlement", b => + modelBuilder.Entity("Tiku.Domain.Platform.PermissionModule", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -11984,33 +11861,67 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnName("id") .HasDefaultValueSql("gen_random_uuid()"); - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("ModuleCode") + b.Property("Area") .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("module_code"); + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("area"); - b.Property("PlanCode") + b.Property("Code") .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("plan_code"); + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("code"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("description"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("RequiredFeatureCode") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("required_feature_code"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasColumnName("sort_order"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); b.HasKey("Id") - .HasName("pk_plan_module_entitlements"); + .HasName("pk_permission_modules"); - b.HasIndex("ModuleCode") - .HasDatabaseName("ix_plan_module_entitlements_module_code"); + b.HasAlternateKey("Code") + .HasName("ak_permission_modules_code"); - b.HasIndex("PlanCode", "ModuleCode") + b.HasIndex("Code") .IsUnique() - .HasDatabaseName("ix_plan_module_entitlements_plan_code_module_code"); + .HasDatabaseName("ix_permission_modules_code"); - b.ToTable("plan_module_entitlements", (string)null); + b.HasIndex("RequiredFeatureCode") + .HasDatabaseName("ix_permission_modules_required_feature_code"); + + b.HasIndex("Area", "SortOrder") + .HasDatabaseName("ix_permission_modules_area_sort_order"); + + b.ToTable("permission_modules", (string)null); }); modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => @@ -12238,7 +12149,7 @@ namespace Tiku.Infrastructure.Persistence.Migrations b.ToTable("platform_audit_alert_rules", (string)null); }); - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationChannel", b => + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingDunningNotificationChannel", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -12331,24 +12242,24 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnName("webhook_url"); b.HasKey("Id") - .HasName("pk_platform_dunning_notification_channels"); + .HasName("pk_platform_billing_dunning_notification_channels"); b.HasIndex("ChannelCode") .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_channels_channel_code"); + .HasDatabaseName("ix_platform_billing_dunning_notification_channels_channel_code"); b.HasIndex("Enabled", "MinReminderLevel", "ChannelCode") - .HasDatabaseName("ix_platform_dunning_notification_channels_enabled_min_reminder~"); + .HasDatabaseName("ix_platform_billing_dunning_notification_channels_enabled_min_~"); - b.ToTable("platform_dunning_notification_channels", null, t => + b.ToTable("platform_billing_dunning_notification_channels", null, t => { - t.HasCheckConstraint("ck_platform_dunning_channels_level", "min_reminder_level between 1 and 20"); + t.HasCheckConstraint("ck_platform_billing_dunning_channels_level", "min_reminder_level between 1 and 20"); - t.HasCheckConstraint("ck_platform_dunning_channels_timeout", "timeout_seconds between 1 and 60"); + t.HasCheckConstraint("ck_platform_billing_dunning_channels_timeout", "timeout_seconds between 1 and 60"); }); }); - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingDunningNotificationEvent", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -12443,43 +12354,43 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasDefaultValueSql("now()"); b.HasKey("Id") - .HasName("pk_platform_dunning_notification_events"); + .HasName("pk_platform_billing_dunning_notification_events"); b.HasAlternateKey("TenantId", "Id") - .HasName("ak_platform_dunning_notification_events_tenant_id_id"); + .HasName("ak_platform_billing_dunning_notification_events_tenant_id_id"); b.HasIndex("ChannelId") - .HasDatabaseName("ix_platform_dunning_notification_events_channel_id"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_channel_id"); b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_invoice_id"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_tenant_id_invo~"); b.HasIndex("TenantId", "ReminderId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_reminder_id"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_tenant_id_remi~"); b.HasIndex("InvoiceId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_invoice_id_status_crea~"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_invoice_id_sta~"); b.HasIndex("ReminderId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_reminder_id_status_cre~"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_reminder_id_st~"); b.HasIndex("TenantId", "ChannelId", "ReminderId") .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_channel_id_r~"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_tenant_id_chan~"); b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_status_creat~"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_tenant_id_stat~"); b.HasIndex("Status", "NextAttemptAt", "ScheduledAt", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_status_next_attempt_at~"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_status_next_at~"); - b.ToTable("platform_dunning_notification_events", null, t => + b.ToTable("platform_billing_dunning_notification_events", null, t => { - t.HasCheckConstraint("ck_platform_dunning_events_attempts", "attempts >= 0"); + t.HasCheckConstraint("ck_platform_billing_dunning_events_attempts", "attempts >= 0"); }); }); - modelBuilder.Entity("Tiku.Domain.Platform.PlatformSaasPlan", b => + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingInvoice", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -12487,21 +12398,11 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnName("id") .HasDefaultValueSql("gen_random_uuid()"); - b.Property("BaseAmountCents") - .HasColumnType("integer") - .HasColumnName("base_amount_cents"); - - b.Property("BillingCycle") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("billing_cycle"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); + b.Property("BillingProfileSnapshot") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("billing_profile_snapshot") + .HasDefaultValueSql("'{}'::jsonb"); b.Property("CreatedAt") .ValueGeneratedOnAdd() @@ -12515,248 +12416,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnType("character varying(10)") .HasColumnName("currency"); - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("FeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("IncludedQuotas") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("included_quotas") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OveragePrices") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("overage_prices") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_saas_plans"); - - b.HasAlternateKey("Code") - .HasName("ak_platform_saas_plans_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_saas_plans_code"); - - b.HasIndex("Status", "SortOrder") - .HasDatabaseName("ix_platform_saas_plans_status_sort_order"); - - b.ToTable("platform_saas_plans", null, t => - { - t.HasCheckConstraint("ck_platform_saas_plans_amount", "base_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.ProductModule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_product_modules"); - - b.HasAlternateKey("Code") - .HasName("ak_product_modules_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_product_modules_code"); - - b.ToTable("product_modules", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("BankAccountMasked") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("bank_account_masked"); - - b.Property("BankName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("bank_name"); - - b.Property("BillingAddress") - .HasColumnType("text") - .HasColumnName("billing_address"); - - b.Property("BillingName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("billing_name"); - - b.Property("ContactEmail") - .HasMaxLength(300) - .HasColumnType("citext") - .HasColumnName("contact_email"); - - b.Property("ContactName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("contact_name"); - - b.Property("ContactPhone") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("contact_phone"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceTitle") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("invoice_title"); - - b.Property("InvoiceType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TaxId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("tax_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_billing_profiles"); - - b.ToTable("tenant_billing_profiles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BalanceCents") - .HasColumnType("integer") - .HasColumnName("balance_cents"); - - b.Property("BillingPeriodEnd") - .HasColumnType("date") - .HasColumnName("billing_period_end"); - - b.Property("BillingPeriodStart") - .HasColumnType("date") - .HasColumnName("billing_period_start"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("DiscountCents") - .HasColumnType("integer") - .HasColumnName("discount_cents"); - b.Property("DueDate") .HasColumnType("date") .HasColumnName("due_date"); @@ -12767,55 +12426,31 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnType("character varying(100)") .HasColumnName("invoice_no"); - b.Property("InvoiceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - b.Property("IssuedAt") .HasColumnType("timestamp with time zone") .HasColumnName("issued_at"); - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); + b.Property("OrderId") + .HasColumnType("uuid") + .HasColumnName("order_id"); b.Property("PaidAt") .HasColumnType("timestamp with time zone") .HasColumnName("paid_at"); - b.Property("PaidCents") - .HasColumnType("integer") - .HasColumnName("paid_cents"); - b.Property("Status") .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("status"); - b.Property("SubtotalCents") - .HasColumnType("integer") - .HasColumnName("subtotal_cents"); - - b.Property("TaxCents") - .HasColumnType("integer") - .HasColumnName("tax_cents"); - b.Property("TenantId") .HasColumnType("uuid") .HasColumnName("tenant_id"); - b.Property("TotalCents") + b.Property("TotalAmountCents") .HasColumnType("integer") - .HasColumnName("total_cents"); + .HasColumnName("total_amount_cents"); b.Property("UpdatedAt") .ValueGeneratedOnAdd() @@ -12824,211 +12459,28 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasDefaultValueSql("now()"); b.HasKey("Id") - .HasName("pk_tenant_invoices"); + .HasName("pk_platform_billing_invoices"); b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoices_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoices_created_by"); + .HasName("ak_platform_billing_invoices_tenant_id_id"); b.HasIndex("TenantId", "InvoiceNo") .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_invoice_no"); + .HasDatabaseName("ix_platform_billing_invoices_tenant_id_invoice_no"); - b.HasIndex("TenantId", "BillingPeriodStart", "BillingPeriodEnd") - .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_billing_period_start_billing_peri~") - .HasFilter("invoice_type = 'usage_overage' and status <> 'void' and metadata->>'source' = 'usage_overage_auto'"); + b.HasIndex("TenantId", "OrderId") + .HasDatabaseName("ix_platform_billing_invoices_tenant_id_order_id"); b.HasIndex("TenantId", "Status", "DueDate") - .HasDatabaseName("ix_tenant_invoices_tenant_id_status_due_date"); + .HasDatabaseName("ix_platform_billing_invoices_tenant_id_status_due_date"); - b.ToTable("tenant_invoices", null, t => + b.ToTable("platform_billing_invoices", null, t => { - t.HasCheckConstraint("ck_tenant_invoices_amounts", "subtotal_cents >= 0 and discount_cents >= 0 and tax_cents >= 0 and total_cents >= 0 and paid_cents >= 0 and balance_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoices_period", "billing_period_end is null or billing_period_start is null or billing_period_end >= billing_period_start"); + t.HasCheckConstraint("ck_platform_billing_invoices_amount", "total_amount_cents >= 0"); }); }); - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("description"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("ItemRefId") - .HasColumnType("uuid") - .HasColumnName("item_ref_id"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Quantity") - .HasPrecision(12, 2) - .HasColumnType("numeric(12,2)") - .HasColumnName("quantity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitAmountCents") - .HasColumnType("integer") - .HasColumnName("unit_amount_cents"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_items_tenant_id_id"); - - b.HasIndex("InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_invoice_id"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_tenant_id_invoice_id"); - - b.ToTable("tenant_invoice_items", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_items_amounts", "unit_amount_cents >= 0 and amount_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoice_items_quantity", "quantity > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("Method") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("method"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_no"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ReceivedBy") - .HasColumnType("uuid") - .HasColumnName("received_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_payments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_payments_tenant_id_id"); - - b.HasIndex("ReceivedBy") - .HasDatabaseName("ix_tenant_invoice_payments_received_by"); - - b.HasIndex("InvoiceId", "Status") - .HasDatabaseName("ix_tenant_invoice_payments_invoice_id_status"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_invoice_id"); - - b.HasIndex("TenantId", "PaymentNo") - .IsUnique() - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_payment_no"); - - b.ToTable("tenant_invoice_payments", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_payments_amount", "amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingInvoiceReminder", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -13113,33 +12565,1092 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasDefaultValueSql("now()"); b.HasKey("Id") - .HasName("pk_tenant_invoice_reminders"); + .HasName("pk_platform_billing_invoice_reminders"); b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_reminders_tenant_id_id"); + .HasName("ak_platform_billing_invoice_reminders_tenant_id_id"); b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoice_reminders_created_by"); + .HasDatabaseName("ix_platform_billing_invoice_reminders_created_by"); b.HasIndex("InvoiceId", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_invoice_id_reminder_date"); + .HasDatabaseName("ix_platform_billing_invoice_reminders_invoice_id_reminder_date"); b.HasIndex("TenantId", "Status", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_status_reminder_date"); + .HasDatabaseName("ix_platform_billing_invoice_reminders_tenant_id_status_reminde~"); b.HasIndex("TenantId", "InvoiceId", "ReminderType", "Channel", "ReminderDate") .IsUnique() - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_invoice_id_reminder_type~"); + .HasDatabaseName("ix_platform_billing_invoice_reminders_tenant_id_invoice_id_rem~"); - b.ToTable("tenant_invoice_reminders", null, t => + b.ToTable("platform_billing_invoice_reminders", null, t => { - t.HasCheckConstraint("ck_tenant_invoice_reminders_balance", "balance_cents_snapshot >= 0"); + t.HasCheckConstraint("ck_platform_billing_invoice_reminders_balance", "balance_cents_snapshot >= 0"); - t.HasCheckConstraint("ck_tenant_invoice_reminders_level", "reminder_level between 1 and 20"); + t.HasCheckConstraint("ck_platform_billing_invoice_reminders_level", "reminder_level between 1 and 20"); }); }); - modelBuilder.Entity("Tiku.Domain.Platform.TenantModuleOverride", b => + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("cancelled_at"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("currency"); + + b.Property("DiscountAmountCents") + .HasColumnType("integer") + .HasColumnName("discount_amount_cents"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("IdempotencyKey") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("idempotency_key"); + + b.Property("OrderNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("order_no"); + + b.Property("OriginalAmountCents") + .HasColumnType("integer") + .HasColumnName("original_amount_cents"); + + b.Property("PaidAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("paid_at"); + + b.Property("Purpose") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("purpose"); + + b.Property("QuoteId") + .HasColumnType("uuid") + .HasColumnName("quote_id"); + + b.Property("Snapshot") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("snapshot") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TotalAmountCents") + .HasColumnType("integer") + .HasColumnName("total_amount_cents"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_platform_billing_orders"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_orders_tenant_id_id"); + + b.HasIndex("TenantId", "IdempotencyKey") + .IsUnique() + .HasDatabaseName("ix_platform_billing_orders_tenant_id_idempotency_key"); + + b.HasIndex("TenantId", "OrderNo") + .IsUnique() + .HasDatabaseName("ix_platform_billing_orders_tenant_id_order_no"); + + b.HasIndex("TenantId", "QuoteId") + .HasDatabaseName("ix_platform_billing_orders_tenant_id_quote_id"); + + b.HasIndex("TenantId", "Status", "CreatedAt") + .HasDatabaseName("ix_platform_billing_orders_tenant_id_status_created_at"); + + b.ToTable("platform_billing_orders", null, t => + { + t.HasCheckConstraint("ck_platform_billing_orders_amounts", "original_amount_cents >= 0 and discount_amount_cents >= 0 and total_amount_cents >= 0 and total_amount_cents = original_amount_cents - discount_amount_cents"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingOrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("AmountCents") + .HasColumnType("integer") + .HasColumnName("amount_cents"); + + b.Property("ItemType") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("item_type"); + + b.Property("OfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("offering_version_id"); + + b.Property("OrderId") + .HasColumnType("uuid") + .HasColumnName("order_id"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasColumnName("quantity"); + + b.Property("Snapshot") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("snapshot") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UnitAmountCents") + .HasColumnType("integer") + .HasColumnName("unit_amount_cents"); + + b.HasKey("Id") + .HasName("pk_platform_billing_order_items"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_order_items_tenant_id_id"); + + b.HasIndex("OfferingVersionId") + .HasDatabaseName("ix_platform_billing_order_items_offering_version_id"); + + b.HasIndex("TenantId", "OrderId", "OfferingVersionId") + .IsUnique() + .HasDatabaseName("ix_platform_billing_order_items_tenant_id_order_id_offering_ve~"); + + b.ToTable("platform_billing_order_items", null, t => + { + t.HasCheckConstraint("ck_platform_billing_order_items_amounts", "quantity > 0 and unit_amount_cents >= 0 and amount_cents >= 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingPayment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("AmountCents") + .HasColumnType("integer") + .HasColumnName("amount_cents"); + + b.Property("ClientPayload") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("client_payload") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("IdempotencyKey") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("idempotency_key"); + + b.Property("Method") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("method"); + + b.Property("OrderId") + .HasColumnType("uuid") + .HasColumnName("order_id"); + + b.Property("PaidAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("paid_at"); + + b.Property("PaymentNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("payment_no"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("provider"); + + b.Property("ProviderTradeNo") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("provider_trade_no"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_platform_billing_payments"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_payments_tenant_id_id"); + + b.HasIndex("TenantId", "IdempotencyKey") + .IsUnique() + .HasDatabaseName("ix_platform_billing_payments_tenant_id_idempotency_key"); + + b.HasIndex("TenantId", "PaymentNo") + .IsUnique() + .HasDatabaseName("ix_platform_billing_payments_tenant_id_payment_no"); + + b.HasIndex("TenantId", "OrderId", "Status") + .HasDatabaseName("ix_platform_billing_payments_tenant_id_order_id_status"); + + b.ToTable("platform_billing_payments", null, t => + { + t.HasCheckConstraint("ck_platform_billing_payments_amount", "amount_cents >= 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingPaymentEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("event_type"); + + b.Property("Payload") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("payload") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("PaymentId") + .HasColumnType("uuid") + .HasColumnName("payment_id"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("provider"); + + b.Property("ProviderEventId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("provider_event_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.HasKey("Id") + .HasName("pk_platform_billing_payment_events"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_payment_events_tenant_id_id"); + + b.HasIndex("TenantId", "PaymentId", "CreatedAt") + .HasDatabaseName("ix_platform_billing_payment_events_tenant_id_payment_id_create~"); + + b.HasIndex("TenantId", "Provider", "ProviderEventId") + .IsUnique() + .HasDatabaseName("ix_platform_billing_payment_events_tenant_id_provider_provider~"); + + b.ToTable("platform_billing_payment_events", (string)null); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingQuote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("currency"); + + b.Property("DiscountAmountCents") + .HasColumnType("integer") + .HasColumnName("discount_amount_cents"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("FeatureSnapshot") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("feature_snapshot") + .HasDefaultValueSql("'[]'::jsonb"); + + b.Property("IdempotencyKey") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("idempotency_key"); + + b.Property("LimitSnapshot") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("limit_snapshot") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("OriginalAmountCents") + .HasColumnType("integer") + .HasColumnName("original_amount_cents"); + + b.Property("Purpose") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("purpose"); + + b.Property("QuoteNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("quote_no"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TotalAmountCents") + .HasColumnType("integer") + .HasColumnName("total_amount_cents"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_platform_billing_quotes"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_quotes_tenant_id_id"); + + b.HasIndex("TenantId", "IdempotencyKey") + .IsUnique() + .HasDatabaseName("ix_platform_billing_quotes_tenant_id_idempotency_key"); + + b.HasIndex("TenantId", "QuoteNo") + .IsUnique() + .HasDatabaseName("ix_platform_billing_quotes_tenant_id_quote_no"); + + b.HasIndex("TenantId", "Status", "ExpiresAt") + .HasDatabaseName("ix_platform_billing_quotes_tenant_id_status_expires_at"); + + b.ToTable("platform_billing_quotes", null, t => + { + t.HasCheckConstraint("ck_platform_billing_quotes_amounts", "original_amount_cents >= 0 and discount_amount_cents >= 0 and total_amount_cents >= 0 and total_amount_cents = original_amount_cents - discount_amount_cents"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingQuoteItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("AmountCents") + .HasColumnType("integer") + .HasColumnName("amount_cents"); + + b.Property("ItemType") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("item_type"); + + b.Property("OfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("offering_version_id"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasColumnName("quantity"); + + b.Property("QuoteId") + .HasColumnType("uuid") + .HasColumnName("quote_id"); + + b.Property("Snapshot") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("snapshot") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UnitAmountCents") + .HasColumnType("integer") + .HasColumnName("unit_amount_cents"); + + b.HasKey("Id") + .HasName("pk_platform_billing_quote_items"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_quote_items_tenant_id_id"); + + b.HasIndex("OfferingVersionId") + .HasDatabaseName("ix_platform_billing_quote_items_offering_version_id"); + + b.HasIndex("TenantId", "QuoteId", "OfferingVersionId") + .IsUnique() + .HasDatabaseName("ix_platform_billing_quote_items_tenant_id_quote_id_offering_ve~"); + + b.ToTable("platform_billing_quote_items", null, t => + { + t.HasCheckConstraint("ck_platform_billing_quote_items_amounts", "quantity > 0 and unit_amount_cents >= 0 and amount_cents >= 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingRefund", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("AmountCents") + .HasColumnType("integer") + .HasColumnName("amount_cents"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("completed_at"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("OrderId") + .HasColumnType("uuid") + .HasColumnName("order_id"); + + b.Property("PaymentId") + .HasColumnType("uuid") + .HasColumnName("payment_id"); + + b.Property("ProviderRefundNo") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("provider_refund_no"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("reason"); + + b.Property("RefundNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("refund_no"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_platform_billing_refunds"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_refunds_tenant_id_id"); + + b.HasIndex("TenantId", "PaymentId") + .HasDatabaseName("ix_platform_billing_refunds_tenant_id_payment_id"); + + b.HasIndex("TenantId", "RefundNo") + .IsUnique() + .HasDatabaseName("ix_platform_billing_refunds_tenant_id_refund_no"); + + b.HasIndex("TenantId", "OrderId", "Status") + .HasDatabaseName("ix_platform_billing_refunds_tenant_id_order_id_status"); + + b.ToTable("platform_billing_refunds", null, t => + { + t.HasCheckConstraint("ck_platform_billing_refunds_amount", "amount_cents > 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("category"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("code"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("currency"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("description"); + + b.Property("IsCore") + .HasColumnType("boolean") + .HasColumnName("is_core"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("ReferencePriceCents") + .HasColumnType("integer") + .HasColumnName("reference_price_cents"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasColumnName("sort_order"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_saas_features"); + + b.HasAlternateKey("Code") + .HasName("ak_saas_features_code"); + + b.HasIndex("Code") + .IsUnique() + .HasDatabaseName("ix_saas_features_code"); + + b.HasIndex("Status", "Category", "SortOrder") + .HasDatabaseName("ix_saas_features_status_category_sort_order"); + + b.ToTable("saas_features", null, t => + { + t.HasCheckConstraint("ck_saas_features_reference_price", "reference_price_cents >= 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasFeatureLimitDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("FeatureCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("feature_code"); + + b.Property("IsHardLimit") + .HasColumnType("boolean") + .HasColumnName("is_hard_limit"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("kind"); + + b.Property("MetricCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("metric_code"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("Unit") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("unit"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.Property("WarningPercent") + .HasColumnType("integer") + .HasColumnName("warning_percent"); + + b.HasKey("Id") + .HasName("pk_saas_feature_limit_definitions"); + + b.HasAlternateKey("MetricCode") + .HasName("ak_saas_feature_limit_definitions_metric_code"); + + b.HasIndex("MetricCode") + .IsUnique() + .HasDatabaseName("ix_saas_feature_limit_definitions_metric_code"); + + b.HasIndex("FeatureCode", "Kind") + .HasDatabaseName("ix_saas_feature_limit_definitions_feature_code_kind"); + + b.ToTable("saas_feature_limit_definitions", null, t => + { + t.HasCheckConstraint("ck_saas_feature_limit_warning", "warning_percent between 1 and 100"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOffering", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("code"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("description"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasColumnName("sort_order"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_saas_offerings"); + + b.HasIndex("Code") + .IsUnique() + .HasDatabaseName("ix_saas_offerings_code"); + + b.HasIndex("Type", "Status", "SortOrder") + .HasDatabaseName("ix_saas_offerings_type_status_sort_order"); + + b.ToTable("saas_offerings", (string)null); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOfferingVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("AmountCents") + .HasColumnType("integer") + .HasColumnName("amount_cents"); + + b.Property("BillingCycle") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("billing_cycle"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("currency"); + + b.Property("EffectiveAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("effective_at"); + + b.Property("Metadata") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("metadata") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("OfferingId") + .HasColumnType("uuid") + .HasColumnName("offering_id"); + + b.Property("OriginalAmountCents") + .HasColumnType("integer") + .HasColumnName("original_amount_cents"); + + b.Property("PublishedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("published_at"); + + b.Property("RetiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("retired_at"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.Property("Version") + .HasColumnType("integer") + .HasColumnName("version"); + + b.HasKey("Id") + .HasName("pk_saas_offering_versions"); + + b.HasIndex("OfferingId", "Version") + .IsUnique() + .HasDatabaseName("ix_saas_offering_versions_offering_id_version"); + + b.HasIndex("OfferingId", "Status", "EffectiveAt") + .HasDatabaseName("ix_saas_offering_versions_offering_id_status_effective_at"); + + b.ToTable("saas_offering_versions", null, t => + { + t.HasCheckConstraint("ck_saas_offering_versions_amount", "original_amount_cents >= 0 and amount_cents >= 0 and amount_cents <= original_amount_cents"); + + t.HasCheckConstraint("ck_saas_offering_versions_version", "version > 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOfferingVersionFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("FeatureCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("feature_code"); + + b.Property("OfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("offering_version_id"); + + b.HasKey("Id") + .HasName("pk_saas_offering_version_features"); + + b.HasIndex("FeatureCode") + .HasDatabaseName("ix_saas_offering_version_features_feature_code"); + + b.HasIndex("OfferingVersionId", "FeatureCode") + .IsUnique() + .HasDatabaseName("ix_saas_offering_version_features_offering_version_id_feature_~"); + + b.ToTable("saas_offering_version_features", (string)null); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOfferingVersionLimit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("LimitValue") + .HasColumnType("bigint") + .HasColumnName("limit_value"); + + b.Property("MetricCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("metric_code"); + + b.Property("OfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("offering_version_id"); + + b.HasKey("Id") + .HasName("pk_saas_offering_version_limits"); + + b.HasIndex("MetricCode") + .HasDatabaseName("ix_saas_offering_version_limits_metric_code"); + + b.HasIndex("OfferingVersionId", "MetricCode") + .IsUnique() + .HasDatabaseName("ix_saas_offering_version_limits_offering_version_id_metric_code"); + + b.ToTable("saas_offering_version_limits", null, t => + { + t.HasCheckConstraint("ck_saas_offering_version_limits_value", "limit_value >= 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => + { + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("BankAccountMasked") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("bank_account_masked"); + + b.Property("BankName") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("bank_name"); + + b.Property("BillingAddress") + .HasColumnType("text") + .HasColumnName("billing_address"); + + b.Property("BillingName") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .HasColumnName("billing_name"); + + b.Property("ContactEmail") + .HasMaxLength(300) + .HasColumnType("citext") + .HasColumnName("contact_email"); + + b.Property("ContactName") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("contact_name"); + + b.Property("ContactPhone") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("contact_phone"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("InvoiceTitle") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .HasColumnName("invoice_title"); + + b.Property("InvoiceType") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("invoice_type"); + + b.Property("Metadata") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("metadata") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("TaxId") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("tax_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("TenantId") + .HasName("pk_tenant_billing_profiles"); + + b.ToTable("tenant_billing_profiles", (string)null); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantFeatureOverride", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -13157,19 +13668,20 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("expires_at"); + b.Property("FeatureCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("feature_code"); + b.Property("Mode") .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("mode"); - b.Property("ModuleCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("module_code"); - b.Property("Reason") + .IsRequired() .HasMaxLength(1000) .HasColumnType("character varying(1000)") .HasColumnName("reason"); @@ -13185,19 +13697,277 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasDefaultValueSql("now()"); b.HasKey("Id") - .HasName("pk_tenant_module_overrides"); + .HasName("pk_tenant_feature_overrides"); b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_module_overrides_tenant_id_id"); + .HasName("ak_tenant_feature_overrides_tenant_id_id"); - b.HasIndex("ModuleCode") - .HasDatabaseName("ix_tenant_module_overrides_module_code"); + b.HasIndex("FeatureCode") + .HasDatabaseName("ix_tenant_feature_overrides_feature_code"); - b.HasIndex("TenantId", "ModuleCode") + b.HasIndex("TenantId", "FeatureCode") .IsUnique() - .HasDatabaseName("ix_tenant_module_overrides_tenant_id_module_code"); + .HasDatabaseName("ix_tenant_feature_overrides_tenant_id_feature_code"); - b.ToTable("tenant_module_overrides", (string)null); + b.ToTable("tenant_feature_overrides", (string)null); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantFeatureUsage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("LimitValueSnapshot") + .HasColumnType("bigint") + .HasColumnName("limit_value_snapshot"); + + b.Property("MetricCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("metric_code"); + + b.Property("PeriodEnd") + .HasColumnType("timestamp with time zone") + .HasColumnName("period_end"); + + b.Property("PeriodStart") + .HasColumnType("timestamp with time zone") + .HasColumnName("period_start"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.Property("UsedValue") + .HasColumnType("bigint") + .HasColumnName("used_value"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("bigint") + .HasColumnName("version"); + + b.Property("WarningIssued") + .HasColumnType("boolean") + .HasColumnName("warning_issued"); + + b.HasKey("Id") + .HasName("pk_tenant_feature_usage"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_tenant_feature_usage_tenant_id_id"); + + b.HasIndex("MetricCode") + .HasDatabaseName("ix_tenant_feature_usage_metric_code"); + + b.HasIndex("TenantId", "MetricCode", "PeriodStart", "PeriodEnd") + .IsUnique() + .HasDatabaseName("ix_tenant_feature_usage_tenant_id_metric_code_period_start_per~"); + + b.ToTable("tenant_feature_usage", null, t => + { + t.HasCheckConstraint("ck_tenant_feature_usage_period", "period_end > period_start"); + + t.HasCheckConstraint("ck_tenant_feature_usage_values", "used_value >= 0 and limit_value_snapshot >= 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantSaasSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("BaseOfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("base_offering_version_id"); + + b.Property("CancelAtPeriodEnd") + .HasColumnType("boolean") + .HasColumnName("cancel_at_period_end"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("cancelled_at"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("CurrentPeriodEnd") + .HasColumnType("timestamp with time zone") + .HasColumnName("current_period_end"); + + b.Property("CurrentPeriodStart") + .HasColumnType("timestamp with time zone") + .HasColumnName("current_period_start"); + + b.Property("LifecycleVersion") + .IsConcurrencyToken() + .HasColumnType("bigint") + .HasColumnName("lifecycle_version"); + + b.Property("Metadata") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("metadata") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("ScheduledBaseOfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("scheduled_base_offering_version_id"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("starts_at"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_tenant_saas_subscriptions"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_tenant_saas_subscriptions_tenant_id_id"); + + b.HasIndex("BaseOfferingVersionId") + .HasDatabaseName("ix_tenant_saas_subscriptions_base_offering_version_id"); + + b.HasIndex("ScheduledBaseOfferingVersionId") + .HasDatabaseName("ix_tenant_saas_subscriptions_scheduled_base_offering_version_id"); + + b.HasIndex("TenantId") + .IsUnique() + .HasDatabaseName("ix_tenant_saas_subscriptions_tenant_id") + .HasFilter("status in ('trial', 'active', 'past_due', 'suspended')"); + + b.HasIndex("TenantId", "Status", "CurrentPeriodEnd") + .HasDatabaseName("ix_tenant_saas_subscriptions_tenant_id_status_current_period_e~"); + + b.ToTable("tenant_saas_subscriptions", null, t => + { + t.HasCheckConstraint("ck_tenant_saas_subscriptions_period", "current_period_end > current_period_start and current_period_start >= starts_at"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantSaasSubscriptionItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("ends_at"); + + b.Property("ItemType") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("item_type"); + + b.Property("OfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("offering_version_id"); + + b.Property("SourceOrderItemId") + .HasColumnType("uuid") + .HasColumnName("source_order_item_id"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("starts_at"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("SubscriptionId") + .HasColumnType("uuid") + .HasColumnName("subscription_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_tenant_saas_subscription_items"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_tenant_saas_subscription_items_tenant_id_id"); + + b.HasIndex("OfferingVersionId") + .HasDatabaseName("ix_tenant_saas_subscription_items_offering_version_id"); + + b.HasIndex("TenantId", "SourceOrderItemId") + .HasDatabaseName("ix_tenant_saas_subscription_items_tenant_id_source_order_item_~"); + + b.HasIndex("TenantId", "SubscriptionId", "ItemType") + .IsUnique() + .HasDatabaseName("ux_tenant_saas_subscription_items_current_base") + .HasFilter("item_type = 'base_plan' and status = 'active'"); + + b.HasIndex("TenantId", "SubscriptionId", "Status") + .IsUnique() + .HasDatabaseName("ux_tenant_saas_subscription_items_scheduled_base") + .HasFilter("item_type = 'base_plan' and status = 'scheduled'"); + + b.HasIndex("TenantId", "SubscriptionId", "OfferingVersionId", "Status") + .HasDatabaseName("ix_tenant_saas_subscription_items_tenant_id_subscription_id_of~"); + + b.ToTable("tenant_saas_subscription_items", null, t => + { + t.HasCheckConstraint("ck_tenant_saas_subscription_items_period", "ends_at > starts_at"); + }); }); modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => @@ -14036,6 +14806,13 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasDefaultValue(false) .HasColumnName("allow_external_student_self_registration"); + b.PrimitiveCollection("AllowedStudentLoginMethods") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text[]") + .HasColumnName("allowed_student_login_methods") + .HasDefaultValueSql("ARRAY['password']::text[]"); + b.Property("CreatedAt") .ValueGeneratedOnAdd() .HasColumnType("timestamp with time zone") @@ -15961,26 +16738,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasConstraintName("fk_svip_plans_regions_tenant_id_region_id"); }); - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_subscriptions_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_usage_records_tenants_tenant_id"); - }); - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => { b.HasOne("Tiku.Domain.Tenancy.Tenant", null) @@ -17679,6 +18436,17 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasConstraintName("fk_backend_menus_backend_permissions_permission_code"); }); + modelBuilder.Entity("Tiku.Domain.Operations.BackendPermission", b => + { + b.HasOne("Tiku.Domain.Platform.PermissionModule", null) + .WithMany() + .HasForeignKey("PermissionModuleCode") + .HasPrincipalKey("Code") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_backend_permissions_permission_modules_permission_module_co~"); + }); + modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b => { b.HasOne("Tiku.Domain.Tenancy.Tenant", null) @@ -17976,23 +18744,14 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasConstraintName("fk_user_notifications_users_user_id"); }); - modelBuilder.Entity("Tiku.Domain.Platform.PlanModuleEntitlement", b => + modelBuilder.Entity("Tiku.Domain.Platform.PermissionModule", b => { - b.HasOne("Tiku.Domain.Platform.ProductModule", null) + b.HasOne("Tiku.Domain.Platform.SaasFeature", null) .WithMany() - .HasForeignKey("ModuleCode") + .HasForeignKey("RequiredFeatureCode") .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_plan_module_entitlements_product_modules_module_code"); - - b.HasOne("Tiku.Domain.Platform.PlatformSaasPlan", null) - .WithMany() - .HasForeignKey("PlanCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_plan_module_entitlements_platform_saas_plans_plan_code"); + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_permission_modules_saas_features_required_feature_code"); }); modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => @@ -18039,37 +18798,274 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasConstraintName("fk_platform_audit_alert_rules_tenants_tenant_id"); }); - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingDunningNotificationEvent", b => { - b.HasOne("Tiku.Domain.Platform.PlatformDunningNotificationChannel", null) + b.HasOne("Tiku.Domain.Platform.PlatformBillingDunningNotificationChannel", null) .WithMany() .HasForeignKey("ChannelId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_platform_dunning_notif~"); + .HasConstraintName("fk_platform_billing_dunning_events_channel"); b.HasOne("Tiku.Domain.Tenancy.Tenant", null) .WithMany() .HasForeignKey("TenantId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenants_tenant_id"); + .HasConstraintName("fk_platform_billing_dunning_notification_events_tenants_tenant~"); - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) + b.HasOne("Tiku.Domain.Platform.PlatformBillingInvoice", null) .WithMany() .HasForeignKey("TenantId", "InvoiceId") .HasPrincipalKey("TenantId", "Id") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoices_tenant~"); + .HasConstraintName("fk_platform_billing_dunning_events_invoice"); - b.HasOne("Tiku.Domain.Platform.TenantInvoiceReminder", null) + b.HasOne("Tiku.Domain.Platform.PlatformBillingInvoiceReminder", null) .WithMany() .HasForeignKey("TenantId", "ReminderId") .HasPrincipalKey("TenantId", "Id") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoice_reminde~"); + .HasConstraintName("fk_platform_billing_dunning_events_reminder"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingInvoice", b => + { + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_invoices_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingOrder", null) + .WithMany() + .HasForeignKey("TenantId", "OrderId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_platform_billing_invoices_platform_billing_orders_tenant_id~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingInvoiceReminder", b => + { + b.HasOne("Tiku.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_platform_billing_invoice_reminders_users_created_by"); + + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_invoice_reminders_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingInvoice", null) + .WithMany() + .HasForeignKey("TenantId", "InvoiceId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_invoice_reminders_platform_billing_invoice~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingOrder", b => + { + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_orders_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingQuote", null) + .WithMany() + .HasForeignKey("TenantId", "QuoteId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_platform_billing_orders_platform_billing_quotes_tenant_id_q~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingOrderItem", b => + { + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("OfferingVersionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_platform_billing_order_items_saas_offering_versions_offerin~"); + + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_order_items_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingOrder", null) + .WithMany() + .HasForeignKey("TenantId", "OrderId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_order_items_platform_billing_orders_tenant~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingPayment", b => + { + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_payments_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingOrder", null) + .WithMany() + .HasForeignKey("TenantId", "OrderId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_payments_platform_billing_orders_tenant_id~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingPaymentEvent", b => + { + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_payment_events_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingPayment", null) + .WithMany() + .HasForeignKey("TenantId", "PaymentId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_payment_events_platform_billing_payments_t~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingQuote", b => + { + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_quotes_tenants_tenant_id"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingQuoteItem", b => + { + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("OfferingVersionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_platform_billing_quote_items_saas_offering_versions_offerin~"); + + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_quote_items_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingQuote", null) + .WithMany() + .HasForeignKey("TenantId", "QuoteId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_quote_items_platform_billing_quotes_tenant~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingRefund", b => + { + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_refunds_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingOrder", null) + .WithMany() + .HasForeignKey("TenantId", "OrderId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_platform_billing_refunds_platform_billing_orders_tenant_id_~"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingPayment", null) + .WithMany() + .HasForeignKey("TenantId", "PaymentId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_platform_billing_refunds_platform_billing_payments_tenant_i~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasFeatureLimitDefinition", b => + { + b.HasOne("Tiku.Domain.Platform.SaasFeature", null) + .WithMany() + .HasForeignKey("FeatureCode") + .HasPrincipalKey("Code") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_saas_feature_limit_definitions_saas_features_feature_code"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOfferingVersion", b => + { + b.HasOne("Tiku.Domain.Platform.SaasOffering", null) + .WithMany() + .HasForeignKey("OfferingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_saas_offering_versions_saas_offerings_offering_id"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOfferingVersionFeature", b => + { + b.HasOne("Tiku.Domain.Platform.SaasFeature", null) + .WithMany() + .HasForeignKey("FeatureCode") + .HasPrincipalKey("Code") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_saas_offering_version_features_saas_features_feature_code"); + + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("OfferingVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_saas_offering_version_features_saas_offering_versions_offer~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOfferingVersionLimit", b => + { + b.HasOne("Tiku.Domain.Platform.SaasFeatureLimitDefinition", null) + .WithMany() + .HasForeignKey("MetricCode") + .HasPrincipalKey("MetricCode") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_saas_offering_version_limits_saas_feature_limit_definitions~"); + + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("OfferingVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_saas_offering_version_limits_saas_offering_versions_offerin~"); }); modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => @@ -18082,104 +19078,95 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasConstraintName("fk_tenant_billing_profiles_tenants_tenant_id"); }); - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => + modelBuilder.Entity("Tiku.Domain.Platform.TenantFeatureOverride", b => { - b.HasOne("Tiku.Domain.Identity.User", null) + b.HasOne("Tiku.Domain.Platform.SaasFeature", null) .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoices_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoices_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReceivedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_payments_users_received_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_reminders_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenant_invoices_tenant_id_invoice_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantModuleOverride", b => - { - b.HasOne("Tiku.Domain.Platform.ProductModule", null) - .WithMany() - .HasForeignKey("ModuleCode") + .HasForeignKey("FeatureCode") .HasPrincipalKey("Code") .OnDelete(DeleteBehavior.Restrict) .IsRequired() - .HasConstraintName("fk_tenant_module_overrides_product_modules_module_code"); + .HasConstraintName("fk_tenant_feature_overrides_saas_features_feature_code"); b.HasOne("Tiku.Domain.Tenancy.Tenant", null) .WithMany() .HasForeignKey("TenantId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_tenant_module_overrides_tenants_tenant_id"); + .HasConstraintName("fk_tenant_feature_overrides_tenants_tenant_id"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantFeatureUsage", b => + { + b.HasOne("Tiku.Domain.Platform.SaasFeatureLimitDefinition", null) + .WithMany() + .HasForeignKey("MetricCode") + .HasPrincipalKey("MetricCode") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_tenant_feature_usage_saas_feature_limit_definitions_metric_~"); + + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_feature_usage_tenants_tenant_id"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantSaasSubscription", b => + { + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("BaseOfferingVersionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_tenant_saas_subscriptions_saas_offering_versions_base_offer~"); + + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("ScheduledBaseOfferingVersionId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_tenant_saas_subscriptions_saas_offering_versions_scheduled_~"); + + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_saas_subscriptions_tenants_tenant_id"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantSaasSubscriptionItem", b => + { + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("OfferingVersionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_tenant_saas_subscription_items_saas_offering_versions_offer~"); + + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_saas_subscription_items_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingOrderItem", null) + .WithMany() + .HasForeignKey("TenantId", "SourceOrderItemId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_tenant_saas_subscription_items_platform_billing_order_items~"); + + b.HasOne("Tiku.Domain.Platform.TenantSaasSubscription", null) + .WithMany() + .HasForeignKey("TenantId", "SubscriptionId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_saas_subscription_items_tenant_saas_subscriptions_te~"); }); modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => diff --git a/Tiku.Infrastructure/Persistence/Migrations/20260728031410_InitialSchema.cs b/Tiku.Infrastructure/Persistence/Migrations/20260729053904_InitialSchema.cs similarity index 87% rename from Tiku.Infrastructure/Persistence/Migrations/20260728031410_InitialSchema.cs rename to Tiku.Infrastructure/Persistence/Migrations/20260729053904_InitialSchema.cs index bbff296..78afab5 100644 --- a/Tiku.Infrastructure/Persistence/Migrations/20260728031410_InitialSchema.cs +++ b/Tiku.Infrastructure/Persistence/Migrations/20260729053904_InitialSchema.cs @@ -17,27 +17,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations .Annotation("Npgsql:PostgresExtension:citext", ",,") .Annotation("Npgsql:PostgresExtension:ltree", ",,"); - migrationBuilder.CreateTable( - name: "backend_permissions", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - code = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), - name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - area = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - module = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), - is_system = table.Column(type: "boolean", nullable: false), - sort_order = table.Column(type: "integer", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), - updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("pk_backend_permissions", x => x.id); - table.UniqueConstraint("ak_backend_permissions_code", x => x.code); - }); - migrationBuilder.CreateTable( name: "data_protection_keys", columns: table => new @@ -52,6 +31,45 @@ namespace Tiku.Infrastructure.Persistence.Migrations table.PrimaryKey("pk_data_protection_keys", x => x.id); }); + migrationBuilder.CreateTable( + name: "inbox_state", + columns: table => new + { + id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + message_id = table.Column(type: "uuid", nullable: false), + consumer_id = table.Column(type: "uuid", nullable: false), + lock_id = table.Column(type: "uuid", nullable: false), + row_version = table.Column(type: "bytea", rowVersion: true, nullable: true), + received = table.Column(type: "timestamp with time zone", nullable: false), + receive_count = table.Column(type: "integer", nullable: false), + expiration_time = table.Column(type: "timestamp with time zone", nullable: true), + consumed = table.Column(type: "timestamp with time zone", nullable: true), + delivered = table.Column(type: "timestamp with time zone", nullable: true), + last_sequence_number = table.Column(type: "bigint", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_inbox_state", x => x.id); + table.UniqueConstraint("ak_inbox_state_message_id_consumer_id", x => new { x.message_id, x.consumer_id }); + }); + + migrationBuilder.CreateTable( + name: "outbox_state", + columns: table => new + { + outbox_id = table.Column(type: "uuid", nullable: false), + lock_id = table.Column(type: "uuid", nullable: false), + row_version = table.Column(type: "bytea", rowVersion: true, nullable: true), + created = table.Column(type: "timestamp with time zone", nullable: false), + delivered = table.Column(type: "timestamp with time zone", nullable: true), + last_sequence_number = table.Column(type: "bigint", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_outbox_state", x => x.outbox_id); + }); + migrationBuilder.CreateTable( name: "platform_backend_roles", columns: table => new @@ -71,7 +89,7 @@ namespace Tiku.Infrastructure.Persistence.Migrations }); migrationBuilder.CreateTable( - name: "platform_dunning_notification_channels", + name: "platform_billing_dunning_notification_channels", columns: table => new { id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), @@ -93,34 +111,52 @@ namespace Tiku.Infrastructure.Persistence.Migrations }, constraints: table => { - table.PrimaryKey("pk_platform_dunning_notification_channels", x => x.id); - table.CheckConstraint("ck_platform_dunning_channels_level", "min_reminder_level between 1 and 20"); - table.CheckConstraint("ck_platform_dunning_channels_timeout", "timeout_seconds between 1 and 60"); + table.PrimaryKey("pk_platform_billing_dunning_notification_channels", x => x.id); + table.CheckConstraint("ck_platform_billing_dunning_channels_level", "min_reminder_level between 1 and 20"); + table.CheckConstraint("ck_platform_billing_dunning_channels_timeout", "timeout_seconds between 1 and 60"); }); migrationBuilder.CreateTable( - name: "platform_saas_plans", + name: "saas_features", columns: table => new { id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - code = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + code = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - description = table.Column(type: "text", nullable: true), - billing_cycle = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - base_amount_cents = table.Column(type: "integer", nullable: false), + category = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + reference_price_cents = table.Column(type: "integer", nullable: false), currency = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), - included_quotas = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), - overage_prices = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), - feature_flags = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + is_core = table.Column(type: "boolean", nullable: false), sort_order = table.Column(type: "integer", nullable: false), created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") }, constraints: table => { - table.PrimaryKey("pk_platform_saas_plans", x => x.id); - table.CheckConstraint("ck_platform_saas_plans_amount", "base_amount_cents >= 0"); + table.PrimaryKey("pk_saas_features", x => x.id); + table.UniqueConstraint("ak_saas_features_code", x => x.code); + table.CheckConstraint("ck_saas_features_reference_price", "reference_price_cents >= 0"); + }); + + migrationBuilder.CreateTable( + name: "saas_offerings", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + code = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + type = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + sort_order = table.Column(type: "integer", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_saas_offerings", x => x.id); }); migrationBuilder.CreateTable( @@ -183,56 +219,129 @@ namespace Tiku.Infrastructure.Persistence.Migrations }); migrationBuilder.CreateTable( - name: "backend_menus", + name: "outbox_message", + columns: table => new + { + sequence_number = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + enqueue_time = table.Column(type: "timestamp with time zone", nullable: true), + sent_time = table.Column(type: "timestamp with time zone", nullable: false), + headers = table.Column(type: "text", nullable: true), + properties = table.Column(type: "text", nullable: true), + inbox_message_id = table.Column(type: "uuid", nullable: true), + inbox_consumer_id = table.Column(type: "uuid", nullable: true), + outbox_id = table.Column(type: "uuid", nullable: true), + message_id = table.Column(type: "uuid", nullable: false), + content_type = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), + message_type = table.Column(type: "text", nullable: false), + body = table.Column(type: "text", nullable: false), + conversation_id = table.Column(type: "uuid", nullable: true), + correlation_id = table.Column(type: "uuid", nullable: true), + initiator_id = table.Column(type: "uuid", nullable: true), + request_id = table.Column(type: "uuid", nullable: true), + source_address = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + destination_address = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + response_address = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + fault_address = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), + expiration_time = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_outbox_message", x => x.sequence_number); + table.ForeignKey( + name: "fk_outbox_message_inbox_state_inbox_message_id_inbox_consumer_~", + columns: x => new { x.inbox_message_id, x.inbox_consumer_id }, + principalTable: "inbox_state", + principalColumns: new[] { "message_id", "consumer_id" }); + table.ForeignKey( + name: "fk_outbox_message_outbox_state_outbox_id", + column: x => x.outbox_id, + principalTable: "outbox_state", + principalColumn: "outbox_id"); + }); + + migrationBuilder.CreateTable( + name: "permission_modules", columns: table => new { id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - code = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), - parent_code = table.Column(type: "character varying(160)", maxLength: 160, nullable: true), - title = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + code = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), area = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - path = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), - icon = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), - permission_code = table.Column(type: "character varying(160)", maxLength: 160, nullable: true), + required_feature_code = table.Column(type: "character varying(120)", maxLength: 120, nullable: true), + description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), sort_order = table.Column(type: "integer", nullable: false), - is_active = table.Column(type: "boolean", nullable: false), created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") }, constraints: table => { - table.PrimaryKey("pk_backend_menus", x => x.id); - table.UniqueConstraint("ak_backend_menus_code", x => x.code); + table.PrimaryKey("pk_permission_modules", x => x.id); + table.UniqueConstraint("ak_permission_modules_code", x => x.code); table.ForeignKey( - name: "fk_backend_menus_backend_permissions_permission_code", - column: x => x.permission_code, - principalTable: "backend_permissions", + name: "fk_permission_modules_saas_features_required_feature_code", + column: x => x.required_feature_code, + principalTable: "saas_features", principalColumn: "code", - onDelete: ReferentialAction.SetNull); + onDelete: ReferentialAction.Restrict); }); migrationBuilder.CreateTable( - name: "platform_backend_role_permissions", + name: "saas_feature_limit_definitions", columns: table => new { id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - role_id = table.Column(type: "uuid", nullable: false), - permission_code = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + metric_code = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + feature_code = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + unit = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + kind = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + warning_percent = table.Column(type: "integer", nullable: false), + is_hard_limit = table.Column(type: "boolean", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") }, constraints: table => { - table.PrimaryKey("pk_platform_backend_role_permissions", x => x.id); + table.PrimaryKey("pk_saas_feature_limit_definitions", x => x.id); + table.UniqueConstraint("ak_saas_feature_limit_definitions_metric_code", x => x.metric_code); + table.CheckConstraint("ck_saas_feature_limit_warning", "warning_percent between 1 and 100"); table.ForeignKey( - name: "fk_platform_backend_role_permissions_backend_permissions_permi~", - column: x => x.permission_code, - principalTable: "backend_permissions", + name: "fk_saas_feature_limit_definitions_saas_features_feature_code", + column: x => x.feature_code, + principalTable: "saas_features", principalColumn: "code", - onDelete: ReferentialAction.Restrict); + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "saas_offering_versions", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + offering_id = table.Column(type: "uuid", nullable: false), + version = table.Column(type: "integer", nullable: false), + status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + billing_cycle = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + original_amount_cents = table.Column(type: "integer", nullable: false), + amount_cents = table.Column(type: "integer", nullable: false), + currency = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + effective_at = table.Column(type: "timestamp with time zone", nullable: true), + published_at = table.Column(type: "timestamp with time zone", nullable: true), + retired_at = table.Column(type: "timestamp with time zone", nullable: true), + metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_saas_offering_versions", x => x.id); + table.CheckConstraint("ck_saas_offering_versions_amount", "original_amount_cents >= 0 and amount_cents >= 0 and amount_cents <= original_amount_cents"); + table.CheckConstraint("ck_saas_offering_versions_version", "version > 0"); table.ForeignKey( - name: "fk_platform_backend_role_permissions_platform_backend_roles_ro~", - column: x => x.role_id, - principalTable: "platform_backend_roles", + name: "fk_saas_offering_versions_saas_offerings_offering_id", + column: x => x.offering_id, + principalTable: "saas_offerings", principalColumn: "id", onDelete: ReferentialAction.Cascade); }); @@ -379,27 +488,80 @@ namespace Tiku.Infrastructure.Persistence.Migrations }); migrationBuilder.CreateTable( - name: "platform_backend_role_menus", + name: "backend_permissions", columns: table => new { id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - role_id = table.Column(type: "uuid", nullable: false), - menu_code = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + code = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), + name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + area = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + permission_module_code = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + is_system = table.Column(type: "boolean", nullable: false), + sort_order = table.Column(type: "integer", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") }, constraints: table => { - table.PrimaryKey("pk_platform_backend_role_menus", x => x.id); + table.PrimaryKey("pk_backend_permissions", x => x.id); + table.UniqueConstraint("ak_backend_permissions_code", x => x.code); table.ForeignKey( - name: "fk_platform_backend_role_menus_backend_menus_menu_code", - column: x => x.menu_code, - principalTable: "backend_menus", + name: "fk_backend_permissions_permission_modules_permission_module_co~", + column: x => x.permission_module_code, + principalTable: "permission_modules", + principalColumn: "code", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "saas_offering_version_features", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + offering_version_id = table.Column(type: "uuid", nullable: false), + feature_code = table.Column(type: "character varying(120)", maxLength: 120, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_saas_offering_version_features", x => x.id); + table.ForeignKey( + name: "fk_saas_offering_version_features_saas_features_feature_code", + column: x => x.feature_code, + principalTable: "saas_features", principalColumn: "code", onDelete: ReferentialAction.Restrict); table.ForeignKey( - name: "fk_platform_backend_role_menus_platform_backend_roles_role_id", - column: x => x.role_id, - principalTable: "platform_backend_roles", + name: "fk_saas_offering_version_features_saas_offering_versions_offer~", + column: x => x.offering_version_id, + principalTable: "saas_offering_versions", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "saas_offering_version_limits", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + offering_version_id = table.Column(type: "uuid", nullable: false), + metric_code = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + limit_value = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_saas_offering_version_limits", x => x.id); + table.CheckConstraint("ck_saas_offering_version_limits_value", "limit_value >= 0"); + table.ForeignKey( + name: "fk_saas_offering_version_limits_saas_feature_limit_definitions~", + column: x => x.metric_code, + principalTable: "saas_feature_limit_definitions", + principalColumn: "metric_code", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_saas_offering_version_limits_saas_offering_versions_offerin~", + column: x => x.offering_version_id, + principalTable: "saas_offering_versions", principalColumn: "id", onDelete: ReferentialAction.Cascade); }); @@ -415,7 +577,7 @@ namespace Tiku.Infrastructure.Persistence.Migrations background_color = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), sort_order = table.Column(type: "integer", nullable: false), is_active = table.Column(type: "boolean", nullable: false), - tenant_id = table.Column(type: "uuid", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: false), created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") }, @@ -441,7 +603,7 @@ namespace Tiku.Infrastructure.Persistence.Migrations file_name = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), description = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), - tenant_id = table.Column(type: "uuid", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: false), created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") }, @@ -571,7 +733,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations replaced_by_session_id = table.Column(type: "uuid", nullable: true), token_hash = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), security_stamp = table.Column(type: "character varying(128)", maxLength: 128, nullable: false), - mfa_satisfied = table.Column(type: "boolean", nullable: false), provider = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), expires_at = table.Column(type: "timestamp with time zone", nullable: false), revoked_at = table.Column(type: "timestamp with time zone", nullable: true), @@ -1022,6 +1183,39 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "platform_billing_quotes", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + quote_no = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + idempotency_key = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + purpose = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + original_amount_cents = table.Column(type: "integer", nullable: false), + discount_amount_cents = table.Column(type: "integer", nullable: false), + total_amount_cents = table.Column(type: "integer", nullable: false), + currency = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + expires_at = table.Column(type: "timestamp with time zone", nullable: false), + feature_snapshot = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'[]'::jsonb"), + limit_snapshot = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + tenant_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_platform_billing_quotes", x => x.id); + table.UniqueConstraint("ak_platform_billing_quotes_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_platform_billing_quotes_amounts", "original_amount_cents >= 0 and discount_amount_cents >= 0 and total_amount_cents >= 0 and total_amount_cents = original_amount_cents - discount_amount_cents"); + table.ForeignKey( + name: "fk_platform_billing_quotes_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "point_activity_tasks", columns: table => new @@ -1361,6 +1555,27 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "tenant_auth_policies", + columns: table => new + { + tenant_id = table.Column(type: "uuid", nullable: false), + allow_external_student_self_registration = table.Column(type: "boolean", nullable: false, defaultValue: false), + allowed_student_login_methods = table.Column(type: "text[]", nullable: false, defaultValueSql: "ARRAY['password']::text[]"), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_tenant_auth_policies", x => x.tenant_id); + table.ForeignKey( + name: "fk_tenant_auth_policies_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "tenant_backend_roles", columns: table => new @@ -1541,6 +1756,73 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "tenant_feature_overrides", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + feature_code = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + mode = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + expires_at = table.Column(type: "timestamp with time zone", nullable: true), + reason = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: false), + tenant_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_tenant_feature_overrides", x => x.id); + table.UniqueConstraint("ak_tenant_feature_overrides_tenant_id_id", x => new { x.tenant_id, x.id }); + table.ForeignKey( + name: "fk_tenant_feature_overrides_saas_features_feature_code", + column: x => x.feature_code, + principalTable: "saas_features", + principalColumn: "code", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_tenant_feature_overrides_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "tenant_feature_usage", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + metric_code = table.Column(type: "character varying(120)", maxLength: 120, nullable: false), + period_start = table.Column(type: "timestamp with time zone", nullable: false), + period_end = table.Column(type: "timestamp with time zone", nullable: false), + used_value = table.Column(type: "bigint", nullable: false), + limit_value_snapshot = table.Column(type: "bigint", nullable: false), + warning_issued = table.Column(type: "boolean", nullable: false), + version = table.Column(type: "bigint", nullable: false), + tenant_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_tenant_feature_usage", x => x.id); + table.UniqueConstraint("ak_tenant_feature_usage_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_tenant_feature_usage_period", "period_end > period_start"); + table.CheckConstraint("ck_tenant_feature_usage_values", "used_value >= 0 and limit_value_snapshot >= 0"); + table.ForeignKey( + name: "fk_tenant_feature_usage_saas_feature_limit_definitions_metric_~", + column: x => x.metric_code, + principalTable: "saas_feature_limit_definitions", + principalColumn: "metric_code", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_tenant_feature_usage_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "tenant_frontend_configs", columns: table => new @@ -1577,53 +1859,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "tenant_invoices", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - created_by = table.Column(type: "uuid", nullable: true), - invoice_no = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - invoice_type = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - currency = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), - subtotal_cents = table.Column(type: "integer", nullable: false), - discount_cents = table.Column(type: "integer", nullable: false), - tax_cents = table.Column(type: "integer", nullable: false), - total_cents = table.Column(type: "integer", nullable: false), - paid_cents = table.Column(type: "integer", nullable: false), - balance_cents = table.Column(type: "integer", nullable: false), - billing_period_start = table.Column(type: "date", nullable: true), - billing_period_end = table.Column(type: "date", nullable: true), - due_date = table.Column(type: "date", nullable: true), - issued_at = table.Column(type: "timestamp with time zone", nullable: true), - paid_at = table.Column(type: "timestamp with time zone", nullable: true), - note = table.Column(type: "text", nullable: true), - metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), - tenant_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), - updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("pk_tenant_invoices", x => x.id); - table.UniqueConstraint("ak_tenant_invoices_tenant_id_id", x => new { x.tenant_id, x.id }); - table.CheckConstraint("ck_tenant_invoices_amounts", "subtotal_cents >= 0 and discount_cents >= 0 and tax_cents >= 0 and total_cents >= 0 and paid_cents >= 0 and balance_cents >= 0"); - table.CheckConstraint("ck_tenant_invoices_period", "billing_period_end is null or billing_period_start is null or billing_period_end >= billing_period_start"); - table.ForeignKey( - name: "fk_tenant_invoices_tenants_tenant_id", - column: x => x.tenant_id, - principalTable: "tenants", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_tenant_invoices_users_created_by", - column: x => x.created_by, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.SetNull); - }); - migrationBuilder.CreateTable( name: "tenant_memberships", columns: table => new @@ -1655,6 +1890,50 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "tenant_saas_subscriptions", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + base_offering_version_id = table.Column(type: "uuid", nullable: false), + scheduled_base_offering_version_id = table.Column(type: "uuid", nullable: true), + status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + starts_at = table.Column(type: "timestamp with time zone", nullable: false), + current_period_start = table.Column(type: "timestamp with time zone", nullable: false), + current_period_end = table.Column(type: "timestamp with time zone", nullable: false), + cancel_at_period_end = table.Column(type: "boolean", nullable: false), + cancelled_at = table.Column(type: "timestamp with time zone", nullable: true), + lifecycle_version = table.Column(type: "bigint", nullable: false), + metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + tenant_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_tenant_saas_subscriptions", x => x.id); + table.UniqueConstraint("ak_tenant_saas_subscriptions_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_tenant_saas_subscriptions_period", "current_period_end > current_period_start and current_period_start >= starts_at"); + table.ForeignKey( + name: "fk_tenant_saas_subscriptions_saas_offering_versions_base_offer~", + column: x => x.base_offering_version_id, + principalTable: "saas_offering_versions", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_tenant_saas_subscriptions_saas_offering_versions_scheduled_~", + column: x => x.scheduled_base_offering_version_id, + principalTable: "saas_offering_versions", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_tenant_saas_subscriptions_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "tenant_secrets", columns: table => new @@ -1757,34 +2036,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.SetNull); }); - migrationBuilder.CreateTable( - name: "tenant_subscriptions", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - plan_code = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - starts_at = table.Column(type: "timestamp with time zone", nullable: true), - expires_at = table.Column(type: "timestamp with time zone", nullable: true), - billing_cycle = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), - amount_cents = table.Column(type: "integer", nullable: false), - metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), - tenant_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), - updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("pk_tenant_subscriptions", x => x.id); - table.UniqueConstraint("ak_tenant_subscriptions_tenant_id_id", x => new { x.tenant_id, x.id }); - table.ForeignKey( - name: "fk_tenant_subscriptions_tenants_tenant_id", - column: x => x.tenant_id, - principalTable: "tenants", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - migrationBuilder.CreateTable( name: "tenant_theme_configs", columns: table => new @@ -1840,31 +2091,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.SetNull); }); - migrationBuilder.CreateTable( - name: "tenant_usage_records", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - tenant_id = table.Column(type: "uuid", nullable: false), - metric_key = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - metric_value = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), - period_start = table.Column(type: "date", nullable: false), - period_end = table.Column(type: "date", nullable: false), - metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("pk_tenant_usage_records", x => x.id); - table.UniqueConstraint("ak_tenant_usage_records_tenant_id_id", x => new { x.tenant_id, x.id }); - table.ForeignKey( - name: "fk_tenant_usage_records_tenants_tenant_id", - column: x => x.tenant_id, - principalTable: "tenants", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - migrationBuilder.CreateTable( name: "user_notifications", columns: table => new @@ -1946,6 +2172,61 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "backend_menus", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + code = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), + parent_code = table.Column(type: "character varying(160)", maxLength: 160, nullable: true), + title = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + area = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + path = table.Column(type: "character varying(1024)", maxLength: 1024, nullable: true), + icon = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + permission_code = table.Column(type: "character varying(160)", maxLength: 160, nullable: true), + sort_order = table.Column(type: "integer", nullable: false), + is_active = table.Column(type: "boolean", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_backend_menus", x => x.id); + table.UniqueConstraint("ak_backend_menus_code", x => x.code); + table.ForeignKey( + name: "fk_backend_menus_backend_permissions_permission_code", + column: x => x.permission_code, + principalTable: "backend_permissions", + principalColumn: "code", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "platform_backend_role_permissions", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + role_id = table.Column(type: "uuid", nullable: false), + permission_code = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_platform_backend_role_permissions", x => x.id); + table.ForeignKey( + name: "fk_platform_backend_role_permissions_backend_permissions_permi~", + column: x => x.permission_code, + principalTable: "backend_permissions", + principalColumn: "code", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_platform_backend_role_permissions_platform_backend_roles_ro~", + column: x => x.role_id, + principalTable: "platform_backend_roles", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "user_badges", columns: table => new @@ -2209,6 +2490,86 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.SetNull); }); + migrationBuilder.CreateTable( + name: "platform_billing_orders", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + quote_id = table.Column(type: "uuid", nullable: false), + order_no = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + idempotency_key = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + purpose = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + original_amount_cents = table.Column(type: "integer", nullable: false), + discount_amount_cents = table.Column(type: "integer", nullable: false), + total_amount_cents = table.Column(type: "integer", nullable: false), + currency = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + expires_at = table.Column(type: "timestamp with time zone", nullable: false), + paid_at = table.Column(type: "timestamp with time zone", nullable: true), + cancelled_at = table.Column(type: "timestamp with time zone", nullable: true), + snapshot = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + tenant_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_platform_billing_orders", x => x.id); + table.UniqueConstraint("ak_platform_billing_orders_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_platform_billing_orders_amounts", "original_amount_cents >= 0 and discount_amount_cents >= 0 and total_amount_cents >= 0 and total_amount_cents = original_amount_cents - discount_amount_cents"); + table.ForeignKey( + name: "fk_platform_billing_orders_platform_billing_quotes_tenant_id_q~", + columns: x => new { x.tenant_id, x.quote_id }, + principalTable: "platform_billing_quotes", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_platform_billing_orders_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "platform_billing_quote_items", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + tenant_id = table.Column(type: "uuid", nullable: false), + quote_id = table.Column(type: "uuid", nullable: false), + offering_version_id = table.Column(type: "uuid", nullable: false), + item_type = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + quantity = table.Column(type: "integer", nullable: false), + unit_amount_cents = table.Column(type: "integer", nullable: false), + amount_cents = table.Column(type: "integer", nullable: false), + snapshot = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb") + }, + constraints: table => + { + table.PrimaryKey("pk_platform_billing_quote_items", x => x.id); + table.UniqueConstraint("ak_platform_billing_quote_items_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_platform_billing_quote_items_amounts", "quantity > 0 and unit_amount_cents >= 0 and amount_cents >= 0"); + table.ForeignKey( + name: "fk_platform_billing_quote_items_platform_billing_quotes_tenant~", + columns: x => new { x.tenant_id, x.quote_id }, + principalTable: "platform_billing_quotes", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_platform_billing_quote_items_saas_offering_versions_offerin~", + column: x => x.offering_version_id, + principalTable: "saas_offering_versions", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_platform_billing_quote_items_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "point_activity_claims", columns: table => new @@ -2784,34 +3145,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.SetNull); }); - migrationBuilder.CreateTable( - name: "tenant_backend_role_menus", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - tenant_id = table.Column(type: "uuid", nullable: false), - role_id = table.Column(type: "uuid", nullable: false), - menu_code = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("pk_tenant_backend_role_menus", x => x.id); - table.UniqueConstraint("ak_tenant_backend_role_menus_tenant_id_id", x => new { x.tenant_id, x.id }); - table.ForeignKey( - name: "fk_tenant_backend_role_menus_backend_menus_menu_code", - column: x => x.menu_code, - principalTable: "backend_menus", - principalColumn: "code", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "fk_tenant_backend_role_menus_tenant_backend_roles_tenant_id_ro~", - columns: x => new { x.tenant_id, x.role_id }, - principalTable: "tenant_backend_roles", - principalColumns: new[] { "tenant_id", "id" }, - onDelete: ReferentialAction.Cascade); - }); - migrationBuilder.CreateTable( name: "tenant_backend_role_permissions", columns: table => new @@ -2869,35 +3202,90 @@ namespace Tiku.Infrastructure.Persistence.Migrations }); migrationBuilder.CreateTable( - name: "tenant_invoice_items", + name: "platform_backend_role_menus", columns: table => new { id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - tenant_id = table.Column(type: "uuid", nullable: false), - invoice_id = table.Column(type: "uuid", nullable: false), - item_type = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - item_ref_id = table.Column(type: "uuid", nullable: true), - description = table.Column(type: "character varying(500)", maxLength: 500, nullable: false), - quantity = table.Column(type: "numeric(12,2)", precision: 12, scale: 2, nullable: false), - unit_amount_cents = table.Column(type: "integer", nullable: false), - amount_cents = table.Column(type: "integer", nullable: false), - metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + role_id = table.Column(type: "uuid", nullable: false), + menu_code = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") }, constraints: table => { - table.PrimaryKey("pk_tenant_invoice_items", x => x.id); - table.UniqueConstraint("ak_tenant_invoice_items_tenant_id_id", x => new { x.tenant_id, x.id }); - table.CheckConstraint("ck_tenant_invoice_items_amounts", "unit_amount_cents >= 0 and amount_cents >= 0"); - table.CheckConstraint("ck_tenant_invoice_items_quantity", "quantity > 0"); + table.PrimaryKey("pk_platform_backend_role_menus", x => x.id); table.ForeignKey( - name: "fk_tenant_invoice_items_tenant_invoices_tenant_id_invoice_id", - columns: x => new { x.tenant_id, x.invoice_id }, - principalTable: "tenant_invoices", + name: "fk_platform_backend_role_menus_backend_menus_menu_code", + column: x => x.menu_code, + principalTable: "backend_menus", + principalColumn: "code", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_platform_backend_role_menus_platform_backend_roles_role_id", + column: x => x.role_id, + principalTable: "platform_backend_roles", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "tenant_backend_role_menus", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + tenant_id = table.Column(type: "uuid", nullable: false), + role_id = table.Column(type: "uuid", nullable: false), + menu_code = table.Column(type: "character varying(160)", maxLength: 160, nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_tenant_backend_role_menus", x => x.id); + table.UniqueConstraint("ak_tenant_backend_role_menus_tenant_id_id", x => new { x.tenant_id, x.id }); + table.ForeignKey( + name: "fk_tenant_backend_role_menus_backend_menus_menu_code", + column: x => x.menu_code, + principalTable: "backend_menus", + principalColumn: "code", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_tenant_backend_role_menus_tenant_backend_roles_tenant_id_ro~", + columns: x => new { x.tenant_id, x.role_id }, + principalTable: "tenant_backend_roles", principalColumns: new[] { "tenant_id", "id" }, onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "platform_billing_invoices", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + order_id = table.Column(type: "uuid", nullable: true), + invoice_no = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + total_amount_cents = table.Column(type: "integer", nullable: false), + currency = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + due_date = table.Column(type: "date", nullable: true), + issued_at = table.Column(type: "timestamp with time zone", nullable: true), + paid_at = table.Column(type: "timestamp with time zone", nullable: true), + billing_profile_snapshot = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + tenant_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_platform_billing_invoices", x => x.id); + table.UniqueConstraint("ak_platform_billing_invoices_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_platform_billing_invoices_amount", "total_amount_cents >= 0"); table.ForeignKey( - name: "fk_tenant_invoice_items_tenants_tenant_id", + name: "fk_platform_billing_invoices_platform_billing_orders_tenant_id~", + columns: x => new { x.tenant_id, x.order_id }, + principalTable: "platform_billing_orders", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_platform_billing_invoices_tenants_tenant_id", column: x => x.tenant_id, principalTable: "tenants", principalColumn: "id", @@ -2905,95 +3293,80 @@ namespace Tiku.Infrastructure.Persistence.Migrations }); migrationBuilder.CreateTable( - name: "tenant_invoice_payments", + name: "platform_billing_order_items", columns: table => new { id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - invoice_id = table.Column(type: "uuid", nullable: false), - received_by = table.Column(type: "uuid", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: false), + order_id = table.Column(type: "uuid", nullable: false), + offering_version_id = table.Column(type: "uuid", nullable: false), + item_type = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + quantity = table.Column(type: "integer", nullable: false), + unit_amount_cents = table.Column(type: "integer", nullable: false), + amount_cents = table.Column(type: "integer", nullable: false), + snapshot = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb") + }, + constraints: table => + { + table.PrimaryKey("pk_platform_billing_order_items", x => x.id); + table.UniqueConstraint("ak_platform_billing_order_items_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_platform_billing_order_items_amounts", "quantity > 0 and unit_amount_cents >= 0 and amount_cents >= 0"); + table.ForeignKey( + name: "fk_platform_billing_order_items_platform_billing_orders_tenant~", + columns: x => new { x.tenant_id, x.order_id }, + principalTable: "platform_billing_orders", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_platform_billing_order_items_saas_offering_versions_offerin~", + column: x => x.offering_version_id, + principalTable: "saas_offering_versions", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_platform_billing_order_items_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "platform_billing_payments", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + order_id = table.Column(type: "uuid", nullable: false), payment_no = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + idempotency_key = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), provider = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - method = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + method = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), amount_cents = table.Column(type: "integer", nullable: false), - paid_at = table.Column(type: "timestamp with time zone", nullable: true), provider_trade_no = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), - raw_payload = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + paid_at = table.Column(type: "timestamp with time zone", nullable: true), + client_payload = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), tenant_id = table.Column(type: "uuid", nullable: false), created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") }, constraints: table => { - table.PrimaryKey("pk_tenant_invoice_payments", x => x.id); - table.UniqueConstraint("ak_tenant_invoice_payments_tenant_id_id", x => new { x.tenant_id, x.id }); - table.CheckConstraint("ck_tenant_invoice_payments_amount", "amount_cents >= 0"); + table.PrimaryKey("pk_platform_billing_payments", x => x.id); + table.UniqueConstraint("ak_platform_billing_payments_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_platform_billing_payments_amount", "amount_cents >= 0"); table.ForeignKey( - name: "fk_tenant_invoice_payments_tenant_invoices_tenant_id_invoice_id", - columns: x => new { x.tenant_id, x.invoice_id }, - principalTable: "tenant_invoices", + name: "fk_platform_billing_payments_platform_billing_orders_tenant_id~", + columns: x => new { x.tenant_id, x.order_id }, + principalTable: "platform_billing_orders", principalColumns: new[] { "tenant_id", "id" }, onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_tenant_invoice_payments_tenants_tenant_id", + name: "fk_platform_billing_payments_tenants_tenant_id", column: x => x.tenant_id, principalTable: "tenants", principalColumn: "id", onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_tenant_invoice_payments_users_received_by", - column: x => x.received_by, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.SetNull); - }); - - migrationBuilder.CreateTable( - name: "tenant_invoice_reminders", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - invoice_id = table.Column(type: "uuid", nullable: false), - created_by = table.Column(type: "uuid", nullable: true), - reminder_type = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - channel = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - reminder_date = table.Column(type: "date", nullable: false), - reminder_level = table.Column(type: "integer", nullable: false), - due_date = table.Column(type: "date", nullable: true), - balance_cents_snapshot = table.Column(type: "integer", nullable: false), - message = table.Column(type: "text", nullable: true), - metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), - sent_at = table.Column(type: "timestamp with time zone", nullable: true), - acknowledged_at = table.Column(type: "timestamp with time zone", nullable: true), - tenant_id = table.Column(type: "uuid", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), - updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") - }, - constraints: table => - { - table.PrimaryKey("pk_tenant_invoice_reminders", x => x.id); - table.UniqueConstraint("ak_tenant_invoice_reminders_tenant_id_id", x => new { x.tenant_id, x.id }); - table.CheckConstraint("ck_tenant_invoice_reminders_balance", "balance_cents_snapshot >= 0"); - table.CheckConstraint("ck_tenant_invoice_reminders_level", "reminder_level between 1 and 20"); - table.ForeignKey( - name: "fk_tenant_invoice_reminders_tenant_invoices_tenant_id_invoice_~", - columns: x => new { x.tenant_id, x.invoice_id }, - principalTable: "tenant_invoices", - principalColumns: new[] { "tenant_id", "id" }, - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_tenant_invoice_reminders_tenants_tenant_id", - column: x => x.tenant_id, - principalTable: "tenants", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "fk_tenant_invoice_reminders_users_created_by", - column: x => x.created_by, - principalTable: "users", - principalColumn: "id", - onDelete: ReferentialAction.SetNull); }); migrationBuilder.CreateTable( @@ -3505,54 +3878,167 @@ namespace Tiku.Infrastructure.Persistence.Migrations }); migrationBuilder.CreateTable( - name: "platform_dunning_notification_events", + name: "platform_billing_invoice_reminders", columns: table => new { id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), - channel_id = table.Column(type: "uuid", nullable: false), - reminder_id = table.Column(type: "uuid", nullable: false), invoice_id = table.Column(type: "uuid", nullable: false), - provider = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + created_by = table.Column(type: "uuid", nullable: true), + reminder_type = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + channel = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), - attempts = table.Column(type: "integer", nullable: false), - scheduled_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), - next_attempt_at = table.Column(type: "timestamp with time zone", nullable: true), - last_attempt_at = table.Column(type: "timestamp with time zone", nullable: true), - sent_at = table.Column(type: "timestamp with time zone", nullable: true), - last_error = table.Column(type: "text", nullable: true), - last_http_code = table.Column(type: "integer", nullable: true), - last_response_summary = table.Column(type: "text", nullable: true), - request_payload = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + reminder_date = table.Column(type: "date", nullable: false), + reminder_level = table.Column(type: "integer", nullable: false), + due_date = table.Column(type: "date", nullable: true), + balance_cents_snapshot = table.Column(type: "integer", nullable: false), + message = table.Column(type: "text", nullable: true), metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + sent_at = table.Column(type: "timestamp with time zone", nullable: true), + acknowledged_at = table.Column(type: "timestamp with time zone", nullable: true), tenant_id = table.Column(type: "uuid", nullable: false), created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") }, constraints: table => { - table.PrimaryKey("pk_platform_dunning_notification_events", x => x.id); - table.UniqueConstraint("ak_platform_dunning_notification_events_tenant_id_id", x => new { x.tenant_id, x.id }); - table.CheckConstraint("ck_platform_dunning_events_attempts", "attempts >= 0"); + table.PrimaryKey("pk_platform_billing_invoice_reminders", x => x.id); + table.UniqueConstraint("ak_platform_billing_invoice_reminders_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_platform_billing_invoice_reminders_balance", "balance_cents_snapshot >= 0"); + table.CheckConstraint("ck_platform_billing_invoice_reminders_level", "reminder_level between 1 and 20"); table.ForeignKey( - name: "fk_platform_dunning_notification_events_platform_dunning_notif~", - column: x => x.channel_id, - principalTable: "platform_dunning_notification_channels", + name: "fk_platform_billing_invoice_reminders_platform_billing_invoice~", + columns: x => new { x.tenant_id, x.invoice_id }, + principalTable: "platform_billing_invoices", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_platform_billing_invoice_reminders_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", principalColumn: "id", onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_platform_dunning_notification_events_tenant_invoice_reminde~", - columns: x => new { x.tenant_id, x.reminder_id }, - principalTable: "tenant_invoice_reminders", + name: "fk_platform_billing_invoice_reminders_users_created_by", + column: x => x.created_by, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.SetNull); + }); + + migrationBuilder.CreateTable( + name: "tenant_saas_subscription_items", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + subscription_id = table.Column(type: "uuid", nullable: false), + offering_version_id = table.Column(type: "uuid", nullable: false), + source_order_item_id = table.Column(type: "uuid", nullable: true), + item_type = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + starts_at = table.Column(type: "timestamp with time zone", nullable: false), + ends_at = table.Column(type: "timestamp with time zone", nullable: false), + tenant_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_tenant_saas_subscription_items", x => x.id); + table.UniqueConstraint("ak_tenant_saas_subscription_items_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_tenant_saas_subscription_items_period", "ends_at > starts_at"); + table.ForeignKey( + name: "fk_tenant_saas_subscription_items_platform_billing_order_items~", + columns: x => new { x.tenant_id, x.source_order_item_id }, + principalTable: "platform_billing_order_items", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_tenant_saas_subscription_items_saas_offering_versions_offer~", + column: x => x.offering_version_id, + principalTable: "saas_offering_versions", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_tenant_saas_subscription_items_tenant_saas_subscriptions_te~", + columns: x => new { x.tenant_id, x.subscription_id }, + principalTable: "tenant_saas_subscriptions", principalColumns: new[] { "tenant_id", "id" }, onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_platform_dunning_notification_events_tenant_invoices_tenant~", - columns: x => new { x.tenant_id, x.invoice_id }, - principalTable: "tenant_invoices", + name: "fk_tenant_saas_subscription_items_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "platform_billing_payment_events", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + tenant_id = table.Column(type: "uuid", nullable: false), + payment_id = table.Column(type: "uuid", nullable: false), + provider = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + provider_event_id = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + event_type = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + payload = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_platform_billing_payment_events", x => x.id); + table.UniqueConstraint("ak_platform_billing_payment_events_tenant_id_id", x => new { x.tenant_id, x.id }); + table.ForeignKey( + name: "fk_platform_billing_payment_events_platform_billing_payments_t~", + columns: x => new { x.tenant_id, x.payment_id }, + principalTable: "platform_billing_payments", principalColumns: new[] { "tenant_id", "id" }, onDelete: ReferentialAction.Cascade); table.ForeignKey( - name: "fk_platform_dunning_notification_events_tenants_tenant_id", + name: "fk_platform_billing_payment_events_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "platform_billing_refunds", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + order_id = table.Column(type: "uuid", nullable: false), + payment_id = table.Column(type: "uuid", nullable: true), + refund_no = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + amount_cents = table.Column(type: "integer", nullable: false), + reason = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: false), + provider_refund_no = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + completed_at = table.Column(type: "timestamp with time zone", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_platform_billing_refunds", x => x.id); + table.UniqueConstraint("ak_platform_billing_refunds_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_platform_billing_refunds_amount", "amount_cents > 0"); + table.ForeignKey( + name: "fk_platform_billing_refunds_platform_billing_orders_tenant_id_~", + columns: x => new { x.tenant_id, x.order_id }, + principalTable: "platform_billing_orders", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_platform_billing_refunds_platform_billing_payments_tenant_i~", + columns: x => new { x.tenant_id, x.payment_id }, + principalTable: "platform_billing_payments", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "fk_platform_billing_refunds_tenants_tenant_id", column: x => x.tenant_id, principalTable: "tenants", principalColumn: "id", @@ -3835,6 +4321,61 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "platform_billing_dunning_notification_events", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + channel_id = table.Column(type: "uuid", nullable: false), + reminder_id = table.Column(type: "uuid", nullable: false), + invoice_id = table.Column(type: "uuid", nullable: false), + provider = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + attempts = table.Column(type: "integer", nullable: false), + scheduled_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + next_attempt_at = table.Column(type: "timestamp with time zone", nullable: true), + last_attempt_at = table.Column(type: "timestamp with time zone", nullable: true), + sent_at = table.Column(type: "timestamp with time zone", nullable: true), + last_error = table.Column(type: "text", nullable: true), + last_http_code = table.Column(type: "integer", nullable: true), + last_response_summary = table.Column(type: "text", nullable: true), + request_payload = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + tenant_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_platform_billing_dunning_notification_events", x => x.id); + table.UniqueConstraint("ak_platform_billing_dunning_notification_events_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_platform_billing_dunning_events_attempts", "attempts >= 0"); + table.ForeignKey( + name: "fk_platform_billing_dunning_events_channel", + column: x => x.channel_id, + principalTable: "platform_billing_dunning_notification_channels", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_platform_billing_dunning_events_invoice", + columns: x => new { x.tenant_id, x.invoice_id }, + principalTable: "platform_billing_invoices", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_platform_billing_dunning_events_reminder", + columns: x => new { x.tenant_id, x.reminder_id }, + principalTable: "platform_billing_invoice_reminders", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_platform_billing_dunning_notification_events_tenants_tenant~", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "vocabulary_words", columns: table => new @@ -5428,6 +5969,94 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.SetNull); }); + migrationBuilder.CreateTable( + name: "commerce_adjustment_vouchers", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + issue_id = table.Column(type: "uuid", nullable: true), + batch_id = table.Column(type: "uuid", nullable: true), + item_id = table.Column(type: "uuid", nullable: true), + order_id = table.Column(type: "uuid", nullable: true), + payment_id = table.Column(type: "uuid", nullable: true), + refund_request_id = table.Column(type: "uuid", nullable: true), + created_by = table.Column(type: "uuid", nullable: true), + reviewed_by = table.Column(type: "uuid", nullable: true), + voucher_no = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + direction = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + amount_cents = table.Column(type: "integer", nullable: false), + currency = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + reason = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: false), + proof_asset_key = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + reviewed_at = table.Column(type: "timestamp with time zone", nullable: true), + closed_at = table.Column(type: "timestamp with time zone", nullable: true), + metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + tenant_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_commerce_adjustment_vouchers", x => x.id); + table.UniqueConstraint("ak_commerce_adjustment_vouchers_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_commerce_adjustment_vouchers_amount", "amount_cents > 0"); + table.ForeignKey( + name: "fk_commerce_adjustment_vouchers_commerce_reconciliation_batche~", + columns: x => new { x.tenant_id, x.batch_id }, + principalTable: "commerce_reconciliation_batches", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "fk_commerce_adjustment_vouchers_commerce_reconciliation_issues~", + columns: x => new { x.tenant_id, x.issue_id }, + principalTable: "commerce_reconciliation_issues", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "fk_commerce_adjustment_vouchers_commerce_reconciliation_items_~", + columns: x => new { x.tenant_id, x.item_id }, + principalTable: "commerce_reconciliation_items", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "fk_commerce_adjustment_vouchers_commerce_refund_requests_tenan~", + columns: x => new { x.tenant_id, x.refund_request_id }, + principalTable: "commerce_refund_requests", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "fk_commerce_adjustment_vouchers_orders_tenant_id_order_id", + columns: x => new { x.tenant_id, x.order_id }, + principalTable: "orders", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "fk_commerce_adjustment_vouchers_payments_tenant_id_payment_id", + columns: x => new { x.tenant_id, x.payment_id }, + principalTable: "payments", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "fk_commerce_adjustment_vouchers_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_commerce_adjustment_vouchers_users_created_by", + column: x => x.created_by, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.SetNull); + table.ForeignKey( + name: "fk_commerce_adjustment_vouchers_users_reviewed_by", + column: x => x.reviewed_by, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.SetNull); + }); + migrationBuilder.CreateTable( name: "commerce_reconciliation_issue_events", columns: table => new @@ -5578,6 +6207,44 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "commerce_adjustment_voucher_events", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + tenant_id = table.Column(type: "uuid", nullable: false), + voucher_id = table.Column(type: "uuid", nullable: false), + from_status = table.Column(type: "character varying(32)", maxLength: 32, nullable: true), + to_status = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + actor_user_id = table.Column(type: "uuid", nullable: true), + note = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + details = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_commerce_adjustment_voucher_events", x => x.id); + table.UniqueConstraint("ak_commerce_adjustment_voucher_events_tenant_id_id", x => new { x.tenant_id, x.id }); + table.ForeignKey( + name: "fk_commerce_adjustment_voucher_events_commerce_adjustment_vouc~", + columns: x => new { x.tenant_id, x.voucher_id }, + principalTable: "commerce_adjustment_vouchers", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_commerce_adjustment_voucher_events_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_commerce_adjustment_voucher_events_users_actor_user_id", + column: x => x.actor_user_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.SetNull); + }); + migrationBuilder.CreateTable( name: "practice_access_events", columns: table => new @@ -6165,6 +6832,60 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.SetNull); }); + migrationBuilder.CreateTable( + name: "video_playback_progress", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"), + user_id = table.Column(type: "uuid", nullable: false), + video_id = table.Column(type: "uuid", nullable: false), + question_id = table.Column(type: "uuid", nullable: true), + position_seconds = table.Column(type: "integer", nullable: false), + duration_seconds = table.Column(type: "integer", nullable: true), + watched_seconds = table.Column(type: "integer", nullable: false), + is_completed = table.Column(type: "boolean", nullable: false), + completed_at = table.Column(type: "timestamp with time zone", nullable: true), + last_played_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + play_count = table.Column(type: "integer", nullable: false), + metadata = table.Column(type: "jsonb", nullable: false, defaultValueSql: "'{}'::jsonb"), + tenant_id = table.Column(type: "uuid", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("pk_video_playback_progress", x => x.id); + table.UniqueConstraint("ak_video_playback_progress_tenant_id_id", x => new { x.tenant_id, x.id }); + table.CheckConstraint("ck_video_playback_progress_duration", "duration_seconds is null or duration_seconds >= 0"); + table.CheckConstraint("ck_video_playback_progress_play_count", "play_count >= 0"); + table.CheckConstraint("ck_video_playback_progress_position", "position_seconds >= 0"); + table.CheckConstraint("ck_video_playback_progress_watched", "watched_seconds >= 0"); + table.ForeignKey( + name: "fk_video_playback_progress_questions_tenant_id_question_id", + columns: x => new { x.tenant_id, x.question_id }, + principalTable: "questions", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_video_playback_progress_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_video_playback_progress_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_video_playback_progress_video_explanations_tenant_id_video_~", + columns: x => new { x.tenant_id, x.video_id }, + principalTable: "video_explanations", + principalColumns: new[] { "tenant_id", "id" }, + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "report_status_events", columns: table => new @@ -6534,9 +7255,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations column: "permission_code"); migrationBuilder.CreateIndex( - name: "ix_backend_permissions_area_module_sort_order", + name: "ix_backend_permissions_area_permission_module_code_sort_order", table: "backend_permissions", - columns: new[] { "area", "module", "sort_order" }); + columns: new[] { "area", "permission_module_code", "sort_order" }); migrationBuilder.CreateIndex( name: "ix_backend_permissions_code", @@ -6544,6 +7265,11 @@ namespace Tiku.Infrastructure.Persistence.Migrations column: "code", unique: true); + migrationBuilder.CreateIndex( + name: "ix_backend_permissions_permission_module_code", + table: "backend_permissions", + column: "permission_module_code"); + migrationBuilder.CreateIndex( name: "ix_background_jobs_tenant_id_job_type_status_created_at", table: "background_jobs", @@ -6603,6 +7329,67 @@ namespace Tiku.Infrastructure.Persistence.Migrations columns: new[] { "tenant_id", "legacy_id" }, unique: true); + migrationBuilder.CreateIndex( + name: "ix_commerce_adjustment_voucher_events_actor_user_id", + table: "commerce_adjustment_voucher_events", + column: "actor_user_id"); + + migrationBuilder.CreateIndex( + name: "ix_commerce_adjustment_voucher_events_tenant_id_voucher_id_cre~", + table: "commerce_adjustment_voucher_events", + columns: new[] { "tenant_id", "voucher_id", "created_at" }); + + migrationBuilder.CreateIndex( + name: "ix_commerce_adjustment_vouchers_created_by", + table: "commerce_adjustment_vouchers", + column: "created_by"); + + migrationBuilder.CreateIndex( + name: "ix_commerce_adjustment_vouchers_reviewed_by", + table: "commerce_adjustment_vouchers", + column: "reviewed_by"); + + migrationBuilder.CreateIndex( + name: "ix_commerce_adjustment_vouchers_tenant_id_batch_id", + table: "commerce_adjustment_vouchers", + columns: new[] { "tenant_id", "batch_id" }); + + migrationBuilder.CreateIndex( + name: "ix_commerce_adjustment_vouchers_tenant_id_issue_id_created_at", + table: "commerce_adjustment_vouchers", + columns: new[] { "tenant_id", "issue_id", "created_at" }); + + migrationBuilder.CreateIndex( + name: "ix_commerce_adjustment_vouchers_tenant_id_item_id", + table: "commerce_adjustment_vouchers", + columns: new[] { "tenant_id", "item_id" }); + + migrationBuilder.CreateIndex( + name: "ix_commerce_adjustment_vouchers_tenant_id_order_id", + table: "commerce_adjustment_vouchers", + columns: new[] { "tenant_id", "order_id" }); + + migrationBuilder.CreateIndex( + name: "ix_commerce_adjustment_vouchers_tenant_id_payment_id", + table: "commerce_adjustment_vouchers", + columns: new[] { "tenant_id", "payment_id" }); + + migrationBuilder.CreateIndex( + name: "ix_commerce_adjustment_vouchers_tenant_id_refund_request_id", + table: "commerce_adjustment_vouchers", + columns: new[] { "tenant_id", "refund_request_id" }); + + migrationBuilder.CreateIndex( + name: "ix_commerce_adjustment_vouchers_tenant_id_status_created_at", + table: "commerce_adjustment_vouchers", + columns: new[] { "tenant_id", "status", "created_at" }); + + migrationBuilder.CreateIndex( + name: "ix_commerce_adjustment_vouchers_tenant_id_voucher_no", + table: "commerce_adjustment_vouchers", + columns: new[] { "tenant_id", "voucher_no" }, + unique: true); + migrationBuilder.CreateIndex( name: "ix_commerce_reconciliation_batches_created_by", table: "commerce_reconciliation_batches", @@ -7381,6 +8168,11 @@ namespace Tiku.Infrastructure.Persistence.Migrations columns: new[] { "tenant_id", "legacy_id" }, unique: true); + migrationBuilder.CreateIndex( + name: "ix_inbox_state_delivered", + table: "inbox_state", + column: "delivered"); + migrationBuilder.CreateIndex( name: "ix_majors_tenant_id_legacy_id", table: "majors", @@ -7461,6 +8253,33 @@ namespace Tiku.Infrastructure.Persistence.Migrations table: "orders", column: "user_id"); + migrationBuilder.CreateIndex( + name: "ix_outbox_message_enqueue_time", + table: "outbox_message", + column: "enqueue_time"); + + migrationBuilder.CreateIndex( + name: "ix_outbox_message_expiration_time", + table: "outbox_message", + column: "expiration_time"); + + migrationBuilder.CreateIndex( + name: "ix_outbox_message_inbox_message_id_inbox_consumer_id_sequence_~", + table: "outbox_message", + columns: new[] { "inbox_message_id", "inbox_consumer_id", "sequence_number" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_outbox_message_outbox_id_sequence_number", + table: "outbox_message", + columns: new[] { "outbox_id", "sequence_number" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_outbox_state_created", + table: "outbox_state", + column: "created"); + migrationBuilder.CreateIndex( name: "ix_payment_events_tenant_id_payment_id", table: "payment_events", @@ -7520,6 +8339,22 @@ namespace Tiku.Infrastructure.Persistence.Migrations table: "pb_raw_records", columns: new[] { "tenant_id", "run_id" }); + migrationBuilder.CreateIndex( + name: "ix_permission_modules_area_sort_order", + table: "permission_modules", + columns: new[] { "area", "sort_order" }); + + migrationBuilder.CreateIndex( + name: "ix_permission_modules_code", + table: "permission_modules", + column: "code", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_permission_modules_required_feature_code", + table: "permission_modules", + column: "required_feature_code"); + migrationBuilder.CreateIndex( name: "ix_platform_audit_alert_rules_code", table: "platform_audit_alert_rules", @@ -7612,67 +8447,198 @@ namespace Tiku.Infrastructure.Persistence.Migrations unique: true); migrationBuilder.CreateIndex( - name: "ix_platform_dunning_notification_channels_channel_code", - table: "platform_dunning_notification_channels", + name: "ix_platform_billing_dunning_notification_channels_channel_code", + table: "platform_billing_dunning_notification_channels", column: "channel_code", unique: true); migrationBuilder.CreateIndex( - name: "ix_platform_dunning_notification_channels_enabled_min_reminder~", - table: "platform_dunning_notification_channels", + name: "ix_platform_billing_dunning_notification_channels_enabled_min_~", + table: "platform_billing_dunning_notification_channels", columns: new[] { "enabled", "min_reminder_level", "channel_code" }); migrationBuilder.CreateIndex( - name: "ix_platform_dunning_notification_events_channel_id", - table: "platform_dunning_notification_events", + name: "ix_platform_billing_dunning_notification_events_channel_id", + table: "platform_billing_dunning_notification_events", column: "channel_id"); migrationBuilder.CreateIndex( - name: "ix_platform_dunning_notification_events_invoice_id_status_crea~", - table: "platform_dunning_notification_events", + name: "ix_platform_billing_dunning_notification_events_invoice_id_sta~", + table: "platform_billing_dunning_notification_events", columns: new[] { "invoice_id", "status", "created_at" }); migrationBuilder.CreateIndex( - name: "ix_platform_dunning_notification_events_reminder_id_status_cre~", - table: "platform_dunning_notification_events", + name: "ix_platform_billing_dunning_notification_events_reminder_id_st~", + table: "platform_billing_dunning_notification_events", columns: new[] { "reminder_id", "status", "created_at" }); migrationBuilder.CreateIndex( - name: "ix_platform_dunning_notification_events_status_next_attempt_at~", - table: "platform_dunning_notification_events", + name: "ix_platform_billing_dunning_notification_events_status_next_at~", + table: "platform_billing_dunning_notification_events", columns: new[] { "status", "next_attempt_at", "scheduled_at", "created_at" }); migrationBuilder.CreateIndex( - name: "ix_platform_dunning_notification_events_tenant_id_channel_id_r~", - table: "platform_dunning_notification_events", + name: "ix_platform_billing_dunning_notification_events_tenant_id_chan~", + table: "platform_billing_dunning_notification_events", columns: new[] { "tenant_id", "channel_id", "reminder_id" }, unique: true); migrationBuilder.CreateIndex( - name: "ix_platform_dunning_notification_events_tenant_id_invoice_id", - table: "platform_dunning_notification_events", + name: "ix_platform_billing_dunning_notification_events_tenant_id_invo~", + table: "platform_billing_dunning_notification_events", columns: new[] { "tenant_id", "invoice_id" }); migrationBuilder.CreateIndex( - name: "ix_platform_dunning_notification_events_tenant_id_reminder_id", - table: "platform_dunning_notification_events", + name: "ix_platform_billing_dunning_notification_events_tenant_id_remi~", + table: "platform_billing_dunning_notification_events", columns: new[] { "tenant_id", "reminder_id" }); migrationBuilder.CreateIndex( - name: "ix_platform_dunning_notification_events_tenant_id_status_creat~", - table: "platform_dunning_notification_events", + name: "ix_platform_billing_dunning_notification_events_tenant_id_stat~", + table: "platform_billing_dunning_notification_events", columns: new[] { "tenant_id", "status", "created_at" }); migrationBuilder.CreateIndex( - name: "ix_platform_saas_plans_code", - table: "platform_saas_plans", - column: "code", + name: "ix_platform_billing_invoice_reminders_created_by", + table: "platform_billing_invoice_reminders", + column: "created_by"); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_invoice_reminders_invoice_id_reminder_date", + table: "platform_billing_invoice_reminders", + columns: new[] { "invoice_id", "reminder_date" }); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_invoice_reminders_tenant_id_invoice_id_rem~", + table: "platform_billing_invoice_reminders", + columns: new[] { "tenant_id", "invoice_id", "reminder_type", "channel", "reminder_date" }, unique: true); migrationBuilder.CreateIndex( - name: "ix_platform_saas_plans_status_sort_order", - table: "platform_saas_plans", - columns: new[] { "status", "sort_order" }); + name: "ix_platform_billing_invoice_reminders_tenant_id_status_reminde~", + table: "platform_billing_invoice_reminders", + columns: new[] { "tenant_id", "status", "reminder_date" }); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_invoices_tenant_id_invoice_no", + table: "platform_billing_invoices", + columns: new[] { "tenant_id", "invoice_no" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_invoices_tenant_id_order_id", + table: "platform_billing_invoices", + columns: new[] { "tenant_id", "order_id" }); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_invoices_tenant_id_status_due_date", + table: "platform_billing_invoices", + columns: new[] { "tenant_id", "status", "due_date" }); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_order_items_offering_version_id", + table: "platform_billing_order_items", + column: "offering_version_id"); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_order_items_tenant_id_order_id_offering_ve~", + table: "platform_billing_order_items", + columns: new[] { "tenant_id", "order_id", "offering_version_id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_orders_tenant_id_idempotency_key", + table: "platform_billing_orders", + columns: new[] { "tenant_id", "idempotency_key" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_orders_tenant_id_order_no", + table: "platform_billing_orders", + columns: new[] { "tenant_id", "order_no" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_orders_tenant_id_quote_id", + table: "platform_billing_orders", + columns: new[] { "tenant_id", "quote_id" }); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_orders_tenant_id_status_created_at", + table: "platform_billing_orders", + columns: new[] { "tenant_id", "status", "created_at" }); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_payment_events_tenant_id_payment_id_create~", + table: "platform_billing_payment_events", + columns: new[] { "tenant_id", "payment_id", "created_at" }); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_payment_events_tenant_id_provider_provider~", + table: "platform_billing_payment_events", + columns: new[] { "tenant_id", "provider", "provider_event_id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_payments_tenant_id_idempotency_key", + table: "platform_billing_payments", + columns: new[] { "tenant_id", "idempotency_key" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_payments_tenant_id_order_id_status", + table: "platform_billing_payments", + columns: new[] { "tenant_id", "order_id", "status" }); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_payments_tenant_id_payment_no", + table: "platform_billing_payments", + columns: new[] { "tenant_id", "payment_no" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_quote_items_offering_version_id", + table: "platform_billing_quote_items", + column: "offering_version_id"); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_quote_items_tenant_id_quote_id_offering_ve~", + table: "platform_billing_quote_items", + columns: new[] { "tenant_id", "quote_id", "offering_version_id" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_quotes_tenant_id_idempotency_key", + table: "platform_billing_quotes", + columns: new[] { "tenant_id", "idempotency_key" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_quotes_tenant_id_quote_no", + table: "platform_billing_quotes", + columns: new[] { "tenant_id", "quote_no" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_quotes_tenant_id_status_expires_at", + table: "platform_billing_quotes", + columns: new[] { "tenant_id", "status", "expires_at" }); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_refunds_tenant_id_order_id_status", + table: "platform_billing_refunds", + columns: new[] { "tenant_id", "order_id", "status" }); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_refunds_tenant_id_payment_id", + table: "platform_billing_refunds", + columns: new[] { "tenant_id", "payment_id" }); + + migrationBuilder.CreateIndex( + name: "ix_platform_billing_refunds_tenant_id_refund_no", + table: "platform_billing_refunds", + columns: new[] { "tenant_id", "refund_no" }, + unique: true); migrationBuilder.CreateIndex( name: "ix_point_activity_claims_tenant_id_task_id", @@ -8289,6 +9255,72 @@ namespace Tiku.Infrastructure.Persistence.Migrations unique: true) .Annotation("Npgsql:NullsDistinct", false); + migrationBuilder.CreateIndex( + name: "ix_saas_feature_limit_definitions_feature_code_kind", + table: "saas_feature_limit_definitions", + columns: new[] { "feature_code", "kind" }); + + migrationBuilder.CreateIndex( + name: "ix_saas_feature_limit_definitions_metric_code", + table: "saas_feature_limit_definitions", + column: "metric_code", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_saas_features_code", + table: "saas_features", + column: "code", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_saas_features_status_category_sort_order", + table: "saas_features", + columns: new[] { "status", "category", "sort_order" }); + + migrationBuilder.CreateIndex( + name: "ix_saas_offering_version_features_feature_code", + table: "saas_offering_version_features", + column: "feature_code"); + + migrationBuilder.CreateIndex( + name: "ix_saas_offering_version_features_offering_version_id_feature_~", + table: "saas_offering_version_features", + columns: new[] { "offering_version_id", "feature_code" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_saas_offering_version_limits_metric_code", + table: "saas_offering_version_limits", + column: "metric_code"); + + migrationBuilder.CreateIndex( + name: "ix_saas_offering_version_limits_offering_version_id_metric_code", + table: "saas_offering_version_limits", + columns: new[] { "offering_version_id", "metric_code" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_saas_offering_versions_offering_id_status_effective_at", + table: "saas_offering_versions", + columns: new[] { "offering_id", "status", "effective_at" }); + + migrationBuilder.CreateIndex( + name: "ix_saas_offering_versions_offering_id_version", + table: "saas_offering_versions", + columns: new[] { "offering_id", "version" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_saas_offerings_code", + table: "saas_offerings", + column: "code", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_saas_offerings_type_status_sort_order", + table: "saas_offerings", + columns: new[] { "type", "status", "sort_order" }); + migrationBuilder.CreateIndex( name: "ix_schools_tenant_id_legacy_id", table: "schools", @@ -8626,87 +9658,34 @@ namespace Tiku.Infrastructure.Persistence.Migrations table: "tenant_external_providers", columns: new[] { "tenant_id", "capability", "status", "priority" }); + migrationBuilder.CreateIndex( + name: "ix_tenant_feature_overrides_feature_code", + table: "tenant_feature_overrides", + column: "feature_code"); + + migrationBuilder.CreateIndex( + name: "ix_tenant_feature_overrides_tenant_id_feature_code", + table: "tenant_feature_overrides", + columns: new[] { "tenant_id", "feature_code" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_tenant_feature_usage_metric_code", + table: "tenant_feature_usage", + column: "metric_code"); + + migrationBuilder.CreateIndex( + name: "ix_tenant_feature_usage_tenant_id_metric_code_period_start_per~", + table: "tenant_feature_usage", + columns: new[] { "tenant_id", "metric_code", "period_start", "period_end" }, + unique: true); + migrationBuilder.CreateIndex( name: "ix_tenant_frontend_configs_tenant_id", table: "tenant_frontend_configs", column: "tenant_id", unique: true); - migrationBuilder.CreateIndex( - name: "ix_tenant_invoice_items_invoice_id", - table: "tenant_invoice_items", - column: "invoice_id"); - - migrationBuilder.CreateIndex( - name: "ix_tenant_invoice_items_tenant_id_invoice_id", - table: "tenant_invoice_items", - columns: new[] { "tenant_id", "invoice_id" }); - - migrationBuilder.CreateIndex( - name: "ix_tenant_invoice_payments_invoice_id_status", - table: "tenant_invoice_payments", - columns: new[] { "invoice_id", "status" }); - - migrationBuilder.CreateIndex( - name: "ix_tenant_invoice_payments_received_by", - table: "tenant_invoice_payments", - column: "received_by"); - - migrationBuilder.CreateIndex( - name: "ix_tenant_invoice_payments_tenant_id_invoice_id", - table: "tenant_invoice_payments", - columns: new[] { "tenant_id", "invoice_id" }); - - migrationBuilder.CreateIndex( - name: "ix_tenant_invoice_payments_tenant_id_payment_no", - table: "tenant_invoice_payments", - columns: new[] { "tenant_id", "payment_no" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_tenant_invoice_reminders_created_by", - table: "tenant_invoice_reminders", - column: "created_by"); - - migrationBuilder.CreateIndex( - name: "ix_tenant_invoice_reminders_invoice_id_reminder_date", - table: "tenant_invoice_reminders", - columns: new[] { "invoice_id", "reminder_date" }); - - migrationBuilder.CreateIndex( - name: "ix_tenant_invoice_reminders_tenant_id_invoice_id_reminder_type~", - table: "tenant_invoice_reminders", - columns: new[] { "tenant_id", "invoice_id", "reminder_type", "channel", "reminder_date" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_tenant_invoice_reminders_tenant_id_status_reminder_date", - table: "tenant_invoice_reminders", - columns: new[] { "tenant_id", "status", "reminder_date" }); - - migrationBuilder.CreateIndex( - name: "ix_tenant_invoices_created_by", - table: "tenant_invoices", - column: "created_by"); - - migrationBuilder.CreateIndex( - name: "ix_tenant_invoices_tenant_id_billing_period_start_billing_peri~", - table: "tenant_invoices", - columns: new[] { "tenant_id", "billing_period_start", "billing_period_end" }, - unique: true, - filter: "invoice_type = 'usage_overage' and status <> 'void' and metadata->>'source' = 'usage_overage_auto'"); - - migrationBuilder.CreateIndex( - name: "ix_tenant_invoices_tenant_id_invoice_no", - table: "tenant_invoices", - columns: new[] { "tenant_id", "invoice_no" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "ix_tenant_invoices_tenant_id_status_due_date", - table: "tenant_invoices", - columns: new[] { "tenant_id", "status", "due_date" }); - migrationBuilder.CreateIndex( name: "ix_tenant_memberships_tenant_id_user_id_role", table: "tenant_memberships", @@ -8749,6 +9728,57 @@ namespace Tiku.Infrastructure.Persistence.Migrations table: "tenant_question_references", columns: new[] { "question_owner_tenant_id", "question_id" }); + migrationBuilder.CreateIndex( + name: "ix_tenant_saas_subscription_items_offering_version_id", + table: "tenant_saas_subscription_items", + column: "offering_version_id"); + + migrationBuilder.CreateIndex( + name: "ix_tenant_saas_subscription_items_tenant_id_source_order_item_~", + table: "tenant_saas_subscription_items", + columns: new[] { "tenant_id", "source_order_item_id" }); + + migrationBuilder.CreateIndex( + name: "ix_tenant_saas_subscription_items_tenant_id_subscription_id_of~", + table: "tenant_saas_subscription_items", + columns: new[] { "tenant_id", "subscription_id", "offering_version_id", "status" }); + + migrationBuilder.CreateIndex( + name: "ux_tenant_saas_subscription_items_current_base", + table: "tenant_saas_subscription_items", + columns: new[] { "tenant_id", "subscription_id", "item_type" }, + unique: true, + filter: "item_type = 'base_plan' and status = 'active'"); + + migrationBuilder.CreateIndex( + name: "ux_tenant_saas_subscription_items_scheduled_base", + table: "tenant_saas_subscription_items", + columns: new[] { "tenant_id", "subscription_id", "status" }, + unique: true, + filter: "item_type = 'base_plan' and status = 'scheduled'"); + + migrationBuilder.CreateIndex( + name: "ix_tenant_saas_subscriptions_base_offering_version_id", + table: "tenant_saas_subscriptions", + column: "base_offering_version_id"); + + migrationBuilder.CreateIndex( + name: "ix_tenant_saas_subscriptions_scheduled_base_offering_version_id", + table: "tenant_saas_subscriptions", + column: "scheduled_base_offering_version_id"); + + migrationBuilder.CreateIndex( + name: "ix_tenant_saas_subscriptions_tenant_id", + table: "tenant_saas_subscriptions", + column: "tenant_id", + unique: true, + filter: "status in ('trial', 'active', 'past_due', 'suspended')"); + + migrationBuilder.CreateIndex( + name: "ix_tenant_saas_subscriptions_tenant_id_status_current_period_e~", + table: "tenant_saas_subscriptions", + columns: new[] { "tenant_id", "status", "current_period_end" }); + migrationBuilder.CreateIndex( name: "ix_tenant_secrets_tenant_id_purpose_provider_secret_key", table: "tenant_secrets", @@ -8824,11 +9854,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations table: "tenant_student_notes", column: "updated_by"); - migrationBuilder.CreateIndex( - name: "ix_tenant_subscriptions_tenant_id_status_expires_at", - table: "tenant_subscriptions", - columns: new[] { "tenant_id", "status", "expires_at" }); - migrationBuilder.CreateIndex( name: "ix_tenant_theme_configs_active_template_code", table: "tenant_theme_configs", @@ -8866,11 +9891,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations table: "tenant_theme_templates", columns: new[] { "status", "sort_order", "code" }); - migrationBuilder.CreateIndex( - name: "ix_tenant_usage_records_tenant_id_metric_key_period_start_peri~", - table: "tenant_usage_records", - columns: new[] { "tenant_id", "metric_key", "period_start", "period_end" }); - migrationBuilder.CreateIndex( name: "ix_tenants_legacy_id", table: "tenants", @@ -9063,6 +10083,40 @@ namespace Tiku.Infrastructure.Persistence.Migrations table: "video_explanations", columns: new[] { "tenant_id", "subject_id", "is_active", "sort_order" }); + migrationBuilder.CreateIndex( + name: "ix_video_playback_progress_tenant_id_question_id", + table: "video_playback_progress", + columns: new[] { "tenant_id", "question_id" }); + + migrationBuilder.CreateIndex( + name: "ix_video_playback_progress_tenant_id_user_id_last_played_at", + table: "video_playback_progress", + columns: new[] { "tenant_id", "user_id", "last_played_at" }); + + migrationBuilder.CreateIndex( + name: "ix_video_playback_progress_tenant_id_user_id_video_id", + table: "video_playback_progress", + columns: new[] { "tenant_id", "user_id", "video_id" }, + unique: true, + filter: "question_id is null"); + + migrationBuilder.CreateIndex( + name: "ix_video_playback_progress_tenant_id_user_id_video_id_question~", + table: "video_playback_progress", + columns: new[] { "tenant_id", "user_id", "video_id", "question_id" }, + unique: true, + filter: "question_id is not null"); + + migrationBuilder.CreateIndex( + name: "ix_video_playback_progress_tenant_id_video_id", + table: "video_playback_progress", + columns: new[] { "tenant_id", "video_id" }); + + migrationBuilder.CreateIndex( + name: "ix_video_playback_progress_user_id", + table: "video_playback_progress", + column: "user_id"); + migrationBuilder.CreateIndex( name: "ix_vocabulary_units_tenant_id_content_node_id", table: "vocabulary_units", @@ -9201,11 +10255,13 @@ namespace Tiku.Infrastructure.Persistence.Migrations onDelete: ReferentialAction.SetNull); migrationBuilder.EnsureTenantIsolationGuards(); + migrationBuilder.EnsureSaasCatalogGuards(); } /// protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropSaasCatalogGuards(); migrationBuilder.DropTenantIsolationGuards(); migrationBuilder.DropForeignKey( @@ -9442,6 +10498,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations migrationBuilder.DropTable( name: "banners"); + migrationBuilder.DropTable( + name: "commerce_adjustment_voucher_events"); + migrationBuilder.DropTable( name: "commerce_reconciliation_issue_events"); @@ -9502,6 +10561,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations migrationBuilder.DropTable( name: "order_items"); + migrationBuilder.DropTable( + name: "outbox_message"); + migrationBuilder.DropTable( name: "payment_events"); @@ -9524,10 +10586,16 @@ namespace Tiku.Infrastructure.Persistence.Migrations name: "platform_backend_user_roles"); migrationBuilder.DropTable( - name: "platform_dunning_notification_events"); + name: "platform_billing_dunning_notification_events"); migrationBuilder.DropTable( - name: "platform_saas_plans"); + name: "platform_billing_payment_events"); + + migrationBuilder.DropTable( + name: "platform_billing_quote_items"); + + migrationBuilder.DropTable( + name: "platform_billing_refunds"); migrationBuilder.DropTable( name: "point_activity_claims"); @@ -9577,6 +10645,12 @@ namespace Tiku.Infrastructure.Persistence.Migrations migrationBuilder.DropTable( name: "revenue_daily_stats"); + migrationBuilder.DropTable( + name: "saas_offering_version_features"); + + migrationBuilder.DropTable( + name: "saas_offering_version_limits"); + migrationBuilder.DropTable( name: "scoreline_fields"); @@ -9595,6 +10669,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations migrationBuilder.DropTable( name: "subject_shares"); + migrationBuilder.DropTable( + name: "tenant_auth_policies"); + migrationBuilder.DropTable( name: "tenant_backend_role_menus"); @@ -9625,21 +10702,24 @@ namespace Tiku.Infrastructure.Persistence.Migrations migrationBuilder.DropTable( name: "tenant_external_providers"); + migrationBuilder.DropTable( + name: "tenant_feature_overrides"); + + migrationBuilder.DropTable( + name: "tenant_feature_usage"); + migrationBuilder.DropTable( name: "tenant_frontend_configs"); - migrationBuilder.DropTable( - name: "tenant_invoice_items"); - - migrationBuilder.DropTable( - name: "tenant_invoice_payments"); - migrationBuilder.DropTable( name: "tenant_memberships"); migrationBuilder.DropTable( name: "tenant_question_bank_preferences"); + migrationBuilder.DropTable( + name: "tenant_saas_subscription_items"); + migrationBuilder.DropTable( name: "tenant_secrets"); @@ -9652,15 +10732,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations migrationBuilder.DropTable( name: "tenant_student_notes"); - migrationBuilder.DropTable( - name: "tenant_subscriptions"); - migrationBuilder.DropTable( name: "tenant_theme_configs"); - migrationBuilder.DropTable( - name: "tenant_usage_records"); - migrationBuilder.DropTable( name: "user_badges"); @@ -9688,6 +10762,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations migrationBuilder.DropTable( name: "user_word_progress"); + migrationBuilder.DropTable( + name: "video_playback_progress"); + migrationBuilder.DropTable( name: "wrong_questions"); @@ -9701,7 +10778,7 @@ namespace Tiku.Infrastructure.Persistence.Migrations name: "practice_session_questions"); migrationBuilder.DropTable( - name: "commerce_reconciliation_issues"); + name: "commerce_adjustment_vouchers"); migrationBuilder.DropTable( name: "commission_settlements"); @@ -9715,6 +10792,12 @@ namespace Tiku.Infrastructure.Persistence.Migrations migrationBuilder.DropTable( name: "handbook_chapters"); + migrationBuilder.DropTable( + name: "inbox_state"); + + migrationBuilder.DropTable( + name: "outbox_state"); + migrationBuilder.DropTable( name: "pb_import_runs"); @@ -9728,10 +10811,13 @@ namespace Tiku.Infrastructure.Persistence.Migrations name: "platform_backend_roles"); migrationBuilder.DropTable( - name: "platform_dunning_notification_channels"); + name: "platform_billing_dunning_notification_channels"); migrationBuilder.DropTable( - name: "tenant_invoice_reminders"); + name: "platform_billing_invoice_reminders"); + + migrationBuilder.DropTable( + name: "platform_billing_payments"); migrationBuilder.DropTable( name: "point_activity_tasks"); @@ -9745,9 +10831,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations migrationBuilder.DropTable( name: "taxonomy_nodes"); - migrationBuilder.DropTable( - name: "video_explanations"); - migrationBuilder.DropTable( name: "reports"); @@ -9757,6 +10840,15 @@ namespace Tiku.Infrastructure.Persistence.Migrations migrationBuilder.DropTable( name: "tenant_backend_roles"); + migrationBuilder.DropTable( + name: "saas_feature_limit_definitions"); + + migrationBuilder.DropTable( + name: "platform_billing_order_items"); + + migrationBuilder.DropTable( + name: "tenant_saas_subscriptions"); + migrationBuilder.DropTable( name: "tenant_classes"); @@ -9769,6 +10861,9 @@ namespace Tiku.Infrastructure.Persistence.Migrations migrationBuilder.DropTable( name: "vocabulary_words"); + migrationBuilder.DropTable( + name: "video_explanations"); + migrationBuilder.DropTable( name: "coupons"); @@ -9776,7 +10871,7 @@ namespace Tiku.Infrastructure.Persistence.Migrations name: "tenant_question_references"); migrationBuilder.DropTable( - name: "commerce_reconciliation_items"); + name: "commerce_reconciliation_issues"); migrationBuilder.DropTable( name: "content_import_jobs"); @@ -9785,7 +10880,7 @@ namespace Tiku.Infrastructure.Persistence.Migrations name: "handbook_subjects"); migrationBuilder.DropTable( - name: "tenant_invoices"); + name: "platform_billing_invoices"); migrationBuilder.DropTable( name: "practice_sessions"); @@ -9793,9 +10888,27 @@ namespace Tiku.Infrastructure.Persistence.Migrations migrationBuilder.DropTable( name: "backend_permissions"); + migrationBuilder.DropTable( + name: "saas_offering_versions"); + migrationBuilder.DropTable( name: "vocabulary_units"); + migrationBuilder.DropTable( + name: "commerce_reconciliation_items"); + + migrationBuilder.DropTable( + name: "platform_billing_orders"); + + migrationBuilder.DropTable( + name: "practice_blueprints"); + + migrationBuilder.DropTable( + name: "permission_modules"); + + migrationBuilder.DropTable( + name: "saas_offerings"); + migrationBuilder.DropTable( name: "commerce_reconciliation_batches"); @@ -9803,7 +10916,10 @@ namespace Tiku.Infrastructure.Persistence.Migrations name: "commerce_refund_requests"); migrationBuilder.DropTable( - name: "practice_blueprints"); + name: "platform_billing_quotes"); + + migrationBuilder.DropTable( + name: "saas_features"); migrationBuilder.DropTable( name: "payments"); diff --git a/Tiku.Infrastructure/Persistence/Migrations/MigrationBuilderTenantGuardExtensions.cs b/Tiku.Infrastructure/Persistence/Migrations/MigrationBuilderTenantGuardExtensions.cs index fff037a..dfe4147 100644 --- a/Tiku.Infrastructure/Persistence/Migrations/MigrationBuilderTenantGuardExtensions.cs +++ b/Tiku.Infrastructure/Persistence/Migrations/MigrationBuilderTenantGuardExtensions.cs @@ -15,4 +15,17 @@ internal static class MigrationBuilderTenantGuardExtensions { migrationBuilder.Sql(PostgreSqlTenantConstraintSql.DropTenantGuards); } + + public static void EnsureSaasCatalogGuards(this MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(PostgreSqlSaasCatalogConstraintSql.DropOfferingVersionImmutabilityGuards); + migrationBuilder.Sql(PostgreSqlSaasCatalogConstraintSql.CreateOfferingVersionImmutabilityGuards); + migrationBuilder.Sql(PostgreSqlSaasCatalogConstraintSql.CreateOfferingVersionChildrenImmutabilityGuards); + migrationBuilder.Sql(PostgreSqlSaasCatalogConstraintSql.CreateSubscriptionOfferingTypeGuards); + } + + public static void DropSaasCatalogGuards(this MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(PostgreSqlSaasCatalogConstraintSql.DropOfferingVersionImmutabilityGuards); + } } diff --git a/Tiku.Infrastructure/Persistence/Migrations/TikuDbContextModelSnapshot.cs b/Tiku.Infrastructure/Persistence/Migrations/TikuDbContextModelSnapshot.cs index 8a1f2e1..20275a2 100644 --- a/Tiku.Infrastructure/Persistence/Migrations/TikuDbContextModelSnapshot.cs +++ b/Tiku.Infrastructure/Persistence/Migrations/TikuDbContextModelSnapshot.cs @@ -3934,132 +3934,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations }); }); - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("BillingCycle") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("billing_cycle"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("expires_at"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("PlanCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("plan_code"); - - b.Property("StartsAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("starts_at"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_subscriptions"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_subscriptions_tenant_id_id"); - - b.HasIndex("TenantId", "Status", "ExpiresAt") - .HasDatabaseName("ix_tenant_subscriptions_tenant_id_status_expires_at"); - - b.ToTable("tenant_subscriptions", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("MetricKey") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("metric_key"); - - b.Property("MetricValue") - .HasPrecision(18, 4) - .HasColumnType("numeric(18,4)") - .HasColumnName("metric_value"); - - b.Property("PeriodEnd") - .HasColumnType("date") - .HasColumnName("period_end"); - - b.Property("PeriodStart") - .HasColumnType("date") - .HasColumnName("period_start"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.HasKey("Id") - .HasName("pk_tenant_usage_records"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_usage_records_tenant_id_id"); - - b.HasIndex("TenantId", "MetricKey", "PeriodStart", "PeriodEnd") - .HasDatabaseName("ix_tenant_usage_records_tenant_id_metric_key_period_start_peri~"); - - b.ToTable("tenant_usage_records", (string)null); - }); - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => { b.Property("Id") @@ -10694,18 +10568,18 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnType("boolean") .HasColumnName("is_system"); - b.Property("Module") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("module"); - b.Property("Name") .IsRequired() .HasMaxLength(200) .HasColumnType("character varying(200)") .HasColumnName("name"); + b.Property("PermissionModuleCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("permission_module_code"); + b.Property("SortOrder") .HasColumnType("integer") .HasColumnName("sort_order"); @@ -10726,8 +10600,11 @@ namespace Tiku.Infrastructure.Persistence.Migrations .IsUnique() .HasDatabaseName("ix_backend_permissions_code"); - b.HasIndex("Area", "Module", "SortOrder") - .HasDatabaseName("ix_backend_permissions_area_module_sort_order"); + b.HasIndex("PermissionModuleCode") + .HasDatabaseName("ix_backend_permissions_permission_module_code"); + + b.HasIndex("Area", "PermissionModuleCode", "SortOrder") + .HasDatabaseName("ix_backend_permissions_area_permission_module_code_sort_order"); b.ToTable("backend_permissions", (string)null); }); @@ -11973,7 +11850,7 @@ namespace Tiku.Infrastructure.Persistence.Migrations b.ToTable("user_notifications", (string)null); }); - modelBuilder.Entity("Tiku.Domain.Platform.PlanModuleEntitlement", b => + modelBuilder.Entity("Tiku.Domain.Platform.PermissionModule", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -11981,33 +11858,67 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnName("id") .HasDefaultValueSql("gen_random_uuid()"); - b.Property("Enabled") - .HasColumnType("boolean") - .HasColumnName("enabled"); - - b.Property("ModuleCode") + b.Property("Area") .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("module_code"); + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("area"); - b.Property("PlanCode") + b.Property("Code") .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("plan_code"); + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("code"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("description"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("RequiredFeatureCode") + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("required_feature_code"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasColumnName("sort_order"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); b.HasKey("Id") - .HasName("pk_plan_module_entitlements"); + .HasName("pk_permission_modules"); - b.HasIndex("ModuleCode") - .HasDatabaseName("ix_plan_module_entitlements_module_code"); + b.HasAlternateKey("Code") + .HasName("ak_permission_modules_code"); - b.HasIndex("PlanCode", "ModuleCode") + b.HasIndex("Code") .IsUnique() - .HasDatabaseName("ix_plan_module_entitlements_plan_code_module_code"); + .HasDatabaseName("ix_permission_modules_code"); - b.ToTable("plan_module_entitlements", (string)null); + b.HasIndex("RequiredFeatureCode") + .HasDatabaseName("ix_permission_modules_required_feature_code"); + + b.HasIndex("Area", "SortOrder") + .HasDatabaseName("ix_permission_modules_area_sort_order"); + + b.ToTable("permission_modules", (string)null); }); modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => @@ -12235,7 +12146,7 @@ namespace Tiku.Infrastructure.Persistence.Migrations b.ToTable("platform_audit_alert_rules", (string)null); }); - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationChannel", b => + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingDunningNotificationChannel", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -12328,24 +12239,24 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnName("webhook_url"); b.HasKey("Id") - .HasName("pk_platform_dunning_notification_channels"); + .HasName("pk_platform_billing_dunning_notification_channels"); b.HasIndex("ChannelCode") .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_channels_channel_code"); + .HasDatabaseName("ix_platform_billing_dunning_notification_channels_channel_code"); b.HasIndex("Enabled", "MinReminderLevel", "ChannelCode") - .HasDatabaseName("ix_platform_dunning_notification_channels_enabled_min_reminder~"); + .HasDatabaseName("ix_platform_billing_dunning_notification_channels_enabled_min_~"); - b.ToTable("platform_dunning_notification_channels", null, t => + b.ToTable("platform_billing_dunning_notification_channels", null, t => { - t.HasCheckConstraint("ck_platform_dunning_channels_level", "min_reminder_level between 1 and 20"); + t.HasCheckConstraint("ck_platform_billing_dunning_channels_level", "min_reminder_level between 1 and 20"); - t.HasCheckConstraint("ck_platform_dunning_channels_timeout", "timeout_seconds between 1 and 60"); + t.HasCheckConstraint("ck_platform_billing_dunning_channels_timeout", "timeout_seconds between 1 and 60"); }); }); - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingDunningNotificationEvent", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -12440,43 +12351,43 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasDefaultValueSql("now()"); b.HasKey("Id") - .HasName("pk_platform_dunning_notification_events"); + .HasName("pk_platform_billing_dunning_notification_events"); b.HasAlternateKey("TenantId", "Id") - .HasName("ak_platform_dunning_notification_events_tenant_id_id"); + .HasName("ak_platform_billing_dunning_notification_events_tenant_id_id"); b.HasIndex("ChannelId") - .HasDatabaseName("ix_platform_dunning_notification_events_channel_id"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_channel_id"); b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_invoice_id"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_tenant_id_invo~"); b.HasIndex("TenantId", "ReminderId") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_reminder_id"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_tenant_id_remi~"); b.HasIndex("InvoiceId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_invoice_id_status_crea~"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_invoice_id_sta~"); b.HasIndex("ReminderId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_reminder_id_status_cre~"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_reminder_id_st~"); b.HasIndex("TenantId", "ChannelId", "ReminderId") .IsUnique() - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_channel_id_r~"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_tenant_id_chan~"); b.HasIndex("TenantId", "Status", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_tenant_id_status_creat~"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_tenant_id_stat~"); b.HasIndex("Status", "NextAttemptAt", "ScheduledAt", "CreatedAt") - .HasDatabaseName("ix_platform_dunning_notification_events_status_next_attempt_at~"); + .HasDatabaseName("ix_platform_billing_dunning_notification_events_status_next_at~"); - b.ToTable("platform_dunning_notification_events", null, t => + b.ToTable("platform_billing_dunning_notification_events", null, t => { - t.HasCheckConstraint("ck_platform_dunning_events_attempts", "attempts >= 0"); + t.HasCheckConstraint("ck_platform_billing_dunning_events_attempts", "attempts >= 0"); }); }); - modelBuilder.Entity("Tiku.Domain.Platform.PlatformSaasPlan", b => + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingInvoice", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -12484,21 +12395,11 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnName("id") .HasDefaultValueSql("gen_random_uuid()"); - b.Property("BaseAmountCents") - .HasColumnType("integer") - .HasColumnName("base_amount_cents"); - - b.Property("BillingCycle") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("billing_cycle"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); + b.Property("BillingProfileSnapshot") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("billing_profile_snapshot") + .HasDefaultValueSql("'{}'::jsonb"); b.Property("CreatedAt") .ValueGeneratedOnAdd() @@ -12512,248 +12413,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnType("character varying(10)") .HasColumnName("currency"); - b.Property("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property("FeatureFlags") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("feature_flags") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("IncludedQuotas") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("included_quotas") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("OveragePrices") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("overage_prices") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_platform_saas_plans"); - - b.HasAlternateKey("Code") - .HasName("ak_platform_saas_plans_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_platform_saas_plans_code"); - - b.HasIndex("Status", "SortOrder") - .HasDatabaseName("ix_platform_saas_plans_status_sort_order"); - - b.ToTable("platform_saas_plans", null, t => - { - t.HasCheckConstraint("ck_platform_saas_plans_amount", "base_amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.ProductModule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("Code") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("code"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("character varying(1000)") - .HasColumnName("description"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("name"); - - b.Property("SortOrder") - .HasColumnType("integer") - .HasColumnName("sort_order"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_product_modules"); - - b.HasAlternateKey("Code") - .HasName("ak_product_modules_code"); - - b.HasIndex("Code") - .IsUnique() - .HasDatabaseName("ix_product_modules_code"); - - b.ToTable("product_modules", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => - { - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("BankAccountMasked") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("bank_account_masked"); - - b.Property("BankName") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("bank_name"); - - b.Property("BillingAddress") - .HasColumnType("text") - .HasColumnName("billing_address"); - - b.Property("BillingName") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("billing_name"); - - b.Property("ContactEmail") - .HasMaxLength(300) - .HasColumnType("citext") - .HasColumnName("contact_email"); - - b.Property("ContactName") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("contact_name"); - - b.Property("ContactPhone") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("contact_phone"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceTitle") - .HasMaxLength(300) - .HasColumnType("character varying(300)") - .HasColumnName("invoice_title"); - - b.Property("InvoiceType") - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("TaxId") - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("tax_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("TenantId") - .HasName("pk_tenant_billing_profiles"); - - b.ToTable("tenant_billing_profiles", (string)null); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("BalanceCents") - .HasColumnType("integer") - .HasColumnName("balance_cents"); - - b.Property("BillingPeriodEnd") - .HasColumnType("date") - .HasColumnName("billing_period_end"); - - b.Property("BillingPeriodStart") - .HasColumnType("date") - .HasColumnName("billing_period_start"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property("Currency") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("character varying(10)") - .HasColumnName("currency"); - - b.Property("DiscountCents") - .HasColumnType("integer") - .HasColumnName("discount_cents"); - b.Property("DueDate") .HasColumnType("date") .HasColumnName("due_date"); @@ -12764,55 +12423,31 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnType("character varying(100)") .HasColumnName("invoice_no"); - b.Property("InvoiceType") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("invoice_type"); - b.Property("IssuedAt") .HasColumnType("timestamp with time zone") .HasColumnName("issued_at"); - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Note") - .HasColumnType("text") - .HasColumnName("note"); + b.Property("OrderId") + .HasColumnType("uuid") + .HasColumnName("order_id"); b.Property("PaidAt") .HasColumnType("timestamp with time zone") .HasColumnName("paid_at"); - b.Property("PaidCents") - .HasColumnType("integer") - .HasColumnName("paid_cents"); - b.Property("Status") .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("status"); - b.Property("SubtotalCents") - .HasColumnType("integer") - .HasColumnName("subtotal_cents"); - - b.Property("TaxCents") - .HasColumnType("integer") - .HasColumnName("tax_cents"); - b.Property("TenantId") .HasColumnType("uuid") .HasColumnName("tenant_id"); - b.Property("TotalCents") + b.Property("TotalAmountCents") .HasColumnType("integer") - .HasColumnName("total_cents"); + .HasColumnName("total_amount_cents"); b.Property("UpdatedAt") .ValueGeneratedOnAdd() @@ -12821,211 +12456,28 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasDefaultValueSql("now()"); b.HasKey("Id") - .HasName("pk_tenant_invoices"); + .HasName("pk_platform_billing_invoices"); b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoices_tenant_id_id"); - - b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoices_created_by"); + .HasName("ak_platform_billing_invoices_tenant_id_id"); b.HasIndex("TenantId", "InvoiceNo") .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_invoice_no"); + .HasDatabaseName("ix_platform_billing_invoices_tenant_id_invoice_no"); - b.HasIndex("TenantId", "BillingPeriodStart", "BillingPeriodEnd") - .IsUnique() - .HasDatabaseName("ix_tenant_invoices_tenant_id_billing_period_start_billing_peri~") - .HasFilter("invoice_type = 'usage_overage' and status <> 'void' and metadata->>'source' = 'usage_overage_auto'"); + b.HasIndex("TenantId", "OrderId") + .HasDatabaseName("ix_platform_billing_invoices_tenant_id_order_id"); b.HasIndex("TenantId", "Status", "DueDate") - .HasDatabaseName("ix_tenant_invoices_tenant_id_status_due_date"); + .HasDatabaseName("ix_platform_billing_invoices_tenant_id_status_due_date"); - b.ToTable("tenant_invoices", null, t => + b.ToTable("platform_billing_invoices", null, t => { - t.HasCheckConstraint("ck_tenant_invoices_amounts", "subtotal_cents >= 0 and discount_cents >= 0 and tax_cents >= 0 and total_cents >= 0 and paid_cents >= 0 and balance_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoices_period", "billing_period_end is null or billing_period_start is null or billing_period_end >= billing_period_start"); + t.HasCheckConstraint("ck_platform_billing_invoices_amount", "total_amount_cents >= 0"); }); }); - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)") - .HasColumnName("description"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("ItemRefId") - .HasColumnType("uuid") - .HasColumnName("item_ref_id"); - - b.Property("ItemType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("item_type"); - - b.Property("Metadata") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("metadata") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("Quantity") - .HasPrecision(12, 2) - .HasColumnType("numeric(12,2)") - .HasColumnName("quantity"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UnitAmountCents") - .HasColumnType("integer") - .HasColumnName("unit_amount_cents"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_items"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_items_tenant_id_id"); - - b.HasIndex("InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_invoice_id"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_items_tenant_id_invoice_id"); - - b.ToTable("tenant_invoice_items", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_items_amounts", "unit_amount_cents >= 0 and amount_cents >= 0"); - - t.HasCheckConstraint("ck_tenant_invoice_items_quantity", "quantity > 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id") - .HasDefaultValueSql("gen_random_uuid()"); - - b.Property("AmountCents") - .HasColumnType("integer") - .HasColumnName("amount_cents"); - - b.Property("CreatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at") - .HasDefaultValueSql("now()"); - - b.Property("InvoiceId") - .HasColumnType("uuid") - .HasColumnName("invoice_id"); - - b.Property("Method") - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("method"); - - b.Property("PaidAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("paid_at"); - - b.Property("PaymentNo") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("payment_no"); - - b.Property("Provider") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)") - .HasColumnName("provider"); - - b.Property("ProviderTradeNo") - .HasMaxLength(200) - .HasColumnType("character varying(200)") - .HasColumnName("provider_trade_no"); - - b.Property("RawPayload") - .ValueGeneratedOnAdd() - .HasColumnType("jsonb") - .HasColumnName("raw_payload") - .HasDefaultValueSql("'{}'::jsonb"); - - b.Property("ReceivedBy") - .HasColumnType("uuid") - .HasColumnName("received_by"); - - b.Property("Status") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)") - .HasColumnName("status"); - - b.Property("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property("UpdatedAt") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasColumnName("updated_at") - .HasDefaultValueSql("now()"); - - b.HasKey("Id") - .HasName("pk_tenant_invoice_payments"); - - b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_payments_tenant_id_id"); - - b.HasIndex("ReceivedBy") - .HasDatabaseName("ix_tenant_invoice_payments_received_by"); - - b.HasIndex("InvoiceId", "Status") - .HasDatabaseName("ix_tenant_invoice_payments_invoice_id_status"); - - b.HasIndex("TenantId", "InvoiceId") - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_invoice_id"); - - b.HasIndex("TenantId", "PaymentNo") - .IsUnique() - .HasDatabaseName("ix_tenant_invoice_payments_tenant_id_payment_no"); - - b.ToTable("tenant_invoice_payments", null, t => - { - t.HasCheckConstraint("ck_tenant_invoice_payments_amount", "amount_cents >= 0"); - }); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingInvoiceReminder", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -13110,33 +12562,1092 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasDefaultValueSql("now()"); b.HasKey("Id") - .HasName("pk_tenant_invoice_reminders"); + .HasName("pk_platform_billing_invoice_reminders"); b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_invoice_reminders_tenant_id_id"); + .HasName("ak_platform_billing_invoice_reminders_tenant_id_id"); b.HasIndex("CreatedBy") - .HasDatabaseName("ix_tenant_invoice_reminders_created_by"); + .HasDatabaseName("ix_platform_billing_invoice_reminders_created_by"); b.HasIndex("InvoiceId", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_invoice_id_reminder_date"); + .HasDatabaseName("ix_platform_billing_invoice_reminders_invoice_id_reminder_date"); b.HasIndex("TenantId", "Status", "ReminderDate") - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_status_reminder_date"); + .HasDatabaseName("ix_platform_billing_invoice_reminders_tenant_id_status_reminde~"); b.HasIndex("TenantId", "InvoiceId", "ReminderType", "Channel", "ReminderDate") .IsUnique() - .HasDatabaseName("ix_tenant_invoice_reminders_tenant_id_invoice_id_reminder_type~"); + .HasDatabaseName("ix_platform_billing_invoice_reminders_tenant_id_invoice_id_rem~"); - b.ToTable("tenant_invoice_reminders", null, t => + b.ToTable("platform_billing_invoice_reminders", null, t => { - t.HasCheckConstraint("ck_tenant_invoice_reminders_balance", "balance_cents_snapshot >= 0"); + t.HasCheckConstraint("ck_platform_billing_invoice_reminders_balance", "balance_cents_snapshot >= 0"); - t.HasCheckConstraint("ck_tenant_invoice_reminders_level", "reminder_level between 1 and 20"); + t.HasCheckConstraint("ck_platform_billing_invoice_reminders_level", "reminder_level between 1 and 20"); }); }); - modelBuilder.Entity("Tiku.Domain.Platform.TenantModuleOverride", b => + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("cancelled_at"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("currency"); + + b.Property("DiscountAmountCents") + .HasColumnType("integer") + .HasColumnName("discount_amount_cents"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("IdempotencyKey") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("idempotency_key"); + + b.Property("OrderNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("order_no"); + + b.Property("OriginalAmountCents") + .HasColumnType("integer") + .HasColumnName("original_amount_cents"); + + b.Property("PaidAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("paid_at"); + + b.Property("Purpose") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("purpose"); + + b.Property("QuoteId") + .HasColumnType("uuid") + .HasColumnName("quote_id"); + + b.Property("Snapshot") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("snapshot") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TotalAmountCents") + .HasColumnType("integer") + .HasColumnName("total_amount_cents"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_platform_billing_orders"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_orders_tenant_id_id"); + + b.HasIndex("TenantId", "IdempotencyKey") + .IsUnique() + .HasDatabaseName("ix_platform_billing_orders_tenant_id_idempotency_key"); + + b.HasIndex("TenantId", "OrderNo") + .IsUnique() + .HasDatabaseName("ix_platform_billing_orders_tenant_id_order_no"); + + b.HasIndex("TenantId", "QuoteId") + .HasDatabaseName("ix_platform_billing_orders_tenant_id_quote_id"); + + b.HasIndex("TenantId", "Status", "CreatedAt") + .HasDatabaseName("ix_platform_billing_orders_tenant_id_status_created_at"); + + b.ToTable("platform_billing_orders", null, t => + { + t.HasCheckConstraint("ck_platform_billing_orders_amounts", "original_amount_cents >= 0 and discount_amount_cents >= 0 and total_amount_cents >= 0 and total_amount_cents = original_amount_cents - discount_amount_cents"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingOrderItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("AmountCents") + .HasColumnType("integer") + .HasColumnName("amount_cents"); + + b.Property("ItemType") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("item_type"); + + b.Property("OfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("offering_version_id"); + + b.Property("OrderId") + .HasColumnType("uuid") + .HasColumnName("order_id"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasColumnName("quantity"); + + b.Property("Snapshot") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("snapshot") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UnitAmountCents") + .HasColumnType("integer") + .HasColumnName("unit_amount_cents"); + + b.HasKey("Id") + .HasName("pk_platform_billing_order_items"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_order_items_tenant_id_id"); + + b.HasIndex("OfferingVersionId") + .HasDatabaseName("ix_platform_billing_order_items_offering_version_id"); + + b.HasIndex("TenantId", "OrderId", "OfferingVersionId") + .IsUnique() + .HasDatabaseName("ix_platform_billing_order_items_tenant_id_order_id_offering_ve~"); + + b.ToTable("platform_billing_order_items", null, t => + { + t.HasCheckConstraint("ck_platform_billing_order_items_amounts", "quantity > 0 and unit_amount_cents >= 0 and amount_cents >= 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingPayment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("AmountCents") + .HasColumnType("integer") + .HasColumnName("amount_cents"); + + b.Property("ClientPayload") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("client_payload") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("IdempotencyKey") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("idempotency_key"); + + b.Property("Method") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("method"); + + b.Property("OrderId") + .HasColumnType("uuid") + .HasColumnName("order_id"); + + b.Property("PaidAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("paid_at"); + + b.Property("PaymentNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("payment_no"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("provider"); + + b.Property("ProviderTradeNo") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("provider_trade_no"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_platform_billing_payments"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_payments_tenant_id_id"); + + b.HasIndex("TenantId", "IdempotencyKey") + .IsUnique() + .HasDatabaseName("ix_platform_billing_payments_tenant_id_idempotency_key"); + + b.HasIndex("TenantId", "PaymentNo") + .IsUnique() + .HasDatabaseName("ix_platform_billing_payments_tenant_id_payment_no"); + + b.HasIndex("TenantId", "OrderId", "Status") + .HasDatabaseName("ix_platform_billing_payments_tenant_id_order_id_status"); + + b.ToTable("platform_billing_payments", null, t => + { + t.HasCheckConstraint("ck_platform_billing_payments_amount", "amount_cents >= 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingPaymentEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("event_type"); + + b.Property("Payload") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("payload") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("PaymentId") + .HasColumnType("uuid") + .HasColumnName("payment_id"); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("provider"); + + b.Property("ProviderEventId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("provider_event_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.HasKey("Id") + .HasName("pk_platform_billing_payment_events"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_payment_events_tenant_id_id"); + + b.HasIndex("TenantId", "PaymentId", "CreatedAt") + .HasDatabaseName("ix_platform_billing_payment_events_tenant_id_payment_id_create~"); + + b.HasIndex("TenantId", "Provider", "ProviderEventId") + .IsUnique() + .HasDatabaseName("ix_platform_billing_payment_events_tenant_id_provider_provider~"); + + b.ToTable("platform_billing_payment_events", (string)null); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingQuote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("currency"); + + b.Property("DiscountAmountCents") + .HasColumnType("integer") + .HasColumnName("discount_amount_cents"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("expires_at"); + + b.Property("FeatureSnapshot") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("feature_snapshot") + .HasDefaultValueSql("'[]'::jsonb"); + + b.Property("IdempotencyKey") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("idempotency_key"); + + b.Property("LimitSnapshot") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("limit_snapshot") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("OriginalAmountCents") + .HasColumnType("integer") + .HasColumnName("original_amount_cents"); + + b.Property("Purpose") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("purpose"); + + b.Property("QuoteNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("quote_no"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TotalAmountCents") + .HasColumnType("integer") + .HasColumnName("total_amount_cents"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_platform_billing_quotes"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_quotes_tenant_id_id"); + + b.HasIndex("TenantId", "IdempotencyKey") + .IsUnique() + .HasDatabaseName("ix_platform_billing_quotes_tenant_id_idempotency_key"); + + b.HasIndex("TenantId", "QuoteNo") + .IsUnique() + .HasDatabaseName("ix_platform_billing_quotes_tenant_id_quote_no"); + + b.HasIndex("TenantId", "Status", "ExpiresAt") + .HasDatabaseName("ix_platform_billing_quotes_tenant_id_status_expires_at"); + + b.ToTable("platform_billing_quotes", null, t => + { + t.HasCheckConstraint("ck_platform_billing_quotes_amounts", "original_amount_cents >= 0 and discount_amount_cents >= 0 and total_amount_cents >= 0 and total_amount_cents = original_amount_cents - discount_amount_cents"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingQuoteItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("AmountCents") + .HasColumnType("integer") + .HasColumnName("amount_cents"); + + b.Property("ItemType") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("item_type"); + + b.Property("OfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("offering_version_id"); + + b.Property("Quantity") + .HasColumnType("integer") + .HasColumnName("quantity"); + + b.Property("QuoteId") + .HasColumnType("uuid") + .HasColumnName("quote_id"); + + b.Property("Snapshot") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("snapshot") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UnitAmountCents") + .HasColumnType("integer") + .HasColumnName("unit_amount_cents"); + + b.HasKey("Id") + .HasName("pk_platform_billing_quote_items"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_quote_items_tenant_id_id"); + + b.HasIndex("OfferingVersionId") + .HasDatabaseName("ix_platform_billing_quote_items_offering_version_id"); + + b.HasIndex("TenantId", "QuoteId", "OfferingVersionId") + .IsUnique() + .HasDatabaseName("ix_platform_billing_quote_items_tenant_id_quote_id_offering_ve~"); + + b.ToTable("platform_billing_quote_items", null, t => + { + t.HasCheckConstraint("ck_platform_billing_quote_items_amounts", "quantity > 0 and unit_amount_cents >= 0 and amount_cents >= 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingRefund", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("AmountCents") + .HasColumnType("integer") + .HasColumnName("amount_cents"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("completed_at"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("OrderId") + .HasColumnType("uuid") + .HasColumnName("order_id"); + + b.Property("PaymentId") + .HasColumnType("uuid") + .HasColumnName("payment_id"); + + b.Property("ProviderRefundNo") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("provider_refund_no"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("reason"); + + b.Property("RefundNo") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("refund_no"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_platform_billing_refunds"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_platform_billing_refunds_tenant_id_id"); + + b.HasIndex("TenantId", "PaymentId") + .HasDatabaseName("ix_platform_billing_refunds_tenant_id_payment_id"); + + b.HasIndex("TenantId", "RefundNo") + .IsUnique() + .HasDatabaseName("ix_platform_billing_refunds_tenant_id_refund_no"); + + b.HasIndex("TenantId", "OrderId", "Status") + .HasDatabaseName("ix_platform_billing_refunds_tenant_id_order_id_status"); + + b.ToTable("platform_billing_refunds", null, t => + { + t.HasCheckConstraint("ck_platform_billing_refunds_amount", "amount_cents > 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("category"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("code"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("currency"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("description"); + + b.Property("IsCore") + .HasColumnType("boolean") + .HasColumnName("is_core"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("ReferencePriceCents") + .HasColumnType("integer") + .HasColumnName("reference_price_cents"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasColumnName("sort_order"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_saas_features"); + + b.HasAlternateKey("Code") + .HasName("ak_saas_features_code"); + + b.HasIndex("Code") + .IsUnique() + .HasDatabaseName("ix_saas_features_code"); + + b.HasIndex("Status", "Category", "SortOrder") + .HasDatabaseName("ix_saas_features_status_category_sort_order"); + + b.ToTable("saas_features", null, t => + { + t.HasCheckConstraint("ck_saas_features_reference_price", "reference_price_cents >= 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasFeatureLimitDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("FeatureCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("feature_code"); + + b.Property("IsHardLimit") + .HasColumnType("boolean") + .HasColumnName("is_hard_limit"); + + b.Property("Kind") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("kind"); + + b.Property("MetricCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("metric_code"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("Unit") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("unit"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.Property("WarningPercent") + .HasColumnType("integer") + .HasColumnName("warning_percent"); + + b.HasKey("Id") + .HasName("pk_saas_feature_limit_definitions"); + + b.HasAlternateKey("MetricCode") + .HasName("ak_saas_feature_limit_definitions_metric_code"); + + b.HasIndex("MetricCode") + .IsUnique() + .HasDatabaseName("ix_saas_feature_limit_definitions_metric_code"); + + b.HasIndex("FeatureCode", "Kind") + .HasDatabaseName("ix_saas_feature_limit_definitions_feature_code_kind"); + + b.ToTable("saas_feature_limit_definitions", null, t => + { + t.HasCheckConstraint("ck_saas_feature_limit_warning", "warning_percent between 1 and 100"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOffering", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("code"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("description"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("SortOrder") + .HasColumnType("integer") + .HasColumnName("sort_order"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_saas_offerings"); + + b.HasIndex("Code") + .IsUnique() + .HasDatabaseName("ix_saas_offerings_code"); + + b.HasIndex("Type", "Status", "SortOrder") + .HasDatabaseName("ix_saas_offerings_type_status_sort_order"); + + b.ToTable("saas_offerings", (string)null); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOfferingVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("AmountCents") + .HasColumnType("integer") + .HasColumnName("amount_cents"); + + b.Property("BillingCycle") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("billing_cycle"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("currency"); + + b.Property("EffectiveAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("effective_at"); + + b.Property("Metadata") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("metadata") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("OfferingId") + .HasColumnType("uuid") + .HasColumnName("offering_id"); + + b.Property("OriginalAmountCents") + .HasColumnType("integer") + .HasColumnName("original_amount_cents"); + + b.Property("PublishedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("published_at"); + + b.Property("RetiredAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("retired_at"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.Property("Version") + .HasColumnType("integer") + .HasColumnName("version"); + + b.HasKey("Id") + .HasName("pk_saas_offering_versions"); + + b.HasIndex("OfferingId", "Version") + .IsUnique() + .HasDatabaseName("ix_saas_offering_versions_offering_id_version"); + + b.HasIndex("OfferingId", "Status", "EffectiveAt") + .HasDatabaseName("ix_saas_offering_versions_offering_id_status_effective_at"); + + b.ToTable("saas_offering_versions", null, t => + { + t.HasCheckConstraint("ck_saas_offering_versions_amount", "original_amount_cents >= 0 and amount_cents >= 0 and amount_cents <= original_amount_cents"); + + t.HasCheckConstraint("ck_saas_offering_versions_version", "version > 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOfferingVersionFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("FeatureCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("feature_code"); + + b.Property("OfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("offering_version_id"); + + b.HasKey("Id") + .HasName("pk_saas_offering_version_features"); + + b.HasIndex("FeatureCode") + .HasDatabaseName("ix_saas_offering_version_features_feature_code"); + + b.HasIndex("OfferingVersionId", "FeatureCode") + .IsUnique() + .HasDatabaseName("ix_saas_offering_version_features_offering_version_id_feature_~"); + + b.ToTable("saas_offering_version_features", (string)null); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOfferingVersionLimit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("LimitValue") + .HasColumnType("bigint") + .HasColumnName("limit_value"); + + b.Property("MetricCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("metric_code"); + + b.Property("OfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("offering_version_id"); + + b.HasKey("Id") + .HasName("pk_saas_offering_version_limits"); + + b.HasIndex("MetricCode") + .HasDatabaseName("ix_saas_offering_version_limits_metric_code"); + + b.HasIndex("OfferingVersionId", "MetricCode") + .IsUnique() + .HasDatabaseName("ix_saas_offering_version_limits_offering_version_id_metric_code"); + + b.ToTable("saas_offering_version_limits", null, t => + { + t.HasCheckConstraint("ck_saas_offering_version_limits_value", "limit_value >= 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => + { + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("BankAccountMasked") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("bank_account_masked"); + + b.Property("BankName") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("bank_name"); + + b.Property("BillingAddress") + .HasColumnType("text") + .HasColumnName("billing_address"); + + b.Property("BillingName") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .HasColumnName("billing_name"); + + b.Property("ContactEmail") + .HasMaxLength(300) + .HasColumnType("citext") + .HasColumnName("contact_email"); + + b.Property("ContactName") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("contact_name"); + + b.Property("ContactPhone") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("contact_phone"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("InvoiceTitle") + .HasMaxLength(300) + .HasColumnType("character varying(300)") + .HasColumnName("invoice_title"); + + b.Property("InvoiceType") + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("invoice_type"); + + b.Property("Metadata") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("metadata") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("TaxId") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("tax_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("TenantId") + .HasName("pk_tenant_billing_profiles"); + + b.ToTable("tenant_billing_profiles", (string)null); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantFeatureOverride", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -13154,19 +13665,20 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasColumnType("timestamp with time zone") .HasColumnName("expires_at"); + b.Property("FeatureCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("feature_code"); + b.Property("Mode") .IsRequired() .HasMaxLength(32) .HasColumnType("character varying(32)") .HasColumnName("mode"); - b.Property("ModuleCode") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)") - .HasColumnName("module_code"); - b.Property("Reason") + .IsRequired() .HasMaxLength(1000) .HasColumnType("character varying(1000)") .HasColumnName("reason"); @@ -13182,19 +13694,277 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasDefaultValueSql("now()"); b.HasKey("Id") - .HasName("pk_tenant_module_overrides"); + .HasName("pk_tenant_feature_overrides"); b.HasAlternateKey("TenantId", "Id") - .HasName("ak_tenant_module_overrides_tenant_id_id"); + .HasName("ak_tenant_feature_overrides_tenant_id_id"); - b.HasIndex("ModuleCode") - .HasDatabaseName("ix_tenant_module_overrides_module_code"); + b.HasIndex("FeatureCode") + .HasDatabaseName("ix_tenant_feature_overrides_feature_code"); - b.HasIndex("TenantId", "ModuleCode") + b.HasIndex("TenantId", "FeatureCode") .IsUnique() - .HasDatabaseName("ix_tenant_module_overrides_tenant_id_module_code"); + .HasDatabaseName("ix_tenant_feature_overrides_tenant_id_feature_code"); - b.ToTable("tenant_module_overrides", (string)null); + b.ToTable("tenant_feature_overrides", (string)null); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantFeatureUsage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("LimitValueSnapshot") + .HasColumnType("bigint") + .HasColumnName("limit_value_snapshot"); + + b.Property("MetricCode") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("character varying(120)") + .HasColumnName("metric_code"); + + b.Property("PeriodEnd") + .HasColumnType("timestamp with time zone") + .HasColumnName("period_end"); + + b.Property("PeriodStart") + .HasColumnType("timestamp with time zone") + .HasColumnName("period_start"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.Property("UsedValue") + .HasColumnType("bigint") + .HasColumnName("used_value"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("bigint") + .HasColumnName("version"); + + b.Property("WarningIssued") + .HasColumnType("boolean") + .HasColumnName("warning_issued"); + + b.HasKey("Id") + .HasName("pk_tenant_feature_usage"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_tenant_feature_usage_tenant_id_id"); + + b.HasIndex("MetricCode") + .HasDatabaseName("ix_tenant_feature_usage_metric_code"); + + b.HasIndex("TenantId", "MetricCode", "PeriodStart", "PeriodEnd") + .IsUnique() + .HasDatabaseName("ix_tenant_feature_usage_tenant_id_metric_code_period_start_per~"); + + b.ToTable("tenant_feature_usage", null, t => + { + t.HasCheckConstraint("ck_tenant_feature_usage_period", "period_end > period_start"); + + t.HasCheckConstraint("ck_tenant_feature_usage_values", "used_value >= 0 and limit_value_snapshot >= 0"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantSaasSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("BaseOfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("base_offering_version_id"); + + b.Property("CancelAtPeriodEnd") + .HasColumnType("boolean") + .HasColumnName("cancel_at_period_end"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("cancelled_at"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("CurrentPeriodEnd") + .HasColumnType("timestamp with time zone") + .HasColumnName("current_period_end"); + + b.Property("CurrentPeriodStart") + .HasColumnType("timestamp with time zone") + .HasColumnName("current_period_start"); + + b.Property("LifecycleVersion") + .IsConcurrencyToken() + .HasColumnType("bigint") + .HasColumnName("lifecycle_version"); + + b.Property("Metadata") + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasColumnName("metadata") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("ScheduledBaseOfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("scheduled_base_offering_version_id"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("starts_at"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_tenant_saas_subscriptions"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_tenant_saas_subscriptions_tenant_id_id"); + + b.HasIndex("BaseOfferingVersionId") + .HasDatabaseName("ix_tenant_saas_subscriptions_base_offering_version_id"); + + b.HasIndex("ScheduledBaseOfferingVersionId") + .HasDatabaseName("ix_tenant_saas_subscriptions_scheduled_base_offering_version_id"); + + b.HasIndex("TenantId") + .IsUnique() + .HasDatabaseName("ix_tenant_saas_subscriptions_tenant_id") + .HasFilter("status in ('trial', 'active', 'past_due', 'suspended')"); + + b.HasIndex("TenantId", "Status", "CurrentPeriodEnd") + .HasDatabaseName("ix_tenant_saas_subscriptions_tenant_id_status_current_period_e~"); + + b.ToTable("tenant_saas_subscriptions", null, t => + { + t.HasCheckConstraint("ck_tenant_saas_subscriptions_period", "current_period_end > current_period_start and current_period_start >= starts_at"); + }); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantSaasSubscriptionItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id") + .HasDefaultValueSql("gen_random_uuid()"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("EndsAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("ends_at"); + + b.Property("ItemType") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("item_type"); + + b.Property("OfferingVersionId") + .HasColumnType("uuid") + .HasColumnName("offering_version_id"); + + b.Property("SourceOrderItemId") + .HasColumnType("uuid") + .HasColumnName("source_order_item_id"); + + b.Property("StartsAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("starts_at"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasColumnName("status"); + + b.Property("SubscriptionId") + .HasColumnType("uuid") + .HasColumnName("subscription_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id") + .HasName("pk_tenant_saas_subscription_items"); + + b.HasAlternateKey("TenantId", "Id") + .HasName("ak_tenant_saas_subscription_items_tenant_id_id"); + + b.HasIndex("OfferingVersionId") + .HasDatabaseName("ix_tenant_saas_subscription_items_offering_version_id"); + + b.HasIndex("TenantId", "SourceOrderItemId") + .HasDatabaseName("ix_tenant_saas_subscription_items_tenant_id_source_order_item_~"); + + b.HasIndex("TenantId", "SubscriptionId", "ItemType") + .IsUnique() + .HasDatabaseName("ux_tenant_saas_subscription_items_current_base") + .HasFilter("item_type = 'base_plan' and status = 'active'"); + + b.HasIndex("TenantId", "SubscriptionId", "Status") + .IsUnique() + .HasDatabaseName("ux_tenant_saas_subscription_items_scheduled_base") + .HasFilter("item_type = 'base_plan' and status = 'scheduled'"); + + b.HasIndex("TenantId", "SubscriptionId", "OfferingVersionId", "Status") + .HasDatabaseName("ix_tenant_saas_subscription_items_tenant_id_subscription_id_of~"); + + b.ToTable("tenant_saas_subscription_items", null, t => + { + t.HasCheckConstraint("ck_tenant_saas_subscription_items_period", "ends_at > starts_at"); + }); }); modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => @@ -14033,6 +14803,13 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasDefaultValue(false) .HasColumnName("allow_external_student_self_registration"); + b.PrimitiveCollection("AllowedStudentLoginMethods") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text[]") + .HasColumnName("allowed_student_login_methods") + .HasDefaultValueSql("ARRAY['password']::text[]"); + b.Property("CreatedAt") .ValueGeneratedOnAdd() .HasColumnType("timestamp with time zone") @@ -15958,26 +16735,6 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasConstraintName("fk_svip_plans_regions_tenant_id_region_id"); }); - modelBuilder.Entity("Tiku.Domain.Commerce.TenantSubscription", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_subscriptions_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Commerce.TenantUsageRecord", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_usage_records_tenants_tenant_id"); - }); - modelBuilder.Entity("Tiku.Domain.Content.AiRecommendationReport", b => { b.HasOne("Tiku.Domain.Tenancy.Tenant", null) @@ -17676,6 +18433,17 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasConstraintName("fk_backend_menus_backend_permissions_permission_code"); }); + modelBuilder.Entity("Tiku.Domain.Operations.BackendPermission", b => + { + b.HasOne("Tiku.Domain.Platform.PermissionModule", null) + .WithMany() + .HasForeignKey("PermissionModuleCode") + .HasPrincipalKey("Code") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_backend_permissions_permission_modules_permission_module_co~"); + }); + modelBuilder.Entity("Tiku.Domain.Operations.BackgroundJob", b => { b.HasOne("Tiku.Domain.Tenancy.Tenant", null) @@ -17973,23 +18741,14 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasConstraintName("fk_user_notifications_users_user_id"); }); - modelBuilder.Entity("Tiku.Domain.Platform.PlanModuleEntitlement", b => + modelBuilder.Entity("Tiku.Domain.Platform.PermissionModule", b => { - b.HasOne("Tiku.Domain.Platform.ProductModule", null) + b.HasOne("Tiku.Domain.Platform.SaasFeature", null) .WithMany() - .HasForeignKey("ModuleCode") + .HasForeignKey("RequiredFeatureCode") .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_plan_module_entitlements_product_modules_module_code"); - - b.HasOne("Tiku.Domain.Platform.PlatformSaasPlan", null) - .WithMany() - .HasForeignKey("PlanCode") - .HasPrincipalKey("Code") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_plan_module_entitlements_platform_saas_plans_plan_code"); + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_permission_modules_saas_features_required_feature_code"); }); modelBuilder.Entity("Tiku.Domain.Platform.PlatformAuditAlert", b => @@ -18036,37 +18795,274 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasConstraintName("fk_platform_audit_alert_rules_tenants_tenant_id"); }); - modelBuilder.Entity("Tiku.Domain.Platform.PlatformDunningNotificationEvent", b => + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingDunningNotificationEvent", b => { - b.HasOne("Tiku.Domain.Platform.PlatformDunningNotificationChannel", null) + b.HasOne("Tiku.Domain.Platform.PlatformBillingDunningNotificationChannel", null) .WithMany() .HasForeignKey("ChannelId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_platform_dunning_notif~"); + .HasConstraintName("fk_platform_billing_dunning_events_channel"); b.HasOne("Tiku.Domain.Tenancy.Tenant", null) .WithMany() .HasForeignKey("TenantId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenants_tenant_id"); + .HasConstraintName("fk_platform_billing_dunning_notification_events_tenants_tenant~"); - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) + b.HasOne("Tiku.Domain.Platform.PlatformBillingInvoice", null) .WithMany() .HasForeignKey("TenantId", "InvoiceId") .HasPrincipalKey("TenantId", "Id") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoices_tenant~"); + .HasConstraintName("fk_platform_billing_dunning_events_invoice"); - b.HasOne("Tiku.Domain.Platform.TenantInvoiceReminder", null) + b.HasOne("Tiku.Domain.Platform.PlatformBillingInvoiceReminder", null) .WithMany() .HasForeignKey("TenantId", "ReminderId") .HasPrincipalKey("TenantId", "Id") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_platform_dunning_notification_events_tenant_invoice_reminde~"); + .HasConstraintName("fk_platform_billing_dunning_events_reminder"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingInvoice", b => + { + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_invoices_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingOrder", null) + .WithMany() + .HasForeignKey("TenantId", "OrderId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_platform_billing_invoices_platform_billing_orders_tenant_id~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingInvoiceReminder", b => + { + b.HasOne("Tiku.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("CreatedBy") + .OnDelete(DeleteBehavior.SetNull) + .HasConstraintName("fk_platform_billing_invoice_reminders_users_created_by"); + + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_invoice_reminders_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingInvoice", null) + .WithMany() + .HasForeignKey("TenantId", "InvoiceId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_invoice_reminders_platform_billing_invoice~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingOrder", b => + { + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_orders_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingQuote", null) + .WithMany() + .HasForeignKey("TenantId", "QuoteId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_platform_billing_orders_platform_billing_quotes_tenant_id_q~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingOrderItem", b => + { + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("OfferingVersionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_platform_billing_order_items_saas_offering_versions_offerin~"); + + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_order_items_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingOrder", null) + .WithMany() + .HasForeignKey("TenantId", "OrderId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_order_items_platform_billing_orders_tenant~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingPayment", b => + { + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_payments_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingOrder", null) + .WithMany() + .HasForeignKey("TenantId", "OrderId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_payments_platform_billing_orders_tenant_id~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingPaymentEvent", b => + { + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_payment_events_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingPayment", null) + .WithMany() + .HasForeignKey("TenantId", "PaymentId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_payment_events_platform_billing_payments_t~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingQuote", b => + { + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_quotes_tenants_tenant_id"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingQuoteItem", b => + { + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("OfferingVersionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_platform_billing_quote_items_saas_offering_versions_offerin~"); + + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_quote_items_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingQuote", null) + .WithMany() + .HasForeignKey("TenantId", "QuoteId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_quote_items_platform_billing_quotes_tenant~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.PlatformBillingRefund", b => + { + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_platform_billing_refunds_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingOrder", null) + .WithMany() + .HasForeignKey("TenantId", "OrderId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_platform_billing_refunds_platform_billing_orders_tenant_id_~"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingPayment", null) + .WithMany() + .HasForeignKey("TenantId", "PaymentId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_platform_billing_refunds_platform_billing_payments_tenant_i~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasFeatureLimitDefinition", b => + { + b.HasOne("Tiku.Domain.Platform.SaasFeature", null) + .WithMany() + .HasForeignKey("FeatureCode") + .HasPrincipalKey("Code") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_saas_feature_limit_definitions_saas_features_feature_code"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOfferingVersion", b => + { + b.HasOne("Tiku.Domain.Platform.SaasOffering", null) + .WithMany() + .HasForeignKey("OfferingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_saas_offering_versions_saas_offerings_offering_id"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOfferingVersionFeature", b => + { + b.HasOne("Tiku.Domain.Platform.SaasFeature", null) + .WithMany() + .HasForeignKey("FeatureCode") + .HasPrincipalKey("Code") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_saas_offering_version_features_saas_features_feature_code"); + + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("OfferingVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_saas_offering_version_features_saas_offering_versions_offer~"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.SaasOfferingVersionLimit", b => + { + b.HasOne("Tiku.Domain.Platform.SaasFeatureLimitDefinition", null) + .WithMany() + .HasForeignKey("MetricCode") + .HasPrincipalKey("MetricCode") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_saas_offering_version_limits_saas_feature_limit_definitions~"); + + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("OfferingVersionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_saas_offering_version_limits_saas_offering_versions_offerin~"); }); modelBuilder.Entity("Tiku.Domain.Platform.TenantBillingProfile", b => @@ -18079,104 +19075,95 @@ namespace Tiku.Infrastructure.Persistence.Migrations .HasConstraintName("fk_tenant_billing_profiles_tenants_tenant_id"); }); - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoice", b => + modelBuilder.Entity("Tiku.Domain.Platform.TenantFeatureOverride", b => { - b.HasOne("Tiku.Domain.Identity.User", null) + b.HasOne("Tiku.Domain.Platform.SaasFeature", null) .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoices_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoices_tenants_tenant_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceItem", b => - { - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_items_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoicePayment", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("ReceivedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_payments_users_received_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_payments_tenant_invoices_tenant_id_invoice_id"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantInvoiceReminder", b => - { - b.HasOne("Tiku.Domain.Identity.User", null) - .WithMany() - .HasForeignKey("CreatedBy") - .OnDelete(DeleteBehavior.SetNull) - .HasConstraintName("fk_tenant_invoice_reminders_users_created_by"); - - b.HasOne("Tiku.Domain.Tenancy.Tenant", null) - .WithMany() - .HasForeignKey("TenantId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenants_tenant_id"); - - b.HasOne("Tiku.Domain.Platform.TenantInvoice", null) - .WithMany() - .HasForeignKey("TenantId", "InvoiceId") - .HasPrincipalKey("TenantId", "Id") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired() - .HasConstraintName("fk_tenant_invoice_reminders_tenant_invoices_tenant_id_invoice_~"); - }); - - modelBuilder.Entity("Tiku.Domain.Platform.TenantModuleOverride", b => - { - b.HasOne("Tiku.Domain.Platform.ProductModule", null) - .WithMany() - .HasForeignKey("ModuleCode") + .HasForeignKey("FeatureCode") .HasPrincipalKey("Code") .OnDelete(DeleteBehavior.Restrict) .IsRequired() - .HasConstraintName("fk_tenant_module_overrides_product_modules_module_code"); + .HasConstraintName("fk_tenant_feature_overrides_saas_features_feature_code"); b.HasOne("Tiku.Domain.Tenancy.Tenant", null) .WithMany() .HasForeignKey("TenantId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_tenant_module_overrides_tenants_tenant_id"); + .HasConstraintName("fk_tenant_feature_overrides_tenants_tenant_id"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantFeatureUsage", b => + { + b.HasOne("Tiku.Domain.Platform.SaasFeatureLimitDefinition", null) + .WithMany() + .HasForeignKey("MetricCode") + .HasPrincipalKey("MetricCode") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_tenant_feature_usage_saas_feature_limit_definitions_metric_~"); + + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_feature_usage_tenants_tenant_id"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantSaasSubscription", b => + { + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("BaseOfferingVersionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_tenant_saas_subscriptions_saas_offering_versions_base_offer~"); + + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("ScheduledBaseOfferingVersionId") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_tenant_saas_subscriptions_saas_offering_versions_scheduled_~"); + + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_saas_subscriptions_tenants_tenant_id"); + }); + + modelBuilder.Entity("Tiku.Domain.Platform.TenantSaasSubscriptionItem", b => + { + b.HasOne("Tiku.Domain.Platform.SaasOfferingVersion", null) + .WithMany() + .HasForeignKey("OfferingVersionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired() + .HasConstraintName("fk_tenant_saas_subscription_items_saas_offering_versions_offer~"); + + b.HasOne("Tiku.Domain.Tenancy.Tenant", null) + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_saas_subscription_items_tenants_tenant_id"); + + b.HasOne("Tiku.Domain.Platform.PlatformBillingOrderItem", null) + .WithMany() + .HasForeignKey("TenantId", "SourceOrderItemId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Restrict) + .HasConstraintName("fk_tenant_saas_subscription_items_platform_billing_order_items~"); + + b.HasOne("Tiku.Domain.Platform.TenantSaasSubscription", null) + .WithMany() + .HasForeignKey("TenantId", "SubscriptionId") + .HasPrincipalKey("TenantId", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_saas_subscription_items_tenant_saas_subscriptions_te~"); }); modelBuilder.Entity("Tiku.Domain.QuestionBanks.Question", b => diff --git a/Tiku.Infrastructure/Persistence/PostgreSqlSaasCatalogConstraintSql.cs b/Tiku.Infrastructure/Persistence/PostgreSqlSaasCatalogConstraintSql.cs new file mode 100644 index 0000000..116a2f3 --- /dev/null +++ b/Tiku.Infrastructure/Persistence/PostgreSqlSaasCatalogConstraintSql.cs @@ -0,0 +1,224 @@ +namespace Tiku.Infrastructure.Persistence; + +internal static class PostgreSqlSaasCatalogConstraintSql +{ + public const string CreateOfferingVersionImmutabilityGuards = """ + create or replace function tiku_guard_saas_offering_version_immutable() + returns trigger + language plpgsql + as $$ + begin + if tg_op = 'DELETE' then + if old.status in ('published', 'retired') then + raise exception 'published or retired SaaS offering versions cannot be deleted' + using errcode = '23514', + constraint = 'ck_saas_offering_versions_published_immutable'; + end if; + + return old; + end if; + + if old.status not in ('published', 'retired') then + return new; + end if; + + if old.status = 'published' + and new.status = 'retired' + and new.retired_at is not null + and new.id is not distinct from old.id + and new.offering_id is not distinct from old.offering_id + and new.version is not distinct from old.version + and new.billing_cycle is not distinct from old.billing_cycle + and new.original_amount_cents is not distinct from old.original_amount_cents + and new.amount_cents is not distinct from old.amount_cents + and new.currency is not distinct from old.currency + and new.effective_at is not distinct from old.effective_at + and new.published_at is not distinct from old.published_at + and new.metadata is not distinct from old.metadata + and new.created_at is not distinct from old.created_at then + return new; + end if; + + raise exception 'published or retired SaaS offering versions are immutable' + using errcode = '23514', + constraint = 'ck_saas_offering_versions_published_immutable'; + end; + $$; + + do $guard$ + begin + if to_regclass('saas_offering_versions') is not null then + execute 'create trigger trg_saas_offering_versions_published_immutable + before update or delete on saas_offering_versions + for each row execute function tiku_guard_saas_offering_version_immutable()'; + end if; + end + $guard$; + """; + + public const string CreateOfferingVersionChildrenImmutabilityGuards = """ + create or replace function tiku_guard_saas_offering_version_child_immutable() + returns trigger + language plpgsql + as $$ + declare + version_status text; + begin + if tg_op = 'DELETE' then + select status into version_status + from saas_offering_versions + where id = old.offering_version_id; + + if version_status is distinct from 'draft' then + raise exception 'published or retired SaaS offering version children are immutable' + using errcode = '23514', + constraint = 'ck_saas_offering_versions_published_immutable'; + end if; + + return old; + end if; + + if tg_op = 'UPDATE' and old.offering_version_id <> new.offering_version_id then + select status into version_status + from saas_offering_versions + where id = old.offering_version_id; + + if version_status is distinct from 'draft' then + raise exception 'published or retired SaaS offering version children are immutable' + using errcode = '23514', + constraint = 'ck_saas_offering_versions_published_immutable'; + end if; + end if; + + select status into version_status + from saas_offering_versions + where id = new.offering_version_id; + + if version_status is distinct from 'draft' then + raise exception 'published or retired SaaS offering version children are immutable' + using errcode = '23514', + constraint = 'ck_saas_offering_versions_published_immutable'; + end if; + + return new; + end; + $$; + + do $guard$ + begin + if to_regclass('saas_offering_version_features') is not null then + execute 'create trigger trg_saas_offering_version_features_published_immutable + before insert or update or delete on saas_offering_version_features + for each row execute function tiku_guard_saas_offering_version_child_immutable()'; + end if; + + if to_regclass('saas_offering_version_limits') is not null then + execute 'create trigger trg_saas_offering_version_limits_published_immutable + before insert or update or delete on saas_offering_version_limits + for each row execute function tiku_guard_saas_offering_version_child_immutable()'; + end if; + end + $guard$; + """; + + public const string CreateSubscriptionOfferingTypeGuards = """ + create or replace function tiku_guard_saas_subscription_offering_type() + returns trigger + language plpgsql + as $$ + declare + offering_type text; + source_version_id uuid; + begin + if tg_table_name = 'tenant_saas_subscriptions' then + select o.type into offering_type + from saas_offering_versions v + join saas_offerings o on o.id = v.offering_id + where v.id = new.base_offering_version_id; + + if offering_type is distinct from 'base_plan' then + raise exception 'subscription base offering version must belong to a base plan' + using errcode = '23514', constraint = 'ck_tenant_saas_subscription_base_plan'; + end if; + + if new.scheduled_base_offering_version_id is not null then + select o.type into offering_type + from saas_offering_versions v + join saas_offerings o on o.id = v.offering_id + where v.id = new.scheduled_base_offering_version_id; + if offering_type is distinct from 'base_plan' then + raise exception 'scheduled subscription offering version must belong to a base plan' + using errcode = '23514', constraint = 'ck_tenant_saas_subscription_base_plan'; + end if; + end if; + return new; + end if; + + select o.type into offering_type + from saas_offering_versions v + join saas_offerings o on o.id = v.offering_id + where v.id = new.offering_version_id; + if offering_type is distinct from new.item_type::text then + raise exception 'subscription item type must match its offering type' + using errcode = '23514', constraint = 'ck_tenant_saas_subscription_item_offering_type'; + end if; + + if new.source_order_item_id is not null then + select offering_version_id into source_version_id + from platform_billing_order_items + where tenant_id = new.tenant_id and id = new.source_order_item_id; + if source_version_id is distinct from new.offering_version_id then + raise exception 'subscription item must match its order item snapshot' + using errcode = '23514', constraint = 'ck_tenant_saas_subscription_item_order_snapshot'; + end if; + end if; + return new; + end; + $$; + + do $guard$ + begin + if to_regclass('tenant_saas_subscriptions') is not null then + execute 'create trigger trg_tenant_saas_subscriptions_base_plan + before insert or update on tenant_saas_subscriptions + for each row execute function tiku_guard_saas_subscription_offering_type()'; + end if; + if to_regclass('tenant_saas_subscription_items') is not null then + execute 'create trigger trg_tenant_saas_subscription_items_offering_type + before insert or update on tenant_saas_subscription_items + for each row execute function tiku_guard_saas_subscription_offering_type()'; + end if; + end + $guard$; + """; + + public const string DropOfferingVersionImmutabilityGuards = """ + do $guard$ + begin + if to_regclass('saas_offering_version_features') is not null then + execute 'drop trigger if exists trg_saas_offering_version_features_published_immutable on saas_offering_version_features'; + end if; + + if to_regclass('saas_offering_version_limits') is not null then + execute 'drop trigger if exists trg_saas_offering_version_limits_published_immutable on saas_offering_version_limits'; + end if; + + if to_regclass('saas_offering_versions') is not null then + execute 'drop trigger if exists trg_saas_offering_versions_published_immutable on saas_offering_versions'; + end if; + + if to_regclass('tenant_saas_subscription_items') is not null then + execute 'drop trigger if exists trg_tenant_saas_subscription_items_offering_type on tenant_saas_subscription_items'; + end if; + + if to_regclass('tenant_saas_subscriptions') is not null then + execute 'drop trigger if exists trg_tenant_saas_subscriptions_base_plan on tenant_saas_subscriptions'; + end if; + end + $guard$; + + drop function if exists tiku_guard_saas_offering_version_child_immutable(); + drop function if exists tiku_guard_saas_offering_version_immutable(); + drop function if exists tiku_guard_saas_subscription_offering_type(); + """; +} diff --git a/Tiku.Infrastructure/Persistence/TikuDbContext.cs b/Tiku.Infrastructure/Persistence/TikuDbContext.cs index 2b71f7e..b329542 100644 --- a/Tiku.Infrastructure/Persistence/TikuDbContext.cs +++ b/Tiku.Infrastructure/Persistence/TikuDbContext.cs @@ -132,8 +132,6 @@ public sealed class TikuDbContext( public DbSet ActivationCodes => Set(); public DbSet Coupons => Set(); public DbSet CouponRedemptions => Set(); - public DbSet TenantSubscriptions => Set(); - public DbSet TenantUsageRecords => Set(); public DbSet CommerceRefundRequests => Set(); public DbSet CommerceRefundEvents => Set(); public DbSet CommerceReconciliationBatches => Set(); @@ -180,19 +178,31 @@ public sealed class TikuDbContext( public DbSet TenantContentNotifications => Set(); public DbSet TenantThemeTemplates => Set(); public DbSet TenantThemeConfigs => Set(); - public DbSet PlatformSaasPlans => Set(); - public DbSet ProductModules => Set(); - public DbSet PlanModuleEntitlements => Set(); - public DbSet TenantModuleOverrides => Set(); public DbSet TenantBillingProfiles => Set(); - public DbSet TenantInvoices => Set(); - public DbSet TenantInvoiceItems => Set(); - public DbSet TenantInvoicePayments => Set(); - public DbSet TenantInvoiceReminders => Set(); + public DbSet PlatformBillingInvoiceReminders => Set(); public DbSet PlatformAuditAlertRules => Set(); public DbSet PlatformAuditAlerts => Set(); - public DbSet PlatformDunningNotificationChannels => Set(); - public DbSet PlatformDunningNotificationEvents => Set(); + public DbSet PlatformBillingDunningNotificationChannels => Set(); + public DbSet PlatformBillingDunningNotificationEvents => Set(); + public DbSet SaasFeatures => Set(); + public DbSet PermissionModules => Set(); + public DbSet SaasOfferings => Set(); + public DbSet SaasOfferingVersions => Set(); + public DbSet SaasOfferingVersionFeatures => Set(); + public DbSet SaasFeatureLimitDefinitions => Set(); + public DbSet SaasOfferingVersionLimits => Set(); + public DbSet TenantSaasSubscriptions => Set(); + public DbSet TenantSaasSubscriptionItems => Set(); + public DbSet TenantFeatureOverrides => Set(); + public DbSet TenantFeatureUsages => Set(); + public DbSet PlatformBillingQuotes => Set(); + public DbSet PlatformBillingQuoteItems => Set(); + public DbSet PlatformBillingOrders => Set(); + public DbSet PlatformBillingOrderItems => Set(); + public DbSet PlatformBillingPayments => Set(); + public DbSet PlatformBillingPaymentEvents => Set(); + public DbSet PlatformBillingRefunds => Set(); + public DbSet PlatformBillingInvoices => Set(); public DbSet PocketBaseImportRuns => Set(); public DbSet PocketBaseRawRecords => Set(); public DbSet PocketBaseImportIssues => Set(); diff --git a/Tiku.Infrastructure/PlatformAdmin/PlatformAdminService.cs b/Tiku.Infrastructure/PlatformAdmin/PlatformAdminService.cs index 233afea..8153e63 100644 --- a/Tiku.Infrastructure/PlatformAdmin/PlatformAdminService.cs +++ b/Tiku.Infrastructure/PlatformAdmin/PlatformAdminService.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Tiku.Application.PlatformAdmin; @@ -85,10 +86,10 @@ internal sealed class PlatformAdminService( { Tenant = tenant, DomainCount = dbContext.TenantDomains.Count(domain => domain.TenantId == tenant.Id), - SubscriptionExpiresAt = dbContext.TenantSubscriptions + SubscriptionExpiresAt = dbContext.TenantSaasSubscriptions .Where(subscription => subscription.TenantId == tenant.Id) - .OrderByDescending(subscription => subscription.ExpiresAt) - .Select(subscription => subscription.ExpiresAt) + .OrderByDescending(subscription => subscription.CurrentPeriodEnd) + .Select(subscription => (DateTimeOffset?)subscription.CurrentPeriodEnd) .FirstOrDefault() }) .ToArrayAsync(cancellationToken); @@ -113,7 +114,7 @@ internal sealed class PlatformAdminService( .OrderByDescending(domain => domain.IsPrimary) .ThenBy(domain => domain.Host) .ToArrayAsync(cancellationToken); - var subscriptions = await dbContext.TenantSubscriptions.AsNoTracking() + var subscriptions = await dbContext.TenantSaasSubscriptions.AsNoTracking() .Where(subscription => subscription.TenantId == tenantId) .OrderByDescending(subscription => subscription.CreatedAt) .ToArrayAsync(cancellationToken); @@ -121,26 +122,40 @@ internal sealed class PlatformAdminService( .SingleOrDefaultAsync(profile => profile.TenantId == tenantId, cancellationToken); return new PlatformTenantDetail( - ToTenantItem(tenant, domains.Length, subscriptions.FirstOrDefault()?.ExpiresAt), + ToTenantItem(tenant, domains.Length, subscriptions.FirstOrDefault()?.CurrentPeriodEnd), domains.Select(ToDomainItem).ToArray(), subscriptions.Select(ToSubscriptionItem).ToArray(), billingProfile is null ? null : ToBillingProfileItem(billingProfile)); }, cancellationToken); } - public async Task CreateTenantAsync( + public async Task CreateTenantAsync( PlatformAdminActor actor, CreatePlatformTenantCommand command, CancellationToken cancellationToken = default) { await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken); - return await ExecuteSystemAsync("platform tenant create", async dbContext => + return await ExecuteSystemAsync("platform tenant create", async (provider, dbContext) => { + await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken); var slug = NormalizeCode(command.Slug); if (await dbContext.Tenants.AnyAsync(tenant => tenant.Slug == slug, cancellationToken)) { throw new PlatformAdminException("Tenant slug already exists.", "tenant_slug_exists"); } + var ownerEmail = Normalize(command.OwnerEmail); + var ownerPhone = Normalize(command.OwnerPhone); + var ownerIdentifier = ownerEmail ?? ownerPhone; + if (ownerIdentifier is null) + { + throw new PlatformAdminException("Owner email or phone is required.", "tenant_owner_identifier_required"); + } + if (await dbContext.Users.AnyAsync(user => + (ownerEmail != null && user.NormalizedEmail == ownerEmail.ToUpperInvariant()) || + (ownerPhone != null && user.Phone == ownerPhone), cancellationToken)) + { + throw new PlatformAdminException("Tenant owner already exists.", "tenant_owner_exists"); + } var tenant = new Tenant { @@ -153,14 +168,51 @@ internal sealed class PlatformAdminService( Metadata = JsonObjectOrDefault(command.Metadata) }; dbContext.Tenants.Add(tenant); + var owner = new User + { + Email = ownerEmail, + NormalizedEmail = ownerEmail?.ToUpperInvariant(), + UserName = ownerIdentifier, + NormalizedUserName = ownerIdentifier.ToUpperInvariant(), + Phone = ownerPhone, + PhoneNumber = ownerPhone, + Name = command.OwnerName.Trim(), + PrimaryRole = "tenant_owner", + Status = UserStatus.Active, + ForcePasswordChange = true, + EmailConfirmed = ownerEmail is not null, + PhoneNumberConfirmed = ownerPhone is not null + }; + var createOwner = await provider.GetRequiredService>() + .CreateAsync(owner, command.TemporaryPassword); + if (!createOwner.Succeeded) + { + throw new PlatformAdminException( + string.Join("; ", createOwner.Errors.Select(error => error.Description)), + "tenant_owner_password_invalid"); + } + tenant.OwnerUserId = owner.Id; + dbContext.TenantMemberships.Add(new TenantMembership + { + TenantId = tenant.Id, + UserId = owner.Id, + Role = TenantRole.TenantOwner, + Status = MembershipStatus.Active + }); dbContext.TenantAuthPolicies.Add(new TenantAuthPolicy { TenantId = tenant.Id, AllowExternalStudentSelfRegistration = false }); + await EnsureTenantOwnerRoleAsync(dbContext, tenant.Id, owner.Id, cancellationToken); AddAudit(dbContext, actor, "platform.tenant.created", tenant.Id, new { tenant.Slug, tenant.Name, tenant.Status, tenant.BillingStatus }); await dbContext.SaveChangesAsync(cancellationToken); - return ToTenantItem(tenant, 0, null); + await transaction.CommitAsync(cancellationToken); + return new PlatformTenantProvisioningResult( + ToTenantItem(tenant, 0, null), + owner.Id, + ownerIdentifier, + true); }, cancellationToken); } @@ -196,10 +248,10 @@ internal sealed class PlatformAdminService( cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); var domainCount = await dbContext.TenantDomains.CountAsync(domain => domain.TenantId == tenant.Id, cancellationToken); - var expiresAt = await dbContext.TenantSubscriptions + var expiresAt = await dbContext.TenantSaasSubscriptions .Where(subscription => subscription.TenantId == tenant.Id) - .OrderByDescending(subscription => subscription.ExpiresAt) - .Select(subscription => subscription.ExpiresAt) + .OrderByDescending(subscription => subscription.CurrentPeriodEnd) + .Select(subscription => (DateTimeOffset?)subscription.CurrentPeriodEnd) .FirstOrDefaultAsync(cancellationToken); return ToTenantItem(tenant, domainCount, expiresAt); }, cancellationToken); @@ -240,208 +292,6 @@ internal sealed class PlatformAdminService( }, cancellationToken); } - public async Task GetPlansAsync( - PlatformAdminActor actor, - PlatformAdminQuery query, - CancellationToken cancellationToken = default) - { - await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken); - return await ExecuteSystemAsync("platform plan list", async dbContext => - { - var plans = dbContext.PlatformSaasPlans.AsNoTracking(); - if (!string.IsNullOrWhiteSpace(query.Status)) - { - plans = plans.Where(plan => plan.Status == ParsePlanStatus(query.Status)); - } - - return new PlatformPlanList(await plans - .OrderBy(plan => plan.SortOrder) - .ThenBy(plan => plan.Code) - .Take(Limit(query.Limit)) - .ToArrayAsync(cancellationToken)); - }, cancellationToken); - } - - public async Task UpsertSubscriptionAsync( - PlatformAdminActor actor, - UpsertPlatformSubscriptionCommand command, - CancellationToken cancellationToken = default) - { - await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken); - return await ExecuteSystemAsync("platform tenant subscription upsert", async (provider, dbContext) => - { - await RequireTenantAsync(dbContext, command.TenantId, cancellationToken); - if (!await dbContext.PlatformSaasPlans.AnyAsync(plan => plan.Code == NormalizeCode(command.PlanCode), cancellationToken)) - { - throw new PlatformAdminException("SaaS plan was not found.", "plan_not_found"); - } - - var subscription = await dbContext.TenantSubscriptions.SingleOrDefaultAsync( - item => item.TenantId == command.TenantId && item.PlanCode == NormalizeCode(command.PlanCode), - cancellationToken); - if (subscription is null) - { - subscription = new TenantSubscription { TenantId = command.TenantId }; - dbContext.TenantSubscriptions.Add(subscription); - } - - subscription.PlanCode = NormalizeCode(command.PlanCode); - subscription.Status = command.Status; - subscription.StartsAt = command.StartsAt; - subscription.ExpiresAt = command.ExpiresAt; - subscription.BillingCycle = Normalize(command.BillingCycle); - subscription.AmountCents = command.AmountCents; - subscription.Metadata = JsonObjectOrDefault(command.Metadata); - AddAudit(dbContext, actor, "platform.tenant.subscription.updated", command.TenantId, new - { - subscription.PlanCode, - subscription.Status, - subscription.ExpiresAt - }); - var moduleCodes = await dbContext.PlanModuleEntitlements.AsNoTracking() - .Where(item => item.PlanCode == subscription.PlanCode) - .Select(item => item.ModuleCode) - .Distinct() - .ToArrayAsync(cancellationToken); - if (moduleCodes.Length == 0) - { - moduleCodes = ["*"]; - } - var eventPublisher = provider.GetRequiredService(); - var version = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - foreach (var moduleCode in moduleCodes) - { - await eventPublisher.CapabilityChangedAsync( - command.TenantId, - moduleCode, - "subscription_changed", - version, - $"tenant-subscription-{subscription.Id:N}", - cancellationToken); - } - await dbContext.SaveChangesAsync(cancellationToken); - return ToSubscriptionItem(subscription); - }, cancellationToken); - } - - public async Task ReplacePlanModulesAsync( - PlatformAdminActor actor, - ReplacePlatformPlanModulesCommand command, - CancellationToken cancellationToken = default) - { - await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken); - return await ExecuteSystemAsync("platform plan module entitlements replace", async (provider, dbContext) => - { - var planCode = NormalizeCode(command.PlanCode); - var plan = await dbContext.PlatformSaasPlans.SingleOrDefaultAsync( - item => item.Code == planCode, - cancellationToken) ?? throw new PlatformAdminException("SaaS plan was not found.", "plan_not_found"); - var moduleCodes = command.ModuleCodes - .Select(NormalizeCode) - .Distinct(StringComparer.Ordinal) - .Order(StringComparer.Ordinal) - .ToArray(); - var invalidModules = moduleCodes.Where(module => !ProductModuleCatalog.Contains(module)).ToArray(); - if (invalidModules.Length > 0) - { - throw new PlatformAdminException("One or more product modules are invalid.", "product_module_invalid"); - } - - var existing = await dbContext.PlanModuleEntitlements - .Where(item => item.PlanCode == planCode) - .ToArrayAsync(cancellationToken); - var changedModules = existing.Select(item => item.ModuleCode) - .Concat(moduleCodes) - .Distinct(StringComparer.Ordinal) - .ToArray(); - dbContext.PlanModuleEntitlements.RemoveRange(existing); - dbContext.PlanModuleEntitlements.AddRange(moduleCodes.Select(moduleCode => new PlanModuleEntitlement - { - PlanCode = planCode, - ModuleCode = moduleCode, - Enabled = true - })); - AddAudit(dbContext, actor, "platform.plan.modules.replaced", plan.Id, new - { - planCode, - previousModuleCodes = existing.Select(item => item.ModuleCode).Order(StringComparer.Ordinal), - moduleCodes - }); - - var tenantIds = await dbContext.TenantSubscriptions.AsNoTracking() - .Where(item => item.PlanCode == planCode) - .Select(item => item.TenantId) - .Distinct() - .ToArrayAsync(cancellationToken); - var publisher = provider.GetRequiredService(); - var version = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - foreach (var tenantId in tenantIds) - { - foreach (var moduleCode in changedModules) - { - await publisher.CapabilityChangedAsync( - tenantId, moduleCode, "plan_entitlements_changed", version, - $"plan-modules-{plan.Id:N}", cancellationToken); - } - } - await dbContext.SaveChangesAsync(cancellationToken); - return new PlatformPlanModuleEntitlements(planCode, moduleCodes); - }, cancellationToken); - } - - public async Task UpsertTenantModuleOverrideAsync( - PlatformAdminActor actor, - UpsertPlatformTenantModuleOverrideCommand command, - CancellationToken cancellationToken = default) - { - await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformTenantManage, cancellationToken); - return await ExecuteSystemAsync("platform tenant module override upsert", async (provider, dbContext) => - { - await RequireTenantAsync(dbContext, command.TenantId, cancellationToken); - var moduleCode = NormalizeCode(command.ModuleCode); - if (!ProductModuleCatalog.Contains(moduleCode)) - { - throw new PlatformAdminException("Product module was not found.", "product_module_not_found"); - } - var reason = command.Reason.Trim(); - if (string.IsNullOrWhiteSpace(reason)) - { - throw new PlatformAdminException("Module override reason is required.", "module_override_reason_required"); - } - - var item = await dbContext.TenantModuleOverrides.SingleOrDefaultAsync( - value => value.TenantId == command.TenantId && value.ModuleCode == moduleCode, - cancellationToken); - if (item is null) - { - item = new TenantModuleOverride { TenantId = command.TenantId, ModuleCode = moduleCode }; - dbContext.TenantModuleOverrides.Add(item); - } - var previousMode = item.Mode; - item.Mode = command.Mode; - item.ExpiresAt = command.ExpiresAt; - item.Reason = reason; - AddAudit(dbContext, actor, "platform.tenant.module_override.updated", command.TenantId, new - { - moduleCode, - previousMode, - item.Mode, - item.ExpiresAt, - reason - }); - await provider.GetRequiredService().CapabilityChangedAsync( - command.TenantId, - moduleCode, - "tenant_module_override_changed", - DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), - $"tenant-module-override-{item.Id:N}", - cancellationToken); - await dbContext.SaveChangesAsync(cancellationToken); - return new PlatformTenantModuleOverrideItem( - item.TenantId, item.ModuleCode, item.Mode, item.ExpiresAt, item.Reason); - }, cancellationToken); - } - public async Task GetDomainsAsync( PlatformAdminActor actor, PlatformAdminQuery query, @@ -693,7 +543,7 @@ internal sealed class PlatformAdminService( }, cancellationToken); } - public async Task GetDunningChannelsAsync( + public async Task GetBillingDunningChannelsAsync( PlatformAdminActor actor, PlatformAdminQuery query, CancellationToken cancellationToken = default) @@ -701,7 +551,7 @@ internal sealed class PlatformAdminService( await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken); return await ExecuteSystemAsync("platform dunning channel list", async dbContext => { - var channels = dbContext.PlatformDunningNotificationChannels.AsNoTracking(); + var channels = dbContext.PlatformBillingDunningNotificationChannels.AsNoTracking(); if (!string.IsNullOrWhiteSpace(query.Search)) { var search = query.Search.Trim(); @@ -716,7 +566,7 @@ internal sealed class PlatformAdminService( channels = channels.Where(channel => channel.Enabled == enabled); } - return new PlatformDunningChannelList(await channels + return new PlatformBillingDunningChannelList(await channels .OrderByDescending(channel => channel.Enabled) .ThenBy(channel => channel.MinReminderLevel) .ThenBy(channel => channel.ChannelCode) @@ -726,9 +576,9 @@ internal sealed class PlatformAdminService( }, cancellationToken); } - public async Task UpsertDunningChannelAsync( + public async Task UpsertBillingDunningChannelAsync( PlatformAdminActor actor, - UpsertPlatformDunningChannelCommand command, + UpsertPlatformBillingDunningChannelCommand command, CancellationToken cancellationToken = default) { await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken); @@ -736,12 +586,12 @@ internal sealed class PlatformAdminService( { var code = NormalizeCode(command.ChannelCode); var channel = command.ChannelId.HasValue - ? await dbContext.PlatformDunningNotificationChannels.SingleOrDefaultAsync(item => item.Id == command.ChannelId.Value, cancellationToken) - : await dbContext.PlatformDunningNotificationChannels.SingleOrDefaultAsync(item => item.ChannelCode == code, cancellationToken); + ? await dbContext.PlatformBillingDunningNotificationChannels.SingleOrDefaultAsync(item => item.Id == command.ChannelId.Value, cancellationToken) + : await dbContext.PlatformBillingDunningNotificationChannels.SingleOrDefaultAsync(item => item.ChannelCode == code, cancellationToken); if (channel is null) { - channel = new PlatformDunningNotificationChannel { ChannelCode = code }; - dbContext.PlatformDunningNotificationChannels.Add(channel); + channel = new PlatformBillingDunningNotificationChannel { ChannelCode = code }; + dbContext.PlatformBillingDunningNotificationChannels.Add(channel); } channel.ChannelCode = code; @@ -757,7 +607,7 @@ internal sealed class PlatformAdminService( channel.TenantIds = command.TenantIds.Where(id => id != Guid.Empty).Distinct().ToArray(); channel.TimeoutSeconds = Math.Clamp(command.TimeoutSeconds, 1, 60); channel.Metadata = JsonObjectOrDefault(command.Metadata); - AddAudit(dbContext, actor, "platform.dunning_channel.upserted", channel.Id, new + AddAudit(dbContext, actor, "platform.billing_dunning_channel.upserted", channel.Id, new { channel.ChannelCode, channel.Name, @@ -771,18 +621,18 @@ internal sealed class PlatformAdminService( }, cancellationToken); } - public async Task DisableDunningChannelAsync( + public async Task DisableBillingDunningChannelAsync( PlatformAdminActor actor, - DisablePlatformDunningChannelCommand command, + DisablePlatformBillingDunningChannelCommand command, CancellationToken cancellationToken = default) { await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken); return await ExecuteSystemAsync("platform dunning channel disable", async dbContext => { - var channel = await dbContext.PlatformDunningNotificationChannels.SingleOrDefaultAsync(item => item.Id == command.ChannelId, cancellationToken) + var channel = await dbContext.PlatformBillingDunningNotificationChannels.SingleOrDefaultAsync(item => item.Id == command.ChannelId, cancellationToken) ?? throw new PlatformAdminException("Platform dunning channel was not found.", "dunning_channel_not_found"); channel.Enabled = false; - AddAudit(dbContext, actor, "platform.dunning_channel.disabled", channel.Id, new + AddAudit(dbContext, actor, "platform.billing_dunning_channel.disabled", channel.Id, new { channel.ChannelCode, command.Reason @@ -792,7 +642,7 @@ internal sealed class PlatformAdminService( }, cancellationToken); } - public async Task GetDunningEventsAsync( + public async Task GetBillingDunningEventsAsync( PlatformAdminActor actor, PlatformAdminQuery query, CancellationToken cancellationToken = default) @@ -800,13 +650,13 @@ internal sealed class PlatformAdminService( await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken); return await ExecuteSystemAsync("platform dunning event list", async dbContext => { - var events = dbContext.PlatformDunningNotificationEvents.AsNoTracking(); + var events = dbContext.PlatformBillingDunningNotificationEvents.AsNoTracking(); if (!string.IsNullOrWhiteSpace(query.Status)) { events = events.Where(item => item.Status == ParseDunningEventStatus(query.Status)); } - return new PlatformDunningEventList(await events + return new PlatformBillingDunningEventList(await events .OrderByDescending(item => item.CreatedAt) .Take(Limit(query.Limit)) .Select(item => ToDunningEventItem(item)) @@ -814,7 +664,7 @@ internal sealed class PlatformAdminService( }, cancellationToken); } - public async Task GetDunningEventDetailAsync( + public async Task GetBillingDunningEventDetailAsync( PlatformAdminActor actor, Guid eventId, CancellationToken cancellationToken = default) @@ -822,27 +672,27 @@ internal sealed class PlatformAdminService( await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken); return await ExecuteSystemAsync("platform dunning event detail", async dbContext => { - var item = await dbContext.PlatformDunningNotificationEvents.AsNoTracking() + var item = await dbContext.PlatformBillingDunningNotificationEvents.AsNoTracking() .SingleOrDefaultAsync(item => item.Id == eventId, cancellationToken) ?? throw new PlatformAdminException("Platform dunning event was not found.", "dunning_event_not_found"); return ToDunningEventItem(item); }, cancellationToken); } - public async Task RetryDunningEventAsync( + public async Task RetryBillingDunningEventAsync( PlatformAdminActor actor, - RetryPlatformDunningEventCommand command, + RetryPlatformBillingDunningEventCommand command, CancellationToken cancellationToken = default) { await AssertPlatformPermissionAsync(actor, BackendPermissions.PlatformBillingNotification, cancellationToken); return await ExecuteSystemAsync("platform dunning event retry", async dbContext => { - var item = await dbContext.PlatformDunningNotificationEvents.SingleOrDefaultAsync(item => item.Id == command.EventId, cancellationToken) + var item = await dbContext.PlatformBillingDunningNotificationEvents.SingleOrDefaultAsync(item => item.Id == command.EventId, cancellationToken) ?? throw new PlatformAdminException("Platform dunning event was not found.", "dunning_event_not_found"); - item.Status = PlatformDunningNotificationStatus.Pending; + item.Status = PlatformBillingDunningNotificationStatus.Pending; item.NextAttemptAt = DateTimeOffset.UtcNow; item.LastError = null; - AddAudit(dbContext, actor, "platform.dunning_event.retry_requested", item.Id, new + AddAudit(dbContext, actor, "platform.billing_dunning_event.retry_requested", item.Id, new { item.TenantId, item.ChannelId, @@ -941,16 +791,16 @@ internal sealed class PlatformAdminService( domain.LastCheckedAt, domain.LastFailureReason); - private static PlatformTenantSubscriptionItem ToSubscriptionItem(TenantSubscription subscription) => + private static PlatformTenantSubscriptionItem ToSubscriptionItem(TenantSaasSubscription subscription) => new( subscription.Id, subscription.TenantId, - subscription.PlanCode, + subscription.BaseOfferingVersionId, subscription.Status, subscription.StartsAt, - subscription.ExpiresAt, - subscription.BillingCycle, - subscription.AmountCents, + subscription.CurrentPeriodStart, + subscription.CurrentPeriodEnd, + subscription.CancelAtPeriodEnd, subscription.Metadata); private static TenantBillingProfileItem ToBillingProfileItem(TenantBillingProfile profile) => @@ -968,7 +818,7 @@ internal sealed class PlatformAdminService( profile.BankAccountMasked, profile.Metadata); - private static PlatformDunningChannelItem ToDunningChannelItem(PlatformDunningNotificationChannel channel) => + private static PlatformBillingDunningChannelItem ToDunningChannelItem(PlatformBillingDunningNotificationChannel channel) => new( channel.Id, channel.ChannelCode, @@ -987,7 +837,7 @@ internal sealed class PlatformAdminService( channel.CreatedAt, channel.UpdatedAt); - private static PlatformDunningEventItem ToDunningEventItem(PlatformDunningNotificationEvent item) => + private static PlatformBillingDunningEventItem ToDunningEventItem(PlatformBillingDunningNotificationEvent item) => new( item.Id, item.TenantId, @@ -1082,14 +932,91 @@ internal sealed class PlatformAdminService( private static TenantDomainStatus ParseDomainStatus(string status) => Enum.TryParse(status, true, out var value) ? value : throw InvalidStatus(status); - private static PlatformSaasPlanStatus ParsePlanStatus(string status) => - Enum.TryParse(status, true, out var value) ? value : throw InvalidStatus(status); + private static async Task EnsureTenantOwnerRoleAsync( + TikuDbContext dbContext, + Guid tenantId, + Guid ownerUserId, + CancellationToken cancellationToken) + { + var existingFeatureCodes = await dbContext.SaasFeatures + .Where(value => SaasFeatureCatalog.All.Contains(value.Code)) + .Select(value => value.Code) + .ToArrayAsync(cancellationToken); + dbContext.SaasFeatures.AddRange(SaasFeatureCatalog.All + .Except(existingFeatureCodes, StringComparer.Ordinal) + .Select((code, index) => new SaasFeature + { + Code = code, + Name = code, + Category = code.Split('.')[0], + IsCore = code == SaasFeatureCatalog.CoreBackoffice, + Status = SaasFeatureStatus.Active, + SortOrder = index * 10 + })); + + var moduleCodes = BackendPermissions.Tenant + .Select(PermissionModuleCatalog.ResolvePermissionModuleCode) + .Distinct(StringComparer.Ordinal) + .ToArray(); + var existingModuleCodes = await dbContext.PermissionModules + .Where(value => moduleCodes.Contains(value.Code)) + .Select(value => value.Code) + .ToArrayAsync(cancellationToken); + dbContext.PermissionModules.AddRange(moduleCodes + .Except(existingModuleCodes, StringComparer.Ordinal) + .Select(code => new PermissionModule + { + Code = code, + Name = code, + Area = BackendPermissionArea.Tenant, + RequiredFeatureCode = PermissionModuleCatalog.RequiredFeatures[code] + })); + + var existingPermissionCodes = await dbContext.BackendPermissions + .Where(value => BackendPermissions.Tenant.Contains(value.Code)) + .Select(value => value.Code) + .ToArrayAsync(cancellationToken); + dbContext.BackendPermissions.AddRange(BackendPermissions.Tenant + .Except(existingPermissionCodes, StringComparer.Ordinal) + .Select(code => new BackendPermission + { + Code = code, + Name = code, + Area = BackendPermissionArea.Tenant, + PermissionModuleCode = PermissionModuleCatalog.ResolvePermissionModuleCode(code), + IsSystem = true + })); + + var role = new TenantBackendRole + { + TenantId = tenantId, + Code = "tenant_owner", + Name = "租户所有者", + Status = BackendRoleStatus.Active, + IsSystem = true, + Description = "系统内置租户所有者角色", + DataScope = JsonSerializer.SerializeToElement(new { mode = "all" }) + }; + dbContext.TenantBackendRoles.Add(role); + dbContext.TenantBackendRolePermissions.AddRange(BackendPermissions.Tenant.Select(code => new TenantBackendRolePermission + { + TenantId = tenantId, + RoleId = role.Id, + PermissionCode = code + })); + dbContext.TenantBackendUserRoles.Add(new TenantBackendUserRole + { + TenantId = tenantId, + UserId = ownerUserId, + RoleId = role.Id + }); + } private static PlatformAuditAlertStatus ParseAuditAlertStatus(string status) => Enum.TryParse(status, true, out var value) ? value : throw InvalidStatus(status); - private static PlatformDunningNotificationStatus ParseDunningEventStatus(string status) => - Enum.TryParse(status, true, out var value) ? value : throw InvalidStatus(status); + private static PlatformBillingDunningNotificationStatus ParseDunningEventStatus(string status) => + Enum.TryParse(status, true, out var value) ? value : throw InvalidStatus(status); private static PlatformAdminException InvalidStatus(string status) => new($"Unsupported status '{status}'.", "invalid_status"); diff --git a/Tiku.Infrastructure/PlatformBilling/PlatformBillingAdminService.cs b/Tiku.Infrastructure/PlatformBilling/PlatformBillingAdminService.cs new file mode 100644 index 0000000..4aafe60 --- /dev/null +++ b/Tiku.Infrastructure/PlatformBilling/PlatformBillingAdminService.cs @@ -0,0 +1,147 @@ +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Tiku.Application.PlatformBilling; +using Tiku.Application.Security; +using Tiku.Domain.Operations; +using Tiku.Domain.Platform; +using Tiku.Infrastructure.Persistence; +using Tiku.Infrastructure.Messaging; + +namespace Tiku.Infrastructure.PlatformBilling; + +internal sealed class PlatformBillingAdminService( + ITenantExecutionScope tenantExecutionScope) : IPlatformBillingAdminService +{ + public Task> GetOrdersAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default) => + ExecuteAsync>("list SaaS orders", async (services, token) => + { + var db = services.GetRequiredService(); + var values = db.PlatformBillingOrders.AsNoTracking(); + if (query.TenantId.HasValue) values = values.Where(value => value.TenantId == query.TenantId); + if (!string.IsNullOrWhiteSpace(query.Status)) values = values.Where(value => value.Status == Parse(query.Status)); + return await values.OrderByDescending(value => value.CreatedAt).Take(Limit(query.Limit)).ToArrayAsync(token); + }, cancellationToken); + + public Task> GetPaymentsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default) => + ExecuteAsync>("list SaaS payments", async (services, token) => + { + var db = services.GetRequiredService(); + var values = db.PlatformBillingPayments.AsNoTracking(); + if (query.TenantId.HasValue) values = values.Where(value => value.TenantId == query.TenantId); + if (!string.IsNullOrWhiteSpace(query.Status)) values = values.Where(value => value.Status == Parse(query.Status)); + return await values.OrderByDescending(value => value.CreatedAt).Take(Limit(query.Limit)).ToArrayAsync(token); + }, cancellationToken); + + public Task> GetRefundsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default) => + ExecuteAsync>("list SaaS refunds", async (services, token) => + { + var db = services.GetRequiredService(); + var values = db.PlatformBillingRefunds.AsNoTracking(); + if (query.TenantId.HasValue) values = values.Where(value => value.TenantId == query.TenantId); + if (!string.IsNullOrWhiteSpace(query.Status)) values = values.Where(value => value.Status == Parse(query.Status)); + return await values.OrderByDescending(value => value.CreatedAt).Take(Limit(query.Limit)).ToArrayAsync(token); + }, cancellationToken); + + public Task> GetInvoicesAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default) => + ExecuteAsync>("list SaaS invoices", async (services, token) => + { + var db = services.GetRequiredService(); + var values = db.PlatformBillingInvoices.AsNoTracking(); + if (query.TenantId.HasValue) values = values.Where(value => value.TenantId == query.TenantId); + if (!string.IsNullOrWhiteSpace(query.Status)) values = values.Where(value => value.Status == Parse(query.Status)); + return await values.OrderByDescending(value => value.CreatedAt).Take(Limit(query.Limit)).ToArrayAsync(token); + }, cancellationToken); + + public Task> GetSubscriptionsAsync(SaasCatalogActor actor, PlatformBillingAdminQuery query, CancellationToken cancellationToken = default) => + ExecuteAsync>("list SaaS subscriptions", async (services, token) => + { + var db = services.GetRequiredService(); + var values = db.TenantSaasSubscriptions.AsNoTracking(); + if (query.TenantId.HasValue) values = values.Where(value => value.TenantId == query.TenantId); + if (!string.IsNullOrWhiteSpace(query.Status)) values = values.Where(value => value.Status == Parse(query.Status)); + return await values.OrderByDescending(value => value.UpdatedAt).Take(Limit(query.Limit)).ToArrayAsync(token); + }, cancellationToken); + + public Task ConfirmManualPaymentAsync( + SaasCatalogActor actor, + ConfirmManualPaymentCommand command, + CancellationToken cancellationToken = default) => + ExecuteAsync("confirm manual SaaS payment", async (services, token) => + { + var db = services.GetRequiredService(); + var payment = await db.PlatformBillingPayments.AsNoTracking().SingleOrDefaultAsync(value => value.Id == command.PaymentId, token) + ?? throw Error("Platform billing payment was not found.", "platform_billing_payment_not_found"); + if (payment.Provider != "manual") + { + throw Error("Only a manual payment can be confirmed by an operator.", "platform_billing_manual_payment_required"); + } + if (string.IsNullOrWhiteSpace(command.Reason)) + { + throw Error("Manual payment confirmation reason is required.", "platform_billing_reason_required"); + } + return await services.GetRequiredService().MarkPaidAsync( + payment.Id, + $"manual-{payment.Id:N}", + "manual_payment_confirmed", + command.ProviderTradeNo, + command.PaidAt ?? DateTimeOffset.UtcNow, + JsonSerializer.SerializeToElement(new { command.Reason }), + actor.UserId, + token); + }, cancellationToken); + + public Task UpsertFeatureOverrideAsync( + SaasCatalogActor actor, + UpsertTenantFeatureOverrideCommand command, + CancellationToken cancellationToken = default) => + ExecuteAsync("upsert tenant feature override", async (services, token) => + { + var db = services.GetRequiredService(); + var featureCode = command.FeatureCode.Trim().ToLowerInvariant(); + if (string.IsNullOrWhiteSpace(command.Reason)) throw Error("Override reason is required.", "platform_billing_reason_required"); + if (!await db.Tenants.AnyAsync(value => value.Id == command.TenantId, token)) throw Error("Tenant was not found.", "tenant_not_found"); + if (!await db.SaasFeatures.AnyAsync(value => value.Code == featureCode && !value.IsCore, token)) throw Error("SaaS feature was not found.", "saas_feature_not_found"); + var item = await db.TenantFeatureOverrides.SingleOrDefaultAsync(value => value.TenantId == command.TenantId && value.FeatureCode == featureCode, token); + if (item is null) + { + item = new TenantFeatureOverride { TenantId = command.TenantId, FeatureCode = featureCode }; + db.TenantFeatureOverrides.Add(item); + } + item.Mode = command.Mode; + item.ExpiresAt = command.ExpiresAt; + item.Reason = command.Reason.Trim(); + db.AuditLogs.Add(new AuditLog + { + TenantId = command.TenantId, + ActorUserId = actor.UserId, + Action = "platform.saas.feature_override.updated", + TargetType = "tenant_feature_overrides", + TargetId = item.Id.ToString(), + Details = JsonSerializer.SerializeToElement(new { item.FeatureCode, item.Mode, item.ExpiresAt, item.Reason }) + }); + await db.SaveChangesAsync(token); + await services.GetRequiredService().CapabilityChangedAsync( + command.TenantId, + featureCode, + "tenant_feature_override_changed", + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + $"tenant-feature-override-{item.Id:N}", + token); + return item; + }, cancellationToken); + + private Task ExecuteAsync( + string reason, + Func> operation, + CancellationToken cancellationToken) => + tenantExecutionScope.ExecuteAsync( + new SystemScopeRequest(null, SystemScopeCallerType.Platform, nameof(PlatformBillingAdminService), reason, Guid.NewGuid().ToString("N"), true), + operation, + cancellationToken); + + private static T Parse(string value) where T : struct, Enum => + Enum.TryParse(value, true, out var parsed) ? parsed : throw Error("Status is invalid.", "status_invalid"); + private static int Limit(int value) => Math.Clamp(value, 1, 200); + private static PlatformBillingException Error(string message, string code) => new(message, code); +} diff --git a/Tiku.Infrastructure/PlatformBilling/PlatformBillingNotificationService.cs b/Tiku.Infrastructure/PlatformBilling/PlatformBillingNotificationService.cs new file mode 100644 index 0000000..101b5dd --- /dev/null +++ b/Tiku.Infrastructure/PlatformBilling/PlatformBillingNotificationService.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Tiku.Application.Commerce; +using Tiku.Application.PlatformBilling; +using Tiku.Application.Security; +using Tiku.Domain.Platform; +using Tiku.Infrastructure.Persistence; + +namespace Tiku.Infrastructure.PlatformBilling; + +internal sealed class PlatformBillingNotificationService( + ITenantExecutionScope tenantExecutionScope, + IPlatformBillingPaymentGateway paymentGateway) : IPlatformBillingNotificationService +{ + public async Task ProcessAsync(PlatformBillingNotification notification, CancellationToken cancellationToken = default) + { + var parsed = await paymentGateway.ParseNotificationAsync( + notification.Provider, + new PaymentNotificationRequest(Guid.Empty, notification.Provider, notification.Headers, notification.RawBody, notification.Body), + cancellationToken); + if (!parsed.SignatureValid || !parsed.Paid) + { + throw Error("Payment notification was not a valid paid event.", "platform_billing_notification_invalid"); + } + + await tenantExecutionScope.ExecuteAsync( + new SystemScopeRequest(null, SystemScopeCallerType.Platform, nameof(PlatformBillingNotificationService), "Settle platform billing notification", parsed.EventId, true), + async (services, token) => + { + var db = services.GetRequiredService(); + var order = await db.PlatformBillingOrders.AsNoTracking().SingleOrDefaultAsync(value => value.OrderNo == parsed.OrderNo, token) + ?? throw Error("Platform billing order was not found.", "platform_billing_order_not_found"); + if (order.TotalAmountCents != parsed.AmountCents) + { + throw Error("Payment notification amount does not match the order.", "platform_billing_payment_amount_mismatch"); + } + var payment = await db.PlatformBillingPayments.AsNoTracking() + .Where(value => value.TenantId == order.TenantId && value.OrderId == order.Id && value.Provider == parsed.Provider) + .OrderByDescending(value => value.CreatedAt) + .FirstOrDefaultAsync(token) + ?? throw Error("Platform billing payment was not found.", "platform_billing_payment_not_found"); + await services.GetRequiredService().MarkPaidAsync( + payment.Id, + parsed.EventId, + parsed.EventType, + parsed.ProviderTradeNo, + parsed.PaidAt ?? DateTimeOffset.UtcNow, + parsed.RawPayload, + null, + token); + }, + cancellationToken); + } + + private static PlatformBillingException Error(string message, string code) => new(message, code); +} diff --git a/Tiku.Infrastructure/PlatformBilling/PlatformBillingPaymentGateway.cs b/Tiku.Infrastructure/PlatformBilling/PlatformBillingPaymentGateway.cs new file mode 100644 index 0000000..08d5653 --- /dev/null +++ b/Tiku.Infrastructure/PlatformBilling/PlatformBillingPaymentGateway.cs @@ -0,0 +1,91 @@ +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Tiku.Application.Commerce; +using Tiku.Application.PlatformBilling; +using Tiku.Application.Security; +using Tiku.Domain.Tenancy; +using Tiku.Infrastructure.Persistence; + +namespace Tiku.Infrastructure.PlatformBilling; + +internal sealed class PlatformBillingPaymentGateway( + ITenantExecutionScope tenantExecutionScope) : IPlatformBillingPaymentGateway +{ + public Task CreatePaymentAsync( + string provider, + CreatePaymentProviderRequest request, + CancellationToken cancellationToken = default) => + ExecuteAsync(provider, request.OrderNo, async (resolvedProvider, account, token) => + await resolvedProvider.CreatePaymentAsync(account, request, token), cancellationToken); + + public Task ParseNotificationAsync( + string provider, + PaymentNotificationRequest request, + CancellationToken cancellationToken = default) => + ExecuteAsync(provider, "payment-notification", async (resolvedProvider, account, token) => + await resolvedProvider.ParsePaymentNotificationAsync(account, request, token), cancellationToken); + + private Task ExecuteAsync( + string provider, + string correlationId, + Func> operation, + CancellationToken cancellationToken) + { + return tenantExecutionScope.ExecuteAsync( + new SystemScopeRequest( + null, + SystemScopeCallerType.Platform, + nameof(PlatformBillingPaymentGateway), + "Resolve the platform-owned SaaS billing payment account", + correlationId, + IsGlobal: true), + async (services, token) => + { + var dbContext = services.GetRequiredService(); + var platformTenantId = await dbContext.Tenants.AsNoTracking() + .Where(value => value.Mode == TenantMode.PlatformOwned) + .Select(value => value.Id) + .SingleOrDefaultAsync(token); + if (platformTenantId == Guid.Empty) + { + throw new PaymentProviderException("Platform-owned tenant is missing.", "platform_payment_owner_missing"); + } + + var normalized = NormalizeProvider(provider); + var resolvedProvider = services.GetServices() + .SingleOrDefault(value => value.Provider == normalized) + ?? throw new PaymentProviderException("Payment provider is not supported.", "payment_provider_not_supported"); + PaymentProviderAccount account; + if (normalized == PaymentProviders.Manual) + { + account = new PaymentProviderAccount( + platformTenantId, + PaymentProviders.Manual, + "PlatformCollect", + JsonDocument.Parse("{}").RootElement.Clone(), + JsonDocument.Parse("{}").RootElement.Clone()); + } + else + { + account = await services.GetRequiredService() + .GetActiveAccountAsync(platformTenantId, normalized, token); + } + + return await operation(resolvedProvider, account, token); + }, + cancellationToken); + } + + private static string NormalizeProvider(string provider) + { + var normalized = provider.Trim().ToLowerInvariant().Replace("-", "_", StringComparison.Ordinal); + return normalized switch + { + "wechat" or "wechatpay" or "wxpay" or "wx_pay" => PaymentProviders.WechatPay, + "ali_pay" => PaymentProviders.Alipay, + PaymentProviders.WechatPay or PaymentProviders.Alipay or PaymentProviders.Manual => normalized, + _ => throw new PaymentProviderException("Payment provider is not supported.", "payment_provider_not_supported") + }; + } +} diff --git a/Tiku.Infrastructure/PlatformBilling/PlatformBillingSettlementService.cs b/Tiku.Infrastructure/PlatformBilling/PlatformBillingSettlementService.cs new file mode 100644 index 0000000..454c49e --- /dev/null +++ b/Tiku.Infrastructure/PlatformBilling/PlatformBillingSettlementService.cs @@ -0,0 +1,268 @@ +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Tiku.Application.PlatformBilling; +using Tiku.Application.Security; +using Tiku.Domain.Operations; +using Tiku.Domain.Platform; +using Tiku.Domain.Common; +using Tiku.Domain.Tenancy; +using Tiku.Infrastructure.Persistence; +using Tiku.Infrastructure.Messaging; + +namespace Tiku.Infrastructure.PlatformBilling; + +internal sealed class PlatformBillingSettlementService( + TikuDbContext dbContext, + ISecurityEventPublisher securityEventPublisher) : IPlatformBillingSettlementService +{ + public async Task MarkPaidAsync( + Guid paymentId, + string providerEventId, + string eventType, + string? providerTradeNo, + DateTimeOffset paidAt, + JsonElement payload, + Guid? actorUserId, + CancellationToken cancellationToken = default) + { + var payment = await dbContext.PlatformBillingPayments.SingleOrDefaultAsync(value => value.Id == paymentId, cancellationToken) + ?? throw Error("Platform billing payment was not found.", "platform_billing_payment_not_found"); + if (await dbContext.PlatformBillingPaymentEvents.AnyAsync(value => + value.TenantId == payment.TenantId && + value.Provider == payment.Provider && + value.ProviderEventId == providerEventId, cancellationToken)) + { + return payment; + } + + var order = await dbContext.PlatformBillingOrders.SingleAsync(value => + value.TenantId == payment.TenantId && value.Id == payment.OrderId, cancellationToken); + if (payment.AmountCents != order.TotalAmountCents) + { + throw Error("Payment amount does not match the order.", "platform_billing_payment_amount_mismatch"); + } + + dbContext.PlatformBillingPaymentEvents.Add(new PlatformBillingPaymentEvent + { + TenantId = payment.TenantId, + PaymentId = payment.Id, + Provider = payment.Provider, + ProviderEventId = providerEventId, + EventType = eventType, + Payload = payload.ValueKind == JsonValueKind.Undefined ? JsonDefaults.Object() : payload.Clone() + }); + if (payment.Status == PlatformBillingPaymentStatus.Succeeded) + { + await dbContext.SaveChangesAsync(cancellationToken); + return payment; + } + if (payment.Status != PlatformBillingPaymentStatus.Pending || order.Status != PlatformBillingOrderStatus.PendingPayment) + { + throw Error("Payment or order status does not allow settlement.", "platform_billing_payment_status_invalid"); + } + + payment.Status = PlatformBillingPaymentStatus.Succeeded; + payment.ProviderTradeNo = providerTradeNo; + payment.PaidAt = paidAt; + order.Status = PlatformBillingOrderStatus.Paid; + order.PaidAt = paidAt; + var tenant = await dbContext.Tenants.SingleAsync(value => value.Id == order.TenantId, cancellationToken); + tenant.BillingStatus = BillingStatus.Active; + + var orderItems = await dbContext.PlatformBillingOrderItems.AsNoTracking() + .Where(value => value.TenantId == order.TenantId && value.OrderId == order.Id) + .ToArrayAsync(cancellationToken); + var baseItem = orderItems.SingleOrDefault(value => value.ItemType == PlatformBillingItemType.BasePlan) + ?? throw Error("Order base plan item is missing.", "platform_billing_base_plan_missing"); + var baseVersion = await dbContext.SaasOfferingVersions.AsNoTracking() + .SingleAsync(value => value.Id == baseItem.OfferingVersionId, cancellationToken); + var subscription = await dbContext.TenantSaasSubscriptions + .OrderByDescending(value => value.UpdatedAt) + .FirstOrDefaultAsync(value => value.TenantId == order.TenantId, cancellationToken); + var oldFeatures = subscription is null + ? [] + : await LoadSubscriptionFeaturesAsync(subscription, cancellationToken); + + var now = paidAt; + if (subscription is null) + { + subscription = new TenantSaasSubscription + { + TenantId = order.TenantId, + BaseOfferingVersionId = baseVersion.Id, + Status = TenantSaasSubscriptionStatus.Active, + StartsAt = now, + CurrentPeriodStart = now, + CurrentPeriodEnd = AddCycle(now, baseVersion.BillingCycle) + }; + dbContext.TenantSaasSubscriptions.Add(subscription); + } + else if (order.Purpose == PlatformBillingOrderPurpose.Renewal) + { + var periodStart = subscription.CurrentPeriodEnd > now ? subscription.CurrentPeriodEnd : now; + subscription.Status = TenantSaasSubscriptionStatus.Active; + subscription.CurrentPeriodStart = subscription.CurrentPeriodEnd > now + ? subscription.CurrentPeriodStart + : now; + subscription.CurrentPeriodEnd = AddCycle(periodStart, baseVersion.BillingCycle); + subscription.CancelAtPeriodEnd = false; + subscription.CancelledAt = null; + } + else if (order.Purpose == PlatformBillingOrderPurpose.Downgrade) + { + subscription.ScheduledBaseOfferingVersionId = baseVersion.Id; + } + else + { + subscription.BaseOfferingVersionId = baseVersion.Id; + subscription.ScheduledBaseOfferingVersionId = null; + subscription.Status = TenantSaasSubscriptionStatus.Active; + subscription.CancelAtPeriodEnd = false; + subscription.CancelledAt = null; + if (subscription.CurrentPeriodEnd <= now) + { + subscription.CurrentPeriodStart = now; + subscription.CurrentPeriodEnd = AddCycle(now, baseVersion.BillingCycle); + } + } + + if (order.Purpose != PlatformBillingOrderPurpose.Downgrade) + { + var existingItems = await dbContext.TenantSaasSubscriptionItems + .Where(value => value.TenantId == order.TenantId && value.SubscriptionId == subscription.Id && + value.Status == TenantSaasSubscriptionItemStatus.Active) + .ToArrayAsync(cancellationToken); + foreach (var existing in existingItems) + { + existing.Status = TenantSaasSubscriptionItemStatus.Cancelled; + existing.EndsAt = now > existing.StartsAt ? now : existing.StartsAt.AddTicks(1); + } + dbContext.TenantSaasSubscriptionItems.AddRange(orderItems.Select(item => new TenantSaasSubscriptionItem + { + TenantId = order.TenantId, + SubscriptionId = subscription.Id, + OfferingVersionId = item.OfferingVersionId, + SourceOrderItemId = item.Id, + ItemType = item.ItemType == PlatformBillingItemType.BasePlan + ? TenantSaasSubscriptionItemType.BasePlan + : TenantSaasSubscriptionItemType.AddOn, + Status = TenantSaasSubscriptionItemStatus.Active, + StartsAt = now, + EndsAt = subscription.CurrentPeriodEnd + })); + } + else + { + var existingScheduledItems = await dbContext.TenantSaasSubscriptionItems + .Where(value => value.TenantId == order.TenantId && + value.SubscriptionId == subscription.Id && + value.Status == TenantSaasSubscriptionItemStatus.Scheduled) + .ToArrayAsync(cancellationToken); + foreach (var existing in existingScheduledItems) + { + existing.Status = TenantSaasSubscriptionItemStatus.Cancelled; + existing.EndsAt = now > existing.StartsAt ? now : existing.StartsAt.AddTicks(1); + } + dbContext.TenantSaasSubscriptionItems.AddRange(orderItems.Select(item => new TenantSaasSubscriptionItem + { + TenantId = order.TenantId, + SubscriptionId = subscription.Id, + OfferingVersionId = item.OfferingVersionId, + SourceOrderItemId = item.Id, + ItemType = item.ItemType == PlatformBillingItemType.BasePlan + ? TenantSaasSubscriptionItemType.BasePlan + : TenantSaasSubscriptionItemType.AddOn, + Status = TenantSaasSubscriptionItemStatus.Scheduled, + StartsAt = subscription.CurrentPeriodEnd, + EndsAt = AddCycle(subscription.CurrentPeriodEnd, baseVersion.BillingCycle) + })); + } + + var invoice = new PlatformBillingInvoice + { + TenantId = order.TenantId, + OrderId = order.Id, + InvoiceNo = $"SI{now:yyyyMMddHHmmss}{Random.Shared.Next(1000, 9999)}", + Status = PlatformBillingInvoiceStatus.Paid, + TotalAmountCents = order.TotalAmountCents, + Currency = order.Currency, + IssuedAt = now, + PaidAt = now, + BillingProfileSnapshot = await LoadBillingProfileSnapshotAsync(order.TenantId, cancellationToken) + }; + dbContext.PlatformBillingInvoices.Add(invoice); + dbContext.AuditLogs.Add(new AuditLog + { + TenantId = order.TenantId, + ActorUserId = actorUserId, + Action = "platform_billing.payment.settled", + TargetType = "platform_billing_orders", + TargetId = order.Id.ToString(), + Details = JsonSerializer.SerializeToElement(new { order.OrderNo, payment.PaymentNo, payment.Provider, order.Purpose }) + }); + await dbContext.SaveChangesAsync(cancellationToken); + + var newFeatures = await LoadSubscriptionFeaturesAsync(subscription, cancellationToken); + var changedFeatures = oldFeatures.Concat(newFeatures).Distinct(StringComparer.Ordinal).ToArray(); + var version = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + foreach (var featureCode in changedFeatures) + { + await securityEventPublisher.CapabilityChangedAsync( + order.TenantId, + featureCode, + "saas_subscription_changed", + version, + $"platform-billing-order-{order.Id:N}", + cancellationToken); + } + + return payment; + } + + private async Task LoadSubscriptionFeaturesAsync(TenantSaasSubscription subscription, CancellationToken cancellationToken) + { + var versionIds = await dbContext.TenantSaasSubscriptionItems.AsNoTracking() + .Where(value => value.TenantId == subscription.TenantId && value.SubscriptionId == subscription.Id && + value.Status == TenantSaasSubscriptionItemStatus.Active) + .Select(value => value.OfferingVersionId) + .ToArrayAsync(cancellationToken); + if (!versionIds.Contains(subscription.BaseOfferingVersionId)) + { + versionIds = [.. versionIds, subscription.BaseOfferingVersionId]; + } + return await dbContext.SaasOfferingVersionFeatures.AsNoTracking() + .Where(value => versionIds.Contains(value.OfferingVersionId)) + .Select(value => value.FeatureCode) + .Distinct() + .ToArrayAsync(cancellationToken); + } + + private async Task LoadBillingProfileSnapshotAsync(Guid tenantId, CancellationToken cancellationToken) + { + var profile = await dbContext.TenantBillingProfiles.AsNoTracking() + .SingleOrDefaultAsync(value => value.TenantId == tenantId, cancellationToken); + return profile is null + ? JsonDefaults.Object() + : JsonSerializer.SerializeToElement(new + { + profile.BillingName, + profile.TaxId, + profile.ContactName, + profile.ContactPhone, + profile.ContactEmail, + profile.InvoiceTitle, + profile.InvoiceType + }); + } + + private static DateTimeOffset AddCycle(DateTimeOffset start, PlatformBillingCycle cycle) => cycle switch + { + PlatformBillingCycle.Monthly => start.AddMonths(1), + PlatformBillingCycle.Quarterly => start.AddMonths(3), + PlatformBillingCycle.Yearly => start.AddYears(1), + PlatformBillingCycle.OneTime => start.AddYears(100), + _ => throw new ArgumentOutOfRangeException(nameof(cycle)) + }; + + private static PlatformBillingException Error(string message, string code) => new(message, code); +} diff --git a/Tiku.Infrastructure/PlatformBilling/SaasCatalogAdminService.cs b/Tiku.Infrastructure/PlatformBilling/SaasCatalogAdminService.cs new file mode 100644 index 0000000..2f0a71a --- /dev/null +++ b/Tiku.Infrastructure/PlatformBilling/SaasCatalogAdminService.cs @@ -0,0 +1,350 @@ +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Tiku.Application.Backoffice; +using Tiku.Application.PlatformBilling; +using Tiku.Application.Security; +using Tiku.Domain.Platform; +using Tiku.Infrastructure.Persistence; + +namespace Tiku.Infrastructure.PlatformBilling; + +internal sealed class SaasCatalogAdminService( + TikuDbContext dbContext, + IOperationAuditService auditService) : ISaasCatalogAdminService +{ + public async Task GetCatalogAsync( + SaasCatalogActor actor, + CancellationToken cancellationToken = default) + { + var features = await dbContext.SaasFeatures.AsNoTracking() + .OrderBy(value => value.SortOrder).ThenBy(value => value.Code) + .ToArrayAsync(cancellationToken); + var limits = await dbContext.SaasFeatureLimitDefinitions.AsNoTracking() + .OrderBy(value => value.FeatureCode).ThenBy(value => value.MetricCode) + .ToArrayAsync(cancellationToken); + var offerings = await dbContext.SaasOfferings.AsNoTracking() + .OrderBy(value => value.SortOrder).ThenBy(value => value.Code) + .ToArrayAsync(cancellationToken); + var versions = await LoadVersionsAsync(null, cancellationToken); + return new SaasCatalogSnapshot(features, limits, offerings, versions); + } + + public async Task UpsertLimitDefinitionAsync( + SaasCatalogActor actor, + UpsertSaasFeatureLimitCommand command, + CancellationToken cancellationToken = default) + { + var metricCode = Normalize(command.MetricCode); + var featureCode = Normalize(command.FeatureCode); + if (command.WarningPercent != 80 || !command.IsHardLimit) + { + throw Error("Phase nine supports hard limits with an 80 percent warning threshold.", "saas_feature_limit_policy_invalid"); + } + if (!await dbContext.SaasFeatures.AnyAsync(value => + value.Code == featureCode && value.Status == SaasFeatureStatus.Active, cancellationToken)) + { + throw Error("SaaS feature was not found or active.", "saas_feature_not_found"); + } + + var item = command.Id.HasValue + ? await dbContext.SaasFeatureLimitDefinitions.SingleOrDefaultAsync(value => value.Id == command.Id, cancellationToken) + : await dbContext.SaasFeatureLimitDefinitions.SingleOrDefaultAsync(value => value.MetricCode == metricCode, cancellationToken); + if (item is null) + { + item = new SaasFeatureLimitDefinition { MetricCode = metricCode }; + dbContext.SaasFeatureLimitDefinitions.Add(item); + } + else if (await dbContext.SaasOfferingVersionLimits.AnyAsync(value => value.MetricCode == item.MetricCode, cancellationToken) && + (!string.Equals(item.MetricCode, metricCode, StringComparison.Ordinal) || + !string.Equals(item.FeatureCode, featureCode, StringComparison.Ordinal) || + item.Kind != command.Kind)) + { + throw Error("A limit definition referenced by offering versions cannot change identity or kind.", "saas_feature_limit_locked"); + } + + item.MetricCode = metricCode; + item.FeatureCode = featureCode; + item.Name = Required(command.Name, "name"); + item.Unit = Required(command.Unit, "unit"); + item.Kind = command.Kind; + item.WarningPercent = command.WarningPercent; + item.IsHardLimit = command.IsHardLimit; + await dbContext.SaveChangesAsync(cancellationToken); + await AuditAsync(actor, "platform.saas.feature_limit.upserted", "saas_feature_limit_definitions", item.Id, + new { item.MetricCode, item.FeatureCode, item.Kind }, cancellationToken); + return item; + } + + public async Task UpsertFeatureAsync( + SaasCatalogActor actor, + UpsertSaasFeatureCommand command, + CancellationToken cancellationToken = default) + { + var code = Normalize(command.Code); + if (!SaasFeatureCatalog.All.Contains(code)) + { + throw Error("Feature code is not part of the application catalog.", "saas_feature_code_unknown"); + } + if (command.ReferencePriceCents < 0) + { + throw Error("Reference price cannot be negative.", "saas_feature_price_invalid"); + } + + var item = command.Id.HasValue + ? await dbContext.SaasFeatures.SingleOrDefaultAsync(value => value.Id == command.Id, cancellationToken) + : await dbContext.SaasFeatures.SingleOrDefaultAsync(value => value.Code == code, cancellationToken); + if (item is null) + { + item = new SaasFeature { Code = code }; + dbContext.SaasFeatures.Add(item); + } + if (item.IsCore) + { + throw Error("Core features cannot be changed through the product catalog.", "saas_core_feature_locked"); + } + + item.Code = code; + item.Name = Required(command.Name, "name"); + item.Category = Required(command.Category, "category"); + item.Description = Clean(command.Description); + item.ReferencePriceCents = command.ReferencePriceCents; + item.Currency = NormalizeCurrency(command.Currency); + item.Status = command.Status; + item.SortOrder = command.SortOrder; + await dbContext.SaveChangesAsync(cancellationToken); + await AuditAsync(actor, "platform.saas.feature.upserted", "saas_features", item.Id, new { item.Code, item.Status }, cancellationToken); + return item; + } + + public async Task UpsertOfferingAsync( + SaasCatalogActor actor, + UpsertSaasOfferingCommand command, + CancellationToken cancellationToken = default) + { + var code = Normalize(command.Code); + var item = command.Id.HasValue + ? await dbContext.SaasOfferings.SingleOrDefaultAsync(value => value.Id == command.Id, cancellationToken) + : await dbContext.SaasOfferings.SingleOrDefaultAsync(value => value.Code == code, cancellationToken); + if (item is null) + { + item = new SaasOffering { Code = code }; + dbContext.SaasOfferings.Add(item); + } + else if (item.Type != command.Type && await dbContext.SaasOfferingVersions.AnyAsync(value => value.OfferingId == item.Id, cancellationToken)) + { + throw Error("Offering type cannot change after versions exist.", "saas_offering_type_locked"); + } + + item.Code = code; + item.Name = Required(command.Name, "name"); + item.Type = command.Type; + item.Status = command.Status; + item.Description = Clean(command.Description); + item.SortOrder = command.SortOrder; + await dbContext.SaveChangesAsync(cancellationToken); + await AuditAsync(actor, "platform.saas.offering.upserted", "saas_offerings", item.Id, new { item.Code, item.Type, item.Status }, cancellationToken); + return item; + } + + public async Task UpsertDraftVersionAsync( + SaasCatalogActor actor, + UpsertSaasOfferingVersionCommand command, + CancellationToken cancellationToken = default) + { + var offering = await dbContext.SaasOfferings.SingleOrDefaultAsync(value => value.Id == command.OfferingId, cancellationToken) + ?? throw Error("SaaS offering was not found.", "saas_offering_not_found"); + if (command.AmountCents < 0 || command.OriginalAmountCents < command.AmountCents) + { + throw Error("Offering version price is invalid.", "saas_offering_price_invalid"); + } + + var featureCodes = command.FeatureCodes.Select(Normalize).Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal).ToArray(); + if (offering.Type == SaasOfferingType.BasePlan && featureCodes.Length == 0) + { + throw Error("A base plan must contain at least one sellable feature.", "saas_base_plan_features_required"); + } + var validFeatures = await dbContext.SaasFeatures.CountAsync(value => + featureCodes.Contains(value.Code) && value.Status == SaasFeatureStatus.Active && !value.IsCore, cancellationToken); + if (validFeatures != featureCodes.Length) + { + throw Error("One or more features are unavailable.", "saas_feature_unavailable"); + } + + var metricCodes = command.Limits.Keys.Select(Normalize).Distinct(StringComparer.Ordinal).ToArray(); + var limitDefinitions = await dbContext.SaasFeatureLimitDefinitions.AsNoTracking() + .Where(value => metricCodes.Contains(value.MetricCode)) + .ToArrayAsync(cancellationToken); + if (command.Limits.Any(value => value.Value < 0) || + limitDefinitions.Length != metricCodes.Length || + limitDefinitions.Any(value => !featureCodes.Contains(value.FeatureCode))) + { + throw Error("One or more feature limits are invalid.", "saas_feature_limit_invalid"); + } + + SaasOfferingVersion version; + if (command.Id.HasValue) + { + version = await dbContext.SaasOfferingVersions.SingleOrDefaultAsync(value => value.Id == command.Id, cancellationToken) + ?? throw Error("Offering version was not found.", "saas_offering_version_not_found"); + if (version.Status != SaasOfferingVersionStatus.Draft) + { + throw Error("Published offering versions are immutable.", "saas_offering_version_immutable"); + } + if (version.OfferingId != command.OfferingId) + { + throw Error("Offering version cannot move to another offering.", "saas_offering_version_offering_locked"); + } + } + else + { + var nextVersion = (await dbContext.SaasOfferingVersions + .Where(value => value.OfferingId == command.OfferingId) + .MaxAsync(value => (int?)value.Version, cancellationToken) ?? 0) + 1; + version = new SaasOfferingVersion { OfferingId = command.OfferingId, Version = nextVersion }; + dbContext.SaasOfferingVersions.Add(version); + } + + version.BillingCycle = command.BillingCycle; + version.OriginalAmountCents = command.OriginalAmountCents; + version.AmountCents = command.AmountCents; + version.Currency = NormalizeCurrency(command.Currency); + version.EffectiveAt = command.EffectiveAt; + version.Metadata = ObjectOrEmpty(command.Metadata); + + await dbContext.SaasOfferingVersionFeatures.Where(value => value.OfferingVersionId == version.Id).ExecuteDeleteAsync(cancellationToken); + await dbContext.SaasOfferingVersionLimits.Where(value => value.OfferingVersionId == version.Id).ExecuteDeleteAsync(cancellationToken); + dbContext.SaasOfferingVersionFeatures.AddRange(featureCodes.Select(code => new SaasOfferingVersionFeature + { + OfferingVersionId = version.Id, + FeatureCode = code + })); + dbContext.SaasOfferingVersionLimits.AddRange(command.Limits.Select(value => new SaasOfferingVersionLimit + { + OfferingVersionId = version.Id, + MetricCode = Normalize(value.Key), + LimitValue = value.Value + })); + await dbContext.SaveChangesAsync(cancellationToken); + await AuditAsync(actor, "platform.saas.offering_version.saved", "saas_offering_versions", version.Id, new { offering.Code, version.Version }, cancellationToken); + return (await LoadVersionsAsync(version.Id, cancellationToken)).Single(); + } + + public async Task PublishVersionAsync( + SaasCatalogActor actor, + Guid versionId, + CancellationToken cancellationToken = default) + { + var version = await RequireDraftAsync(versionId, cancellationToken); + var hasFeatures = await dbContext.SaasOfferingVersionFeatures.AnyAsync(value => value.OfferingVersionId == versionId, cancellationToken); + if (!hasFeatures) + { + throw Error("Offering version has no features.", "saas_offering_version_empty"); + } + + version.Status = SaasOfferingVersionStatus.Published; + version.PublishedAt = DateTimeOffset.UtcNow; + version.EffectiveAt ??= version.PublishedAt; + var offering = await dbContext.SaasOfferings.SingleAsync(value => value.Id == version.OfferingId, cancellationToken); + offering.Status = SaasOfferingStatus.Active; + await dbContext.SaveChangesAsync(cancellationToken); + await AuditAsync(actor, "platform.saas.offering_version.published", "saas_offering_versions", version.Id, new { version.Version }, cancellationToken); + return (await LoadVersionsAsync(version.Id, cancellationToken)).Single(); + } + + public async Task CloneVersionAsync( + SaasCatalogActor actor, + Guid versionId, + CancellationToken cancellationToken = default) + { + var source = await dbContext.SaasOfferingVersions.AsNoTracking().SingleOrDefaultAsync(value => value.Id == versionId, cancellationToken) + ?? throw Error("Offering version was not found.", "saas_offering_version_not_found"); + var features = await dbContext.SaasOfferingVersionFeatures.AsNoTracking().Where(value => value.OfferingVersionId == versionId).Select(value => value.FeatureCode).ToArrayAsync(cancellationToken); + var limits = await dbContext.SaasOfferingVersionLimits.AsNoTracking().Where(value => value.OfferingVersionId == versionId).ToDictionaryAsync(value => value.MetricCode, value => value.LimitValue, cancellationToken); + return await UpsertDraftVersionAsync(actor, new UpsertSaasOfferingVersionCommand( + null, + source.OfferingId, + source.BillingCycle, + source.OriginalAmountCents, + source.AmountCents, + source.Currency, + null, + features, + limits, + source.Metadata), cancellationToken); + } + + public async Task RetireVersionAsync( + SaasCatalogActor actor, + Guid versionId, + CancellationToken cancellationToken = default) + { + var version = await dbContext.SaasOfferingVersions.SingleOrDefaultAsync(value => value.Id == versionId, cancellationToken) + ?? throw Error("Offering version was not found.", "saas_offering_version_not_found"); + if (version.Status != SaasOfferingVersionStatus.Published) + { + throw Error("Only a published offering version can be retired.", "saas_offering_version_status_invalid"); + } + version.Status = SaasOfferingVersionStatus.Retired; + version.RetiredAt = DateTimeOffset.UtcNow; + await dbContext.SaveChangesAsync(cancellationToken); + await AuditAsync(actor, "platform.saas.offering_version.retired", "saas_offering_versions", version.Id, new { version.Version }, cancellationToken); + return (await LoadVersionsAsync(version.Id, cancellationToken)).Single(); + } + + private async Task LoadVersionsAsync(Guid? versionId, CancellationToken cancellationToken) + { + var query = from version in dbContext.SaasOfferingVersions.AsNoTracking() + join offering in dbContext.SaasOfferings.AsNoTracking() on version.OfferingId equals offering.Id + select new { Version = version, Offering = offering }; + if (versionId.HasValue) + { + query = query.Where(value => value.Version.Id == versionId); + } + var rows = await query.OrderBy(value => value.Offering.SortOrder).ThenBy(value => value.Offering.Code).ThenByDescending(value => value.Version.Version).ToArrayAsync(cancellationToken); + var ids = rows.Select(value => value.Version.Id).ToArray(); + var features = await dbContext.SaasOfferingVersionFeatures.AsNoTracking().Where(value => ids.Contains(value.OfferingVersionId)).ToArrayAsync(cancellationToken); + var limits = await dbContext.SaasOfferingVersionLimits.AsNoTracking().Where(value => ids.Contains(value.OfferingVersionId)).ToArrayAsync(cancellationToken); + return rows.Select(row => new SaasOfferingVersionItem( + row.Version.Id, + row.Offering.Id, + row.Offering.Code, + row.Offering.Name, + row.Offering.Type, + row.Version.Version, + row.Version.Status, + row.Version.BillingCycle, + row.Version.OriginalAmountCents, + row.Version.AmountCents, + row.Version.Currency, + row.Version.EffectiveAt, + row.Version.PublishedAt, + features.Where(value => value.OfferingVersionId == row.Version.Id).Select(value => value.FeatureCode).Order(StringComparer.Ordinal).ToArray(), + limits.Where(value => value.OfferingVersionId == row.Version.Id).ToDictionary(value => value.MetricCode, value => value.LimitValue, StringComparer.Ordinal))) + .ToArray(); + } + + private async Task RequireDraftAsync(Guid versionId, CancellationToken cancellationToken) + { + var version = await dbContext.SaasOfferingVersions.SingleOrDefaultAsync(value => value.Id == versionId, cancellationToken) + ?? throw Error("Offering version was not found.", "saas_offering_version_not_found"); + return version.Status == SaasOfferingVersionStatus.Draft + ? version + : throw Error("Published offering versions are immutable.", "saas_offering_version_immutable"); + } + + private Task AuditAsync(SaasCatalogActor actor, string action, string targetType, Guid targetId, object details, CancellationToken cancellationToken) => + auditService.WriteAsync(new BackofficeOperationAuditCommand( + null, + actor.UserId, + action, + targetType, + targetId.ToString(), + JsonSerializer.SerializeToElement(details)), cancellationToken); + + private static PlatformBillingException Error(string message, string code) => new(message, code); + private static string Normalize(string value) => Required(value, "code").ToLowerInvariant(); + private static string NormalizeCurrency(string value) => Required(value, "currency").ToUpperInvariant(); + private static string Required(string value, string field) => string.IsNullOrWhiteSpace(value) ? throw Error($"{field} is required.", "required_field") : value.Trim(); + private static string? Clean(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); + private static JsonElement ObjectOrEmpty(JsonElement value) => value.ValueKind == JsonValueKind.Object ? value.Clone() : JsonDocument.Parse("{}").RootElement.Clone(); +} diff --git a/Tiku.Infrastructure/PlatformBilling/SaasSubscriptionLifecycleService.cs b/Tiku.Infrastructure/PlatformBilling/SaasSubscriptionLifecycleService.cs new file mode 100644 index 0000000..6803deb --- /dev/null +++ b/Tiku.Infrastructure/PlatformBilling/SaasSubscriptionLifecycleService.cs @@ -0,0 +1,272 @@ +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Tiku.Application.PlatformBilling; +using Tiku.Application.Security; +using Tiku.Application.Tenancy; +using Tiku.Domain.Operations; +using Tiku.Domain.Platform; +using Tiku.Domain.Tenancy; +using Tiku.Infrastructure.Messaging; +using Tiku.Infrastructure.Persistence; + +namespace Tiku.Infrastructure.PlatformBilling; + +internal sealed class SaasSubscriptionLifecycleService( + TikuDbContext directoryDbContext, + ITenantContext tenantContext, + ITenantExecutionScope tenantExecutionScope, + IOptions options) : ISaasSubscriptionLifecycleService +{ + private readonly SaasSubscriptionLifecycleOptions options = options.Value; + + public async Task ProcessDueAsync( + DateTimeOffset? asOf = null, + CancellationToken cancellationToken = default) + { + if (!options.Enabled) + { + return 0; + } + if (!tenantContext.IsSystem || tenantContext.TenantId.HasValue) + { + throw new InvalidOperationException( + "SaaS subscription lifecycle discovery requires a global system context."); + } + + var effectiveAt = asOf ?? DateTimeOffset.UtcNow; + var tenantIds = await directoryDbContext.TenantSaasSubscriptions.AsNoTracking() + .Where(subscription => + subscription.CurrentPeriodEnd <= effectiveAt && + (subscription.Status == TenantSaasSubscriptionStatus.Trial || + subscription.Status == TenantSaasSubscriptionStatus.Active || + subscription.Status == TenantSaasSubscriptionStatus.PastDue)) + .OrderBy(subscription => subscription.CurrentPeriodEnd) + .ThenBy(subscription => subscription.TenantId) + .Select(subscription => subscription.TenantId) + .Distinct() + .Take(Math.Clamp(options.BatchSize, 1, 1000)) + .ToArrayAsync(cancellationToken); + + var changed = 0; + foreach (var tenantId in tenantIds) + { + var wasChanged = await tenantExecutionScope.ExecuteAsync( + new SystemScopeRequest( + tenantId, + SystemScopeCallerType.Worker, + nameof(SaasSubscriptionLifecycleService), + "Apply due SaaS subscription lifecycle transition", + $"saas-subscription-lifecycle-{tenantId:N}-{effectiveAt:yyyyMMddHHmmss}"), + (provider, token) => ProcessTenantAsync(provider, tenantId, effectiveAt, token), + cancellationToken); + if (wasChanged) + { + changed++; + } + } + + return changed; + } + + private async Task ProcessTenantAsync( + IServiceProvider services, + Guid tenantId, + DateTimeOffset asOf, + CancellationToken cancellationToken) + { + var dbContext = services.GetRequiredService(); + var subscription = await dbContext.TenantSaasSubscriptions + .OrderByDescending(value => value.UpdatedAt) + .FirstOrDefaultAsync(value => + value.TenantId == tenantId && + value.CurrentPeriodEnd <= asOf && + (value.Status == TenantSaasSubscriptionStatus.Trial || + value.Status == TenantSaasSubscriptionStatus.Active || + value.Status == TenantSaasSubscriptionStatus.PastDue), + cancellationToken); + if (subscription is null) + { + return false; + } + + var previousStatus = subscription.Status; + var previousBaseVersionId = subscription.BaseOfferingVersionId; + var oldFeatures = await LoadFeaturesAsync(dbContext, subscription, cancellationToken); + var items = await dbContext.TenantSaasSubscriptionItems + .Where(value => value.TenantId == tenantId && value.SubscriptionId == subscription.Id) + .ToArrayAsync(cancellationToken); + var tenant = await dbContext.Tenants.SingleAsync(value => value.Id == tenantId, cancellationToken); + + string transition; + if (subscription.CancelAtPeriodEnd) + { + transition = "cancelled_at_period_end"; + subscription.Status = TenantSaasSubscriptionStatus.Cancelled; + subscription.CancelledAt ??= asOf; + subscription.ScheduledBaseOfferingVersionId = null; + tenant.BillingStatus = BillingStatus.Cancelled; + EndItems(items, subscription.CurrentPeriodEnd, cancelScheduled: true); + } + else if (subscription.ScheduledBaseOfferingVersionId is { } scheduledVersionId) + { + var scheduledBase = items.SingleOrDefault(value => + value.ItemType == TenantSaasSubscriptionItemType.BasePlan && + value.Status == TenantSaasSubscriptionItemStatus.Scheduled && + value.OfferingVersionId == scheduledVersionId); + if (scheduledBase is null) + { + var current = await dbContext.TenantSaasSubscriptions.AsNoTracking() + .SingleAsync(value => value.TenantId == tenantId && value.Id == subscription.Id, cancellationToken); + if (current.ScheduledBaseOfferingVersionId is null && + current.BaseOfferingVersionId == scheduledVersionId && + current.LifecycleVersion != subscription.LifecycleVersion) + { + dbContext.ChangeTracker.Clear(); + return false; + } + throw new InvalidOperationException( + $"Scheduled base offering version '{scheduledVersionId}' has no matching subscription item."); + } + + transition = "scheduled_plan_activated"; + var nextPeriodStart = subscription.CurrentPeriodEnd; + EndItems(items.Where(value => value.Status == TenantSaasSubscriptionItemStatus.Active), nextPeriodStart, cancelScheduled: false); + var scheduledItems = items.Where(value => value.Status == TenantSaasSubscriptionItemStatus.Scheduled).ToArray(); + foreach (var item in scheduledItems) + { + item.Status = TenantSaasSubscriptionItemStatus.Active; + item.StartsAt = nextPeriodStart; + } + + subscription.BaseOfferingVersionId = scheduledVersionId; + subscription.ScheduledBaseOfferingVersionId = null; + subscription.Status = TenantSaasSubscriptionStatus.Active; + subscription.CurrentPeriodStart = nextPeriodStart; + subscription.CurrentPeriodEnd = scheduledBase.EndsAt; + subscription.CancelAtPeriodEnd = false; + subscription.CancelledAt = null; + tenant.BillingStatus = BillingStatus.Active; + } + else if (subscription.Status == TenantSaasSubscriptionStatus.Trial) + { + transition = "trial_expired"; + subscription.Status = TenantSaasSubscriptionStatus.Expired; + subscription.CancelledAt ??= asOf; + tenant.BillingStatus = BillingStatus.Cancelled; + EndItems(items, subscription.CurrentPeriodEnd, cancelScheduled: true); + } + else if (subscription.Status == TenantSaasSubscriptionStatus.Active) + { + transition = "payment_past_due"; + subscription.Status = TenantSaasSubscriptionStatus.PastDue; + tenant.BillingStatus = BillingStatus.PastDue; + } + else if (subscription.Status == TenantSaasSubscriptionStatus.PastDue && + asOf >= subscription.CurrentPeriodEnd.AddDays(Math.Clamp(options.PastDueGraceDays, 0, 365))) + { + transition = "past_due_expired"; + subscription.Status = TenantSaasSubscriptionStatus.Expired; + subscription.CancelledAt ??= asOf; + tenant.BillingStatus = BillingStatus.Cancelled; + EndItems(items, subscription.CurrentPeriodEnd, cancelScheduled: true); + } + else + { + return false; + } + + subscription.LifecycleVersion++; + dbContext.AuditLogs.Add(new AuditLog + { + TenantId = tenantId, + Action = $"platform_billing.subscription.{transition}", + TargetType = "tenant_saas_subscriptions", + TargetId = subscription.Id.ToString(), + Details = JsonSerializer.SerializeToElement(new + { + previousStatus, + subscription.Status, + previousBaseVersionId, + subscription.BaseOfferingVersionId, + subscription.CurrentPeriodStart, + subscription.CurrentPeriodEnd, + subscription.LifecycleVersion + }) + }); + + try + { + await dbContext.SaveChangesAsync(cancellationToken); + } + catch (DbUpdateConcurrencyException) + { + dbContext.ChangeTracker.Clear(); + return false; + } + + var newFeatures = await LoadFeaturesAsync(dbContext, subscription, cancellationToken); + var changedFeatures = oldFeatures.Concat(newFeatures).Distinct(StringComparer.Ordinal).ToArray(); + var eventPublisher = services.GetRequiredService(); + var eventVersion = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + var correlationId = $"saas-subscription-{subscription.Id:N}-lifecycle-{subscription.LifecycleVersion}"; + foreach (var featureCode in changedFeatures) + { + await eventPublisher.CapabilityChangedAsync( + tenantId, + featureCode, + transition, + eventVersion, + correlationId, + cancellationToken); + } + services.GetRequiredService().Invalidate(tenantId); + return true; + } + + private static void EndItems( + IEnumerable items, + DateTimeOffset periodEnd, + bool cancelScheduled) + { + foreach (var item in items) + { + if (item.Status == TenantSaasSubscriptionItemStatus.Active) + { + item.Status = TenantSaasSubscriptionItemStatus.Expired; + if (periodEnd > item.StartsAt && item.EndsAt > periodEnd) + { + item.EndsAt = periodEnd; + } + } + else if (cancelScheduled && item.Status == TenantSaasSubscriptionItemStatus.Scheduled) + { + item.Status = TenantSaasSubscriptionItemStatus.Cancelled; + } + } + } + + private static async Task LoadFeaturesAsync( + TikuDbContext dbContext, + TenantSaasSubscription subscription, + CancellationToken cancellationToken) + { + var versionIds = await dbContext.TenantSaasSubscriptionItems.AsNoTracking() + .Where(value => + value.TenantId == subscription.TenantId && + value.SubscriptionId == subscription.Id && + value.Status == TenantSaasSubscriptionItemStatus.Active) + .Select(value => value.OfferingVersionId) + .ToArrayAsync(cancellationToken); + if (!versionIds.Contains(subscription.BaseOfferingVersionId)) + { + versionIds = [.. versionIds, subscription.BaseOfferingVersionId]; + } + return await dbContext.SaasOfferingVersionFeatures.AsNoTracking() + .Where(value => versionIds.Contains(value.OfferingVersionId)) + .Select(value => value.FeatureCode) + .Distinct() + .ToArrayAsync(cancellationToken); + } +} diff --git a/Tiku.Infrastructure/PlatformBilling/TenantBillingService.cs b/Tiku.Infrastructure/PlatformBilling/TenantBillingService.cs new file mode 100644 index 0000000..f20718f --- /dev/null +++ b/Tiku.Infrastructure/PlatformBilling/TenantBillingService.cs @@ -0,0 +1,505 @@ +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Tiku.Application.Commerce; +using Tiku.Application.PlatformBilling; +using Tiku.Application.Security; +using Tiku.Domain.Common; +using Tiku.Domain.Platform; +using Tiku.Infrastructure.Persistence; + +namespace Tiku.Infrastructure.PlatformBilling; + +internal sealed class TenantBillingService( + TikuDbContext dbContext, + IPlatformBillingPaymentGateway paymentGateway, + IPlatformBillingSettlementService settlementService, + IFeatureAccessService featureAccessService, + IConfiguration configuration) : ITenantBillingService +{ + private static readonly TimeSpan QuoteLifetime = TimeSpan.FromMinutes(30); + private static readonly TimeSpan OrderLifetime = TimeSpan.FromHours(2); + + public async Task GetCatalogAsync( + TenantBillingActor actor, + CancellationToken cancellationToken = default) + { + var now = DateTimeOffset.UtcNow; + var features = await dbContext.SaasFeatures.AsNoTracking() + .Where(value => value.Status == SaasFeatureStatus.Active && !value.IsCore) + .OrderBy(value => value.SortOrder).ThenBy(value => value.Code) + .ToArrayAsync(cancellationToken); + var versions = await LoadPublishedVersionsAsync(now, cancellationToken); + return new TenantBillingCatalog( + features, + versions.Where(value => value.OfferingType == SaasOfferingType.BasePlan).ToArray(), + versions.Where(value => value.OfferingType == SaasOfferingType.AddOn).ToArray()); + } + + public async Task CreateQuoteAsync( + TenantBillingActor actor, + CreatePlatformBillingQuoteCommand command, + CancellationToken cancellationToken = default) + { + var idempotencyKey = Required(command.IdempotencyKey, "idempotencyKey"); + var existingQuote = await dbContext.PlatformBillingQuotes.AsNoTracking() + .SingleOrDefaultAsync(value => value.TenantId == actor.TenantId && value.IdempotencyKey == idempotencyKey, cancellationToken); + if (existingQuote is not null) + { + return await LoadQuoteAsync(actor.TenantId, existingQuote.Id, cancellationToken); + } + + var requestedIds = new[] { command.BaseOfferingVersionId } + .Concat(command.AddOnOfferingVersionIds) + .Distinct() + .ToArray(); + var now = DateTimeOffset.UtcNow; + var versions = await ( + from version in dbContext.SaasOfferingVersions.AsNoTracking() + join offering in dbContext.SaasOfferings.AsNoTracking() on version.OfferingId equals offering.Id + where requestedIds.Contains(version.Id) && + version.Status == SaasOfferingVersionStatus.Published && + offering.Status == SaasOfferingStatus.Active && + (version.EffectiveAt == null || version.EffectiveAt <= now) + select new { Version = version, Offering = offering }) + .ToArrayAsync(cancellationToken); + if (versions.Length != requestedIds.Length || + versions.SingleOrDefault(value => value.Version.Id == command.BaseOfferingVersionId)?.Offering.Type != SaasOfferingType.BasePlan || + versions.Any(value => value.Version.Id != command.BaseOfferingVersionId && value.Offering.Type != SaasOfferingType.AddOn)) + { + throw Error("One or more offering versions are unavailable.", "saas_offering_version_unavailable"); + } + if (versions.Select(value => value.Version.Currency).Distinct(StringComparer.Ordinal).Count() != 1) + { + throw Error("All quote items must use the same currency.", "platform_billing_currency_mismatch"); + } + + var featureRows = await dbContext.SaasOfferingVersionFeatures.AsNoTracking() + .Where(value => requestedIds.Contains(value.OfferingVersionId)) + .ToArrayAsync(cancellationToken); + var limitRows = await dbContext.SaasOfferingVersionLimits.AsNoTracking() + .Where(value => requestedIds.Contains(value.OfferingVersionId)) + .ToArrayAsync(cancellationToken); + var featureCodes = featureRows.Select(value => value.FeatureCode).Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal).ToArray(); + var limits = limitRows.GroupBy(value => value.MetricCode, StringComparer.Ordinal) + .ToDictionary(group => group.Key, group => group.Sum(value => value.LimitValue), StringComparer.Ordinal); + var originalAmount = versions.Sum(value => value.Version.OriginalAmountCents); + var totalAmount = versions.Sum(value => value.Version.AmountCents); + var quote = new PlatformBillingQuote + { + TenantId = actor.TenantId, + QuoteNo = Number("SQ"), + IdempotencyKey = idempotencyKey, + Purpose = command.Purpose, + OriginalAmountCents = originalAmount, + DiscountAmountCents = originalAmount - totalAmount, + TotalAmountCents = totalAmount, + Currency = versions[0].Version.Currency, + ExpiresAt = now.Add(QuoteLifetime), + FeatureSnapshot = JsonSerializer.SerializeToElement(featureCodes), + LimitSnapshot = JsonSerializer.SerializeToElement(limits) + }; + dbContext.PlatformBillingQuotes.Add(quote); + dbContext.PlatformBillingQuoteItems.AddRange(versions.Select(value => new PlatformBillingQuoteItem + { + TenantId = actor.TenantId, + QuoteId = quote.Id, + OfferingVersionId = value.Version.Id, + ItemType = value.Offering.Type == SaasOfferingType.BasePlan + ? PlatformBillingItemType.BasePlan + : PlatformBillingItemType.AddOn, + Quantity = 1, + UnitAmountCents = value.Version.AmountCents, + AmountCents = value.Version.AmountCents, + Snapshot = JsonSerializer.SerializeToElement(new + { + offeringId = value.Offering.Id, + offeringCode = value.Offering.Code, + offeringName = value.Offering.Name, + offeringType = value.Offering.Type, + version = value.Version.Version, + value.Version.BillingCycle, + featureCodes = featureRows.Where(feature => feature.OfferingVersionId == value.Version.Id).Select(feature => feature.FeatureCode).Order(StringComparer.Ordinal), + limits = limitRows.Where(limit => limit.OfferingVersionId == value.Version.Id).ToDictionary(limit => limit.MetricCode, limit => limit.LimitValue) + }) + })); + await dbContext.SaveChangesAsync(cancellationToken); + return await LoadQuoteAsync(actor.TenantId, quote.Id, cancellationToken); + } + + public async Task CreateOrderAsync( + TenantBillingActor actor, + CreatePlatformBillingOrderCommand command, + CancellationToken cancellationToken = default) + { + var key = Required(command.IdempotencyKey, "idempotencyKey"); + var existing = await dbContext.PlatformBillingOrders.AsNoTracking() + .SingleOrDefaultAsync(value => value.TenantId == actor.TenantId && value.IdempotencyKey == key, cancellationToken); + if (existing is not null) + { + return await LoadOrderAsync(actor.TenantId, existing.OrderNo, cancellationToken); + } + + var now = DateTimeOffset.UtcNow; + var quote = await dbContext.PlatformBillingQuotes.SingleOrDefaultAsync(value => + value.TenantId == actor.TenantId && value.Id == command.QuoteId, cancellationToken) + ?? throw Error("Quote was not found.", "platform_billing_quote_not_found"); + if (quote.Status != PlatformBillingQuoteStatus.Active || quote.ExpiresAt <= now) + { + quote.Status = quote.ExpiresAt <= now ? PlatformBillingQuoteStatus.Expired : quote.Status; + await dbContext.SaveChangesAsync(cancellationToken); + throw Error("Quote is no longer active.", "platform_billing_quote_expired"); + } + + var quoteItems = await dbContext.PlatformBillingQuoteItems.AsNoTracking() + .Where(value => value.TenantId == actor.TenantId && value.QuoteId == quote.Id) + .ToArrayAsync(cancellationToken); + var order = new PlatformBillingOrder + { + TenantId = actor.TenantId, + QuoteId = quote.Id, + OrderNo = Number("SO"), + IdempotencyKey = key, + Purpose = quote.Purpose, + OriginalAmountCents = quote.OriginalAmountCents, + DiscountAmountCents = quote.DiscountAmountCents, + TotalAmountCents = quote.TotalAmountCents, + Currency = quote.Currency, + ExpiresAt = now.Add(OrderLifetime), + Snapshot = JsonSerializer.SerializeToElement(new + { + quote.QuoteNo, + quote.FeatureSnapshot, + quote.LimitSnapshot + }) + }; + dbContext.PlatformBillingOrders.Add(order); + dbContext.PlatformBillingOrderItems.AddRange(quoteItems.Select(value => new PlatformBillingOrderItem + { + TenantId = actor.TenantId, + OrderId = order.Id, + OfferingVersionId = value.OfferingVersionId, + ItemType = value.ItemType, + Quantity = value.Quantity, + UnitAmountCents = value.UnitAmountCents, + AmountCents = value.AmountCents, + Snapshot = value.Snapshot + })); + quote.Status = PlatformBillingQuoteStatus.Converted; + await dbContext.SaveChangesAsync(cancellationToken); + return await LoadOrderAsync(actor.TenantId, order.OrderNo, cancellationToken); + } + + public async Task CreatePaymentAsync( + TenantBillingActor actor, + CreatePlatformBillingPaymentCommand command, + CancellationToken cancellationToken = default) + { + var key = Required(command.IdempotencyKey, "idempotencyKey"); + var existing = await dbContext.PlatformBillingPayments.AsNoTracking() + .SingleOrDefaultAsync(value => value.TenantId == actor.TenantId && value.IdempotencyKey == key, cancellationToken); + if (existing is not null) + { + return ToPaymentView(existing); + } + + var order = await dbContext.PlatformBillingOrders.SingleOrDefaultAsync(value => + value.TenantId == actor.TenantId && value.OrderNo == command.OrderNo, cancellationToken) + ?? throw Error("Order was not found.", "platform_billing_order_not_found"); + if (order.Status != PlatformBillingOrderStatus.PendingPayment || order.ExpiresAt <= DateTimeOffset.UtcNow) + { + if (order.ExpiresAt <= DateTimeOffset.UtcNow && order.Status == PlatformBillingOrderStatus.PendingPayment) + { + order.Status = PlatformBillingOrderStatus.Expired; + await dbContext.SaveChangesAsync(cancellationToken); + } + throw Error("Order does not allow a new payment.", "platform_billing_order_status_invalid"); + } + + var provider = NormalizeProvider(command.Provider); + var payment = new PlatformBillingPayment + { + TenantId = actor.TenantId, + OrderId = order.Id, + PaymentNo = Number("SP"), + IdempotencyKey = key, + Provider = provider, + Method = Required(command.Method, "method"), + AmountCents = order.TotalAmountCents + }; + dbContext.PlatformBillingPayments.Add(payment); + await dbContext.SaveChangesAsync(cancellationToken); + + if (order.TotalAmountCents == 0) + { + await settlementService.MarkPaidAsync( + payment.Id, + $"zero-{payment.Id:N}", + "zero_amount_settled", + null, + DateTimeOffset.UtcNow, + JsonDefaults.Object(), + actor.UserId, + cancellationToken); + return ToPaymentView(payment); + } + + try + { + var result = await paymentGateway.CreatePaymentAsync( + provider, + new CreatePaymentProviderRequest( + actor.TenantId, + order.OrderNo, + $"SaaS subscription {order.OrderNo}", + order.TotalAmountCents, + payment.Method, + command.OpenId, + command.ReturnUrl, + command.QuitUrl, + provider == PaymentProviders.Manual ? string.Empty : BuildNotifyUrl(provider), + JsonSerializer.SerializeToElement(new { payment.PaymentNo })), + cancellationToken); + payment.ProviderTradeNo = result.ProviderTradeNo; + payment.ClientPayload = result.ClientPayload; + await dbContext.SaveChangesAsync(cancellationToken); + return ToPaymentView(payment); + } + catch + { + payment.Status = PlatformBillingPaymentStatus.Failed; + await dbContext.SaveChangesAsync(CancellationToken.None); + throw; + } + } + + public async Task> GetOrdersAsync( + TenantBillingActor actor, + int limit, + CancellationToken cancellationToken = default) + { + var orderNos = await dbContext.PlatformBillingOrders.AsNoTracking() + .Where(value => value.TenantId == actor.TenantId) + .OrderByDescending(value => value.CreatedAt) + .Take(Math.Clamp(limit, 1, 200)) + .Select(value => value.OrderNo) + .ToArrayAsync(cancellationToken); + var result = new List(); + foreach (var orderNo in orderNos) + { + result.Add(await LoadOrderAsync(actor.TenantId, orderNo, cancellationToken)); + } + return result; + } + + public Task GetOrderAsync(TenantBillingActor actor, string orderNo, CancellationToken cancellationToken = default) => + LoadOrderAsync(actor.TenantId, orderNo, cancellationToken); + + public async Task GetSubscriptionAsync( + TenantBillingActor actor, + CancellationToken cancellationToken = default) + { + var subscription = await dbContext.TenantSaasSubscriptions.AsNoTracking() + .Where(value => value.TenantId == actor.TenantId) + .OrderByDescending(value => value.UpdatedAt) + .FirstOrDefaultAsync(cancellationToken); + return subscription is null ? null : await ToSubscriptionViewAsync(subscription, cancellationToken); + } + + public async Task ChangeSubscriptionAsync( + TenantBillingActor actor, + ChangeTenantSubscriptionCommand command, + string idempotencyKey, + CancellationToken cancellationToken = default) + { + var current = await dbContext.TenantSaasSubscriptions.AsNoTracking() + .Where(value => value.TenantId == actor.TenantId) + .OrderByDescending(value => value.UpdatedAt) + .FirstOrDefaultAsync(cancellationToken); + var currentAmount = current is null + ? 0 + : await dbContext.SaasOfferingVersions.AsNoTracking().Where(value => value.Id == current.BaseOfferingVersionId).Select(value => value.AmountCents).SingleAsync(cancellationToken); + var requestedAmount = await dbContext.SaasOfferingVersions.AsNoTracking().Where(value => value.Id == command.BaseOfferingVersionId).Select(value => (int?)value.AmountCents).SingleOrDefaultAsync(cancellationToken) + ?? throw Error("Offering version was not found.", "saas_offering_version_not_found"); + var purpose = current is null + ? PlatformBillingOrderPurpose.NewSubscription + : requestedAmount >= currentAmount + ? PlatformBillingOrderPurpose.Upgrade + : PlatformBillingOrderPurpose.Downgrade; + var quote = await CreateQuoteAsync(actor, new CreatePlatformBillingQuoteCommand( + command.BaseOfferingVersionId, command.AddOnOfferingVersionIds, purpose, $"{Required(idempotencyKey, "idempotencyKey")}:quote"), cancellationToken); + return await CreateOrderAsync(actor, new CreatePlatformBillingOrderCommand(quote.Id, idempotencyKey), cancellationToken); + } + + public async Task RenewSubscriptionAsync( + TenantBillingActor actor, + string idempotencyKey, + CancellationToken cancellationToken = default) + { + var subscription = await dbContext.TenantSaasSubscriptions.AsNoTracking() + .Where(value => value.TenantId == actor.TenantId) + .OrderByDescending(value => value.UpdatedAt) + .FirstOrDefaultAsync(cancellationToken) + ?? throw Error("Subscription was not found.", "tenant_saas_subscription_not_found"); + var addOns = await dbContext.TenantSaasSubscriptionItems.AsNoTracking() + .Where(value => value.TenantId == actor.TenantId && value.SubscriptionId == subscription.Id && + value.ItemType == TenantSaasSubscriptionItemType.AddOn && + value.Status == TenantSaasSubscriptionItemStatus.Active) + .Select(value => value.OfferingVersionId) + .ToArrayAsync(cancellationToken); + var quote = await CreateQuoteAsync(actor, new CreatePlatformBillingQuoteCommand( + subscription.BaseOfferingVersionId, addOns, PlatformBillingOrderPurpose.Renewal, $"{Required(idempotencyKey, "idempotencyKey")}:quote"), cancellationToken); + return await CreateOrderAsync(actor, new CreatePlatformBillingOrderCommand(quote.Id, idempotencyKey), cancellationToken); + } + + public async Task CancelSubscriptionAsync( + TenantBillingActor actor, + CancellationToken cancellationToken = default) + { + var subscription = await dbContext.TenantSaasSubscriptions + .Where(value => value.TenantId == actor.TenantId) + .OrderByDescending(value => value.UpdatedAt) + .FirstOrDefaultAsync(cancellationToken) + ?? throw Error("Subscription was not found.", "tenant_saas_subscription_not_found"); + subscription.CancelAtPeriodEnd = true; + subscription.CancelledAt = DateTimeOffset.UtcNow; + await dbContext.SaveChangesAsync(cancellationToken); + return await ToSubscriptionViewAsync(subscription, cancellationToken); + } + + public Task> GetUsageAsync(TenantBillingActor actor, CancellationToken cancellationToken = default) => + featureAccessService.GetQuotaSummaryAsync(actor.TenantId, cancellationToken); + + public async Task> GetInvoicesAsync( + TenantBillingActor actor, + int limit, + CancellationToken cancellationToken = default) => + await dbContext.PlatformBillingInvoices.AsNoTracking() + .Where(value => value.TenantId == actor.TenantId) + .OrderByDescending(value => value.CreatedAt) + .Take(Math.Clamp(limit, 1, 200)) + .ToArrayAsync(cancellationToken); + + private async Task LoadQuoteAsync(Guid tenantId, Guid quoteId, CancellationToken cancellationToken) + { + var quote = await dbContext.PlatformBillingQuotes.AsNoTracking().SingleAsync(value => value.TenantId == tenantId && value.Id == quoteId, cancellationToken); + var items = await LoadQuoteItemsAsync(tenantId, quote.Id, cancellationToken); + return new PlatformBillingQuoteView( + quote.Id, + quote.QuoteNo, + quote.Status, + quote.Purpose, + quote.OriginalAmountCents, + quote.DiscountAmountCents, + quote.TotalAmountCents, + quote.Currency, + quote.ExpiresAt, + items, + ReadStringArray(quote.FeatureSnapshot), + ReadLongDictionary(quote.LimitSnapshot)); + } + + private async Task LoadOrderAsync(Guid tenantId, string orderNo, CancellationToken cancellationToken) + { + var normalized = Required(orderNo, "orderNo"); + var order = await dbContext.PlatformBillingOrders.AsNoTracking().SingleOrDefaultAsync(value => value.TenantId == tenantId && value.OrderNo == normalized, cancellationToken) + ?? throw Error("Order was not found.", "platform_billing_order_not_found"); + var items = await ( + from item in dbContext.PlatformBillingOrderItems.AsNoTracking() + join version in dbContext.SaasOfferingVersions.AsNoTracking() on item.OfferingVersionId equals version.Id + join offering in dbContext.SaasOfferings.AsNoTracking() on version.OfferingId equals offering.Id + where item.TenantId == tenantId && item.OrderId == order.Id + orderby item.ItemType, offering.Code + select new PlatformBillingQuoteItemView(item.OfferingVersionId, offering.Code, offering.Name, item.ItemType, item.UnitAmountCents, item.AmountCents)) + .ToArrayAsync(cancellationToken); + return new PlatformBillingOrderView(order.Id, order.OrderNo, order.Purpose, order.Status, order.TotalAmountCents, order.Currency, order.ExpiresAt, order.PaidAt, items); + } + + private async Task LoadQuoteItemsAsync(Guid tenantId, Guid quoteId, CancellationToken cancellationToken) => + await ( + from item in dbContext.PlatformBillingQuoteItems.AsNoTracking() + join version in dbContext.SaasOfferingVersions.AsNoTracking() on item.OfferingVersionId equals version.Id + join offering in dbContext.SaasOfferings.AsNoTracking() on version.OfferingId equals offering.Id + where item.TenantId == tenantId && item.QuoteId == quoteId + orderby item.ItemType, offering.Code + select new PlatformBillingQuoteItemView(item.OfferingVersionId, offering.Code, offering.Name, item.ItemType, item.UnitAmountCents, item.AmountCents)) + .ToArrayAsync(cancellationToken); + + private async Task ToSubscriptionViewAsync(TenantSaasSubscription subscription, CancellationToken cancellationToken) + { + var versionIds = await dbContext.TenantSaasSubscriptionItems.AsNoTracking() + .Where(value => value.TenantId == subscription.TenantId && value.SubscriptionId == subscription.Id && value.Status == TenantSaasSubscriptionItemStatus.Active) + .Select(value => value.OfferingVersionId) + .ToArrayAsync(cancellationToken); + if (!versionIds.Contains(subscription.BaseOfferingVersionId)) versionIds = [.. versionIds, subscription.BaseOfferingVersionId]; + var features = await dbContext.SaasOfferingVersionFeatures.AsNoTracking() + .Where(value => versionIds.Contains(value.OfferingVersionId)) + .Select(value => value.FeatureCode) + .Distinct() + .OrderBy(value => value) + .ToArrayAsync(cancellationToken); + return new TenantSubscriptionView( + subscription.Id, + subscription.Status, + subscription.StartsAt, + subscription.CurrentPeriodStart, + subscription.CurrentPeriodEnd, + subscription.CancelAtPeriodEnd, + subscription.BaseOfferingVersionId, + subscription.ScheduledBaseOfferingVersionId, + features); + } + + private async Task LoadPublishedVersionsAsync(DateTimeOffset now, CancellationToken cancellationToken) + { + var rows = await ( + from version in dbContext.SaasOfferingVersions.AsNoTracking() + join offering in dbContext.SaasOfferings.AsNoTracking() on version.OfferingId equals offering.Id + where version.Status == SaasOfferingVersionStatus.Published && offering.Status == SaasOfferingStatus.Active && + (version.EffectiveAt == null || version.EffectiveAt <= now) + orderby offering.SortOrder, offering.Code, version.Version descending + select new { Version = version, Offering = offering }) + .ToArrayAsync(cancellationToken); + var latest = rows.GroupBy(value => value.Offering.Id).Select(group => group.First()).ToArray(); + var ids = latest.Select(value => value.Version.Id).ToArray(); + var features = await dbContext.SaasOfferingVersionFeatures.AsNoTracking().Where(value => ids.Contains(value.OfferingVersionId)).ToArrayAsync(cancellationToken); + var limits = await dbContext.SaasOfferingVersionLimits.AsNoTracking().Where(value => ids.Contains(value.OfferingVersionId)).ToArrayAsync(cancellationToken); + return latest.Select(row => new SaasOfferingVersionItem( + row.Version.Id, row.Offering.Id, row.Offering.Code, row.Offering.Name, row.Offering.Type, + row.Version.Version, row.Version.Status, row.Version.BillingCycle, + row.Version.OriginalAmountCents, row.Version.AmountCents, row.Version.Currency, + row.Version.EffectiveAt, row.Version.PublishedAt, + features.Where(value => value.OfferingVersionId == row.Version.Id).Select(value => value.FeatureCode).Order(StringComparer.Ordinal).ToArray(), + limits.Where(value => value.OfferingVersionId == row.Version.Id).ToDictionary(value => value.MetricCode, value => value.LimitValue, StringComparer.Ordinal))) + .ToArray(); + } + + private string BuildNotifyUrl(string provider) + { + var baseUrl = configuration["PlatformBilling:PublicBaseUrl"]?.TrimEnd('/'); + if (string.IsNullOrWhiteSpace(baseUrl)) + { + throw Error("Platform billing public base URL is not configured.", "platform_billing_public_url_missing"); + } + return $"{baseUrl}/api/platform-billing/callbacks/{provider}"; + } + + private static PlatformBillingPaymentView ToPaymentView(PlatformBillingPayment value) => + new(value.Id, value.PaymentNo, value.Provider, value.Method, value.Status, value.AmountCents, value.ClientPayload); + + private static string Number(string prefix) => $"{prefix}{DateTimeOffset.UtcNow:yyyyMMddHHmmss}{Guid.NewGuid():N}"[..32]; + private static string Required(string? value, string field) => string.IsNullOrWhiteSpace(value) ? throw Error($"{field} is required.", "required_field") : value.Trim(); + private static string NormalizeProvider(string value) => Required(value, "provider").ToLowerInvariant().Replace('-', '_'); + private static PlatformBillingException Error(string message, string code) => new(message, code); + + private static string[] ReadStringArray(JsonElement value) => value.ValueKind == JsonValueKind.Array + ? value.EnumerateArray().Where(item => item.ValueKind == JsonValueKind.String).Select(item => item.GetString()!).ToArray() + : []; + + private static IReadOnlyDictionary ReadLongDictionary(JsonElement value) + { + if (value.ValueKind != JsonValueKind.Object) return new Dictionary(StringComparer.Ordinal); + return value.EnumerateObject().Where(property => property.Value.TryGetInt64(out _)) + .ToDictionary(property => property.Name, property => property.Value.GetInt64(), StringComparer.Ordinal); + } +} diff --git a/Tiku.Infrastructure/QuestionBanks/PublicQuestionAccessPolicy.cs b/Tiku.Infrastructure/QuestionBanks/PublicQuestionAccessPolicy.cs index 0357245..09dd60a 100644 --- a/Tiku.Infrastructure/QuestionBanks/PublicQuestionAccessPolicy.cs +++ b/Tiku.Infrastructure/QuestionBanks/PublicQuestionAccessPolicy.cs @@ -1,27 +1,25 @@ using Microsoft.EntityFrameworkCore; using Tiku.Application.QuestionBanks; -using Tiku.Domain.Commerce; +using Tiku.Application.Security; using Tiku.Domain.Tenancy; using Tiku.Infrastructure.Persistence; namespace Tiku.Infrastructure.QuestionBanks; -public sealed class PublicQuestionAccessPolicy(TikuDbContext dbContext) : IPublicQuestionAccessPolicy +public sealed class PublicQuestionAccessPolicy( + TikuDbContext dbContext, + IFeatureAccessService featureAccessService) : IPublicQuestionAccessPolicy { public async Task EnsureCanStartAsync(Guid tenantId, CancellationToken cancellationToken = default) { var tenantIsActive = await dbContext.Tenants.AsNoTracking().AnyAsync( tenant => tenant.Id == tenantId && tenant.Status == TenantStatus.Active, cancellationToken); - var now = DateTimeOffset.UtcNow; - var subscriptionIsActive = await dbContext.TenantSubscriptions.AsNoTracking().AnyAsync( - subscription => - subscription.TenantId == tenantId && - (subscription.Status == TenantSubscriptionStatus.Trial || - subscription.Status == TenantSubscriptionStatus.Active) && - (!subscription.StartsAt.HasValue || subscription.StartsAt <= now) && - (!subscription.ExpiresAt.HasValue || subscription.ExpiresAt > now), - cancellationToken); + var subscriptionIsActive = (await featureAccessService.EvaluateAsync( + tenantId, + SaasFeatureCatalog.Practice, + FeatureAccessOperation.Write, + cancellationToken)).Allowed; if (!tenantIsActive || !subscriptionIsActive) { diff --git a/Tiku.Infrastructure/Security/CapabilityAccessEvaluator.cs b/Tiku.Infrastructure/Security/CapabilityAccessEvaluator.cs deleted file mode 100644 index c0ca7d4..0000000 --- a/Tiku.Infrastructure/Security/CapabilityAccessEvaluator.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Tiku.Application.Security; -using Tiku.Domain.Commerce; -using Tiku.Domain.Platform; -using Tiku.Domain.Tenancy; -using Tiku.Infrastructure.Persistence; - -namespace Tiku.Infrastructure.Security; - -internal sealed class CapabilityAccessEvaluator(TikuDbContext dbContext) : ICapabilityAccessEvaluator -{ - public async Task IsAllowedAsync( - Guid tenantId, - string moduleCode, - CapabilityOperation operation, - CancellationToken cancellationToken = default) - { - var normalized = moduleCode.Trim().ToLowerInvariant(); - if (!ProductModuleCatalog.Contains(normalized)) - { - return false; - } - var moduleExists = await dbContext.ProductModules.AsNoTracking() - .AnyAsync(item => item.Code == normalized && item.Status == ProductModuleStatus.Active, cancellationToken); - if (!moduleExists) - { - return false; - } - - var tenantActive = await dbContext.Tenants.AsNoTracking() - .AnyAsync(item => item.Id == tenantId && item.Status == TenantStatus.Active, cancellationToken); - if (!tenantActive) - { - return false; - } - - var now = DateTimeOffset.UtcNow; - var overrideMode = await dbContext.TenantModuleOverrides.AsNoTracking() - .Where(item => item.TenantId == tenantId && item.ModuleCode == normalized && - (item.ExpiresAt == null || item.ExpiresAt > now)) - .Select(item => (TenantModuleOverrideMode?)item.Mode) - .SingleOrDefaultAsync(cancellationToken); - if (overrideMode == TenantModuleOverrideMode.Disabled) - { - return false; - } - - var subscription = await dbContext.TenantSubscriptions.AsNoTracking() - .Where(item => item.TenantId == tenantId) - .OrderByDescending(item => item.UpdatedAt) - .Select(item => new { item.PlanCode, item.Status, item.StartsAt, item.ExpiresAt }) - .FirstOrDefaultAsync(cancellationToken); - if (subscription is null || subscription.StartsAt > now || subscription.ExpiresAt <= now) - { - return false; - } - - var entitled = overrideMode == TenantModuleOverrideMode.Enabled || - await dbContext.PlanModuleEntitlements.AsNoTracking().AnyAsync( - item => item.PlanCode == subscription.PlanCode && item.ModuleCode == normalized && item.Enabled, - cancellationToken); - if (!entitled) - { - return false; - } - - return operation == CapabilityOperation.Read || - subscription.Status is TenantSubscriptionStatus.Trial or TenantSubscriptionStatus.Active; - } - - public async Task> GetEnabledModulesAsync( - Guid tenantId, - CapabilityOperation operation = CapabilityOperation.Read, - CancellationToken cancellationToken = default) - { - var modules = await dbContext.ProductModules.AsNoTracking() - .Where(item => item.Status == ProductModuleStatus.Active) - .Select(item => item.Code) - .ToArrayAsync(cancellationToken); - var enabled = new HashSet(StringComparer.Ordinal); - foreach (var module in modules) - { - if (await IsAllowedAsync(tenantId, module, operation, cancellationToken)) - { - enabled.Add(module); - } - } - return enabled; - } -} diff --git a/Tiku.Infrastructure/Security/FeatureAccessService.cs b/Tiku.Infrastructure/Security/FeatureAccessService.cs new file mode 100644 index 0000000..ac705dd --- /dev/null +++ b/Tiku.Infrastructure/Security/FeatureAccessService.cs @@ -0,0 +1,364 @@ +using Microsoft.EntityFrameworkCore; +using Tiku.Application.Security; +using Tiku.Domain.Platform; +using Tiku.Domain.Tenancy; +using Tiku.Infrastructure.Persistence; + +namespace Tiku.Infrastructure.Security; + +internal sealed class FeatureAccessService(TikuDbContext dbContext) : IFeatureAccessService +{ + public async Task EvaluateAsync( + Guid tenantId, + string featureCode, + FeatureAccessOperation operation, + CancellationToken cancellationToken = default) + { + var normalized = Normalize(featureCode); + var tenantStatus = await dbContext.Tenants.AsNoTracking() + .Where(value => value.Id == tenantId) + .Select(value => (TenantStatus?)value.Status) + .SingleOrDefaultAsync(cancellationToken); + if (tenantStatus != TenantStatus.Active) + { + return Denied(normalized, operation, "tenant_inactive"); + } + + var feature = await dbContext.SaasFeatures.AsNoTracking() + .Where(value => value.Code == normalized) + .Select(value => new { value.Status, value.IsCore }) + .SingleOrDefaultAsync(cancellationToken); + if (feature is null || feature.Status != SaasFeatureStatus.Active) + { + return Denied(normalized, operation, "feature_unavailable"); + } + + if (feature.IsCore) + { + return Allowed(normalized, operation); + } + + var now = DateTimeOffset.UtcNow; + var overrideMode = await dbContext.TenantFeatureOverrides.AsNoTracking() + .Where(value => value.TenantId == tenantId && value.FeatureCode == normalized && + (value.ExpiresAt == null || value.ExpiresAt > now)) + .Select(value => (TenantFeatureOverrideMode?)value.Mode) + .SingleOrDefaultAsync(cancellationToken); + if (overrideMode == TenantFeatureOverrideMode.Disabled) + { + return Denied(normalized, operation, "feature_disabled"); + } + + var subscription = await dbContext.TenantSaasSubscriptions.AsNoTracking() + .Where(value => value.TenantId == tenantId) + .OrderByDescending(value => value.UpdatedAt) + .Select(value => new + { + value.Id, + value.BaseOfferingVersionId, + value.Status, + value.StartsAt, + value.CurrentPeriodEnd + }) + .FirstOrDefaultAsync(cancellationToken); + if (subscription is null) + { + return overrideMode == TenantFeatureOverrideMode.Enabled + ? Allowed(normalized, operation) + : Denied(normalized, operation, "subscription_missing"); + } + + if (operation == FeatureAccessOperation.Write && + (subscription.Status is not (TenantSaasSubscriptionStatus.Trial or TenantSaasSubscriptionStatus.Active) || + subscription.StartsAt > now || subscription.CurrentPeriodEnd <= now)) + { + return Denied(normalized, operation, "subscription_read_only"); + } + + if (subscription.Status == TenantSaasSubscriptionStatus.Suspended) + { + return Denied(normalized, operation, "subscription_suspended"); + } + + if (overrideMode == TenantFeatureOverrideMode.Enabled) + { + return Allowed(normalized, operation); + } + + var versionIds = await dbContext.TenantSaasSubscriptionItems.AsNoTracking() + .Where(value => value.TenantId == tenantId && value.SubscriptionId == subscription.Id && + (operation == FeatureAccessOperation.Read + ? value.Status != TenantSaasSubscriptionItemStatus.Pending && + value.Status != TenantSaasSubscriptionItemStatus.Scheduled && + value.StartsAt <= now + : value.Status == TenantSaasSubscriptionItemStatus.Active && + value.StartsAt <= now && value.EndsAt > now)) + .Select(value => value.OfferingVersionId) + .ToArrayAsync(cancellationToken); + if (!versionIds.Contains(subscription.BaseOfferingVersionId)) + { + versionIds = [.. versionIds, subscription.BaseOfferingVersionId]; + } + + var entitled = await dbContext.SaasOfferingVersionFeatures.AsNoTracking() + .AnyAsync(value => versionIds.Contains(value.OfferingVersionId) && value.FeatureCode == normalized, cancellationToken); + return entitled + ? Allowed(normalized, operation) + : Denied(normalized, operation, "feature_not_purchased"); + } + + public async Task> GetEnabledFeaturesAsync( + Guid tenantId, + FeatureAccessOperation operation = FeatureAccessOperation.Read, + CancellationToken cancellationToken = default) + { + var codes = await dbContext.SaasFeatures.AsNoTracking() + .Where(value => value.Status == SaasFeatureStatus.Active) + .Select(value => value.Code) + .ToArrayAsync(cancellationToken); + var enabled = new HashSet(StringComparer.Ordinal); + foreach (var code in codes) + { + if ((await EvaluateAsync(tenantId, code, operation, cancellationToken)).Allowed) + { + enabled.Add(code); + } + } + + return enabled; + } + + public async Task> FilterPermissionCodesAsync( + Guid tenantId, + IEnumerable permissionCodes, + FeatureAccessOperation operation = FeatureAccessOperation.Read, + CancellationToken cancellationToken = default) + { + var requested = permissionCodes.Distinct(StringComparer.Ordinal).ToArray(); + var permissions = await ( + from permission in dbContext.BackendPermissions.AsNoTracking() + join module in dbContext.PermissionModules.AsNoTracking() + on permission.PermissionModuleCode equals module.Code + where requested.Contains(permission.Code) + select new { permission.Code, module.RequiredFeatureCode }) + .ToArrayAsync(cancellationToken); + var allowed = new HashSet(StringComparer.Ordinal); + foreach (var permission in permissions) + { + if (permission.RequiredFeatureCode is null || + (await EvaluateAsync(tenantId, permission.RequiredFeatureCode, operation, cancellationToken)).Allowed) + { + allowed.Add(permission.Code); + } + } + + return allowed; + } + + public async Task> GetQuotaSummaryAsync( + Guid tenantId, + CancellationToken cancellationToken = default) + { + var now = DateTimeOffset.UtcNow; + var subscription = await CurrentWritableSubscriptionAsync(tenantId, now, cancellationToken); + if (subscription is null) + { + return []; + } + + var limits = await ResolveLimitsAsync(tenantId, subscription.Id, subscription.BaseOfferingVersionId, now, cancellationToken); + var usages = await dbContext.TenantFeatureUsages.AsNoTracking() + .Where(value => value.TenantId == tenantId && value.PeriodStart <= now && value.PeriodEnd > now) + .ToDictionaryAsync(value => value.MetricCode, StringComparer.Ordinal, cancellationToken); + var result = new List(); + foreach (var limit in limits) + { + usages.TryGetValue(limit.Key, out var usage); + var used = usage?.UsedValue ?? 0; + var percent = limit.Value == 0 ? 100 : (int)Math.Min(100, used * 100 / limit.Value); + result.Add(new FeatureQuotaSnapshot( + limit.Key, + used, + limit.Value, + percent, + percent >= 80, + used >= limit.Value, + usage?.PeriodStart ?? subscription.CurrentPeriodStart, + usage?.PeriodEnd ?? subscription.CurrentPeriodEnd)); + } + + return result.OrderBy(value => value.MetricCode, StringComparer.Ordinal).ToArray(); + } + + public async Task TryConsumeQuotaAsync( + Guid tenantId, + string metricCode, + long amount, + CancellationToken cancellationToken = default) + { + if (amount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(amount)); + } + + var normalized = Normalize(metricCode); + var now = DateTimeOffset.UtcNow; + var subscription = await CurrentWritableSubscriptionAsync(tenantId, now, cancellationToken); + if (subscription is null) + { + return false; + } + + var limits = await ResolveLimitsAsync(tenantId, subscription.Id, subscription.BaseOfferingVersionId, now, cancellationToken); + if (!limits.TryGetValue(normalized, out var limit)) + { + // Quotas are opt-in per offering version. A missing metric means that the + // subscription does not cap this operation, rather than a zero allowance. + return true; + } + + var updated = await dbContext.TenantFeatureUsages + .Where(value => value.TenantId == tenantId && value.MetricCode == normalized && + value.PeriodStart == subscription.CurrentPeriodStart && + value.PeriodEnd == subscription.CurrentPeriodEnd && + value.UsedValue + amount <= limit) + .ExecuteUpdateAsync(setters => setters + .SetProperty(value => value.UsedValue, value => value.UsedValue + amount) + .SetProperty(value => value.LimitValueSnapshot, limit) + .SetProperty(value => value.WarningIssued, value => value.WarningIssued || (value.UsedValue + amount) * 100 >= limit * 80) + .SetProperty(value => value.Version, value => value.Version + 1) + .SetProperty(value => value.UpdatedAt, now), cancellationToken); + if (updated == 1) + { + return true; + } + + var exists = await dbContext.TenantFeatureUsages.AnyAsync(value => + value.TenantId == tenantId && value.MetricCode == normalized && + value.PeriodStart == subscription.CurrentPeriodStart && value.PeriodEnd == subscription.CurrentPeriodEnd, + cancellationToken); + if (exists || amount > limit) + { + return false; + } + + dbContext.TenantFeatureUsages.Add(new TenantFeatureUsage + { + TenantId = tenantId, + MetricCode = normalized, + PeriodStart = subscription.CurrentPeriodStart, + PeriodEnd = subscription.CurrentPeriodEnd, + UsedValue = amount, + LimitValueSnapshot = limit, + WarningIssued = amount * 100 >= limit * 80, + Version = 1 + }); + try + { + await dbContext.SaveChangesAsync(cancellationToken); + return true; + } + catch (DbUpdateException exception) + { + foreach (var entry in exception.Entries) + { + entry.State = EntityState.Detached; + } + return await dbContext.TenantFeatureUsages + .Where(value => value.TenantId == tenantId && value.MetricCode == normalized && + value.PeriodStart == subscription.CurrentPeriodStart && + value.PeriodEnd == subscription.CurrentPeriodEnd && + value.UsedValue + amount <= limit) + .ExecuteUpdateAsync(setters => setters + .SetProperty(value => value.UsedValue, value => value.UsedValue + amount) + .SetProperty(value => value.Version, value => value.Version + 1) + .SetProperty(value => value.UpdatedAt, now), cancellationToken) == 1; + } + } + + public async Task ReleaseQuotaAsync( + Guid tenantId, + string metricCode, + long amount, + CancellationToken cancellationToken = default) + { + if (amount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(amount)); + } + + var normalized = Normalize(metricCode); + var now = DateTimeOffset.UtcNow; + var subscription = await CurrentWritableSubscriptionAsync(tenantId, now, cancellationToken); + if (subscription is null) + { + return; + } + await dbContext.TenantFeatureUsages + .Where(value => value.TenantId == tenantId && value.MetricCode == normalized && + value.PeriodStart == subscription.CurrentPeriodStart && + value.PeriodEnd == subscription.CurrentPeriodEnd) + .ExecuteUpdateAsync(setters => setters + .SetProperty(value => value.UsedValue, value => value.UsedValue > amount ? value.UsedValue - amount : 0) + .SetProperty(value => value.Version, value => value.Version + 1) + .SetProperty(value => value.UpdatedAt, now), cancellationToken); + } + + private async Task CurrentWritableSubscriptionAsync( + Guid tenantId, + DateTimeOffset now, + CancellationToken cancellationToken) + { + return await dbContext.TenantSaasSubscriptions.AsNoTracking() + .Where(value => value.TenantId == tenantId && + (value.Status == TenantSaasSubscriptionStatus.Trial || + value.Status == TenantSaasSubscriptionStatus.Active)) + .Where(value => value.StartsAt <= now && value.CurrentPeriodEnd > now) + .OrderByDescending(value => value.UpdatedAt) + .Select(value => new SubscriptionProjection( + value.Id, + value.BaseOfferingVersionId, + value.CurrentPeriodStart, + value.CurrentPeriodEnd)) + .FirstOrDefaultAsync(cancellationToken); + } + + private async Task> ResolveLimitsAsync( + Guid tenantId, + Guid subscriptionId, + Guid baseVersionId, + DateTimeOffset now, + CancellationToken cancellationToken) + { + var versionIds = await dbContext.TenantSaasSubscriptionItems.AsNoTracking() + .Where(value => value.TenantId == tenantId && value.SubscriptionId == subscriptionId && + value.Status == TenantSaasSubscriptionItemStatus.Active && + value.StartsAt <= now && value.EndsAt > now) + .Select(value => value.OfferingVersionId) + .ToArrayAsync(cancellationToken); + if (!versionIds.Contains(baseVersionId)) + { + versionIds = [.. versionIds, baseVersionId]; + } + + return await dbContext.SaasOfferingVersionLimits.AsNoTracking() + .Where(value => versionIds.Contains(value.OfferingVersionId)) + .GroupBy(value => value.MetricCode) + .Select(group => new { MetricCode = group.Key, Limit = group.Sum(value => value.LimitValue) }) + .ToDictionaryAsync(value => value.MetricCode, value => value.Limit, StringComparer.Ordinal, cancellationToken); + } + + private static FeatureAccessDecision Allowed(string featureCode, FeatureAccessOperation operation) => + new(true, null, featureCode, operation); + + private static FeatureAccessDecision Denied(string featureCode, FeatureAccessOperation operation, string code) => + new(false, code, featureCode, operation); + + private static string Normalize(string value) => value.Trim().ToLowerInvariant(); + + private sealed record SubscriptionProjection( + Guid Id, + Guid BaseOfferingVersionId, + DateTimeOffset CurrentPeriodStart, + DateTimeOffset CurrentPeriodEnd); +} diff --git a/Tiku.Infrastructure/Security/FeatureUsageReconciliationService.cs b/Tiku.Infrastructure/Security/FeatureUsageReconciliationService.cs new file mode 100644 index 0000000..16ef46d --- /dev/null +++ b/Tiku.Infrastructure/Security/FeatureUsageReconciliationService.cs @@ -0,0 +1,213 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Tiku.Application.Security; +using Tiku.Domain.Content; +using Tiku.Domain.Platform; +using Tiku.Domain.QuestionBanks; +using Tiku.Domain.Tenancy; +using Tiku.Infrastructure.Persistence; + +namespace Tiku.Infrastructure.Security; + +internal sealed class FeatureUsageReconciliationService( + TikuDbContext dbContext, + ITenantExecutionScope tenantExecutionScope, + IOptions options) : IFeatureUsageReconciliationService +{ + private static readonly string[] CurrentMetrics = + [ + SaasQuotaMetricCatalog.StaffCount, + SaasQuotaMetricCatalog.StudentCount, + SaasQuotaMetricCatalog.PrivateQuestionCount, + SaasQuotaMetricCatalog.StorageBytes + ]; + + public Task> ReconcileTenantAsync( + ReconcileFeatureUsageRequest request, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(request); + return tenantExecutionScope.ExecuteAsync>( + new SystemScopeRequest( + request.TenantId, + request.CallerType, + request.Caller, + request.Reason, + request.CorrelationId), + async (provider, token) => + { + var dbContext = provider.GetRequiredService(); + return await ReconcileCoreAsync(dbContext, request.TenantId, token); + }, + cancellationToken); + } + + public async Task ProcessDueAsync(CancellationToken cancellationToken = default) + { + if (!options.Value.Enabled) + { + return 0; + } + + var now = DateTimeOffset.UtcNow; + var cutoff = now.AddMinutes(-Math.Clamp(options.Value.IntervalMinutes, 1, 24 * 60)); + var candidates = await dbContext.TenantSaasSubscriptions.AsNoTracking() + .Where(subscription => + (subscription.Status == TenantSaasSubscriptionStatus.Trial || + subscription.Status == TenantSaasSubscriptionStatus.Active) && + subscription.StartsAt <= now && + subscription.CurrentPeriodEnd > now && + dbContext.TenantSaasSubscriptionItems.Any(item => + item.TenantId == subscription.TenantId && + item.SubscriptionId == subscription.Id && + item.Status == TenantSaasSubscriptionItemStatus.Active && + item.StartsAt <= now && + item.EndsAt > now && + dbContext.SaasOfferingVersionLimits.Any(limit => + limit.OfferingVersionId == item.OfferingVersionId && + CurrentMetrics.Contains(limit.MetricCode))) && + !dbContext.AuditLogs.Any(audit => + audit.TenantId == subscription.TenantId && + audit.Action == "system_scope.completed" && + audit.TargetId != null && + audit.TargetId.StartsWith("feature-usage-periodic-") && + audit.CreatedAt >= cutoff)) + .OrderBy(subscription => subscription.UpdatedAt) + .Select(subscription => subscription.TenantId) + .Distinct() + .Take(Math.Clamp(options.Value.BatchSize, 1, 1000)) + .ToArrayAsync(cancellationToken); + + foreach (var tenantId in candidates) + { + await ReconcileTenantAsync( + new ReconcileFeatureUsageRequest( + tenantId, + SystemScopeCallerType.Worker, + nameof(FeatureUsageReconciliationService), + "Periodic tenant feature usage reconciliation", + $"feature-usage-periodic-{tenantId:N}-{now:yyyyMMddHHmmss}"), + cancellationToken); + } + + return candidates.Length; + } + + private static async Task> ReconcileCoreAsync( + TikuDbContext dbContext, + Guid tenantId, + CancellationToken cancellationToken) + { + var now = DateTimeOffset.UtcNow; + var subscription = await dbContext.TenantSaasSubscriptions.AsNoTracking() + .Where(item => item.TenantId == tenantId && + (item.Status == TenantSaasSubscriptionStatus.Trial || + item.Status == TenantSaasSubscriptionStatus.Active) && + item.StartsAt <= now && + item.CurrentPeriodEnd > now) + .OrderByDescending(item => item.UpdatedAt) + .Select(item => new + { + item.Id, + item.BaseOfferingVersionId, + item.CurrentPeriodStart, + item.CurrentPeriodEnd + }) + .FirstOrDefaultAsync(cancellationToken); + if (subscription is null) + { + return []; + } + + var versionIds = await dbContext.TenantSaasSubscriptionItems.AsNoTracking() + .Where(item => item.TenantId == tenantId && + item.SubscriptionId == subscription.Id && + item.Status == TenantSaasSubscriptionItemStatus.Active && + item.StartsAt <= now && + item.EndsAt > now) + .Select(item => item.OfferingVersionId) + .ToArrayAsync(cancellationToken); + if (!versionIds.Contains(subscription.BaseOfferingVersionId)) + { + versionIds = [.. versionIds, subscription.BaseOfferingVersionId]; + } + + var limits = await dbContext.SaasOfferingVersionLimits.AsNoTracking() + .Where(item => versionIds.Contains(item.OfferingVersionId) && CurrentMetrics.Contains(item.MetricCode)) + .GroupBy(item => item.MetricCode) + .Select(group => new { MetricCode = group.Key, LimitValue = group.Sum(item => item.LimitValue) }) + .ToDictionaryAsync(item => item.MetricCode, item => item.LimitValue, StringComparer.Ordinal, cancellationToken); + if (limits.Count == 0) + { + return []; + } + + var actual = new Dictionary(StringComparer.Ordinal) + { + [SaasQuotaMetricCatalog.StaffCount] = await dbContext.TenantMemberships.AsNoTracking() + .Where(item => item.TenantId == tenantId && + item.Status == MembershipStatus.Active && + item.Role != TenantRole.Student) + .Select(item => item.UserId) + .Distinct() + .LongCountAsync(cancellationToken), + [SaasQuotaMetricCatalog.StudentCount] = await dbContext.TenantMemberships.AsNoTracking() + .Where(item => item.TenantId == tenantId && + item.Status == MembershipStatus.Active && + item.Role == TenantRole.Student) + .Select(item => item.UserId) + .Distinct() + .LongCountAsync(cancellationToken), + [SaasQuotaMetricCatalog.PrivateQuestionCount] = await dbContext.Questions.AsNoTracking() + .LongCountAsync(item => item.TenantId == tenantId && item.Status != QuestionStatus.Archived, cancellationToken), + [SaasQuotaMetricCatalog.StorageBytes] = await dbContext.ContentAssets.AsNoTracking() + .Where(item => item.TenantId == tenantId && + item.Status == ContentStatus.Active && + item.VerifiedSizeBytes > 0) + .SumAsync(item => item.VerifiedSizeBytes ?? 0, cancellationToken) + }; + + var existing = await dbContext.TenantFeatureUsages + .Where(item => item.TenantId == tenantId && + item.PeriodStart == subscription.CurrentPeriodStart && + item.PeriodEnd == subscription.CurrentPeriodEnd && + CurrentMetrics.Contains(item.MetricCode)) + .ToDictionaryAsync(item => item.MetricCode, StringComparer.Ordinal, cancellationToken); + var result = new List(); + foreach (var limit in limits.OrderBy(item => item.Key, StringComparer.Ordinal)) + { + var actualValue = actual[limit.Key]; + var warning = limit.Value == 0 || actualValue * 100 >= limit.Value * 80; + var exceeded = actualValue >= limit.Value; + var isNew = !existing.TryGetValue(limit.Key, out var usage); + if (isNew) + { + usage = new TenantFeatureUsage + { + TenantId = tenantId, + MetricCode = limit.Key, + PeriodStart = subscription.CurrentPeriodStart, + PeriodEnd = subscription.CurrentPeriodEnd, + Version = 1 + }; + dbContext.TenantFeatureUsages.Add(usage); + } + + var trackedUsage = usage!; + if (trackedUsage.UsedValue != actualValue || + trackedUsage.LimitValueSnapshot != limit.Value || + trackedUsage.WarningIssued != warning) + { + trackedUsage.UsedValue = actualValue; + trackedUsage.LimitValueSnapshot = limit.Value; + trackedUsage.WarningIssued = warning; + trackedUsage.Version = isNew ? 1 : trackedUsage.Version + 1; + } + result.Add(new ReconciledFeatureUsage(limit.Key, actualValue, limit.Value, warning, exceeded)); + } + + await dbContext.SaveChangesAsync(cancellationToken); + return result; + } +} diff --git a/Tiku.Infrastructure/Tenancy/TenantFrontendConfigService.cs b/Tiku.Infrastructure/Tenancy/TenantFrontendConfigService.cs index 4e546ff..b042902 100644 --- a/Tiku.Infrastructure/Tenancy/TenantFrontendConfigService.cs +++ b/Tiku.Infrastructure/Tenancy/TenantFrontendConfigService.cs @@ -2,7 +2,9 @@ using System.Text.Json; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Tiku.Application.Tenancy; +using Tiku.Application.Security; using Tiku.Domain.Common; +using Tiku.Domain.Platform; using Tiku.Domain.Tenancy; using Tiku.Infrastructure.Persistence; @@ -10,7 +12,8 @@ namespace Tiku.Infrastructure.Tenancy; public sealed class TenantFrontendConfigService( TikuDbContext dbContext, - IMemoryCache cache) : ITenantFrontendConfigService + IMemoryCache cache, + IFeatureAccessService featureAccessService) : ITenantFrontendConfigService { private static readonly TimeSpan RuntimeCacheDuration = TimeSpan.FromMinutes(2); @@ -90,6 +93,16 @@ public sealed class TenantFrontendConfigService( item => item.Id == tenantId && item.Status == TenantStatus.Active, cancellationToken) ?? throw new TenantFrontendConfigException("tenant_not_found", "Active tenant was not found."); + var now = DateTimeOffset.UtcNow; + var hasActiveSubscription = await dbContext.TenantSaasSubscriptions.AsNoTracking().AnyAsync(item => + item.TenantId == tenantId && + (item.Status == TenantSaasSubscriptionStatus.Trial || item.Status == TenantSaasSubscriptionStatus.Active) && + item.StartsAt <= now && item.CurrentPeriodEnd > now, + cancellationToken); + if (!hasActiveSubscription) + { + throw new TenantFrontendConfigException("subscription_inactive", "An active tenant subscription is required."); + } var config = await dbContext.TenantFrontendConfigs.AsNoTracking() .SingleOrDefaultAsync(item => item.TenantId == tenantId, cancellationToken) ?? CreateDefault(tenantId); @@ -102,7 +115,20 @@ public sealed class TenantFrontendConfigService( config.PublishedTheme.Clone(), config.PublishedFeatures.Clone(), config.PublishedNavigation.Clone(), - config.PublishedHomeModules.Clone()); + config.PublishedHomeModules.Clone(), + (await featureAccessService.GetEnabledFeaturesAsync(tenantId, FeatureAccessOperation.Read, cancellationToken)) + .Where(code => code != SaasFeatureCatalog.CoreBackoffice) + .Order(StringComparer.Ordinal) + .ToArray(), + (await dbContext.TenantAuthPolicies.AsNoTracking() + .Where(item => item.TenantId == tenantId) + .Select(item => item.AllowedStudentLoginMethods) + .SingleOrDefaultAsync(cancellationToken) ?? ["password"]) + .Select(value => value.Trim().ToLowerInvariant()) + .Where(value => value.Length > 0) + .Distinct(StringComparer.Ordinal) + .Order(StringComparer.Ordinal) + .ToArray()); cache.Set(CacheKey(tenantId), result, RuntimeCacheDuration); return result; } diff --git a/Tiku.Infrastructure/Tenancy/TenantOnboardingService.cs b/Tiku.Infrastructure/Tenancy/TenantOnboardingService.cs new file mode 100644 index 0000000..d84d4b3 --- /dev/null +++ b/Tiku.Infrastructure/Tenancy/TenantOnboardingService.cs @@ -0,0 +1,81 @@ +using Microsoft.EntityFrameworkCore; +using Tiku.Application.Security; +using Tiku.Application.Tenancy; +using Tiku.Domain.Platform; +using Tiku.Domain.Tenancy; +using Tiku.Infrastructure.Persistence; + +namespace Tiku.Infrastructure.Tenancy; + +internal sealed class TenantOnboardingService( + TikuDbContext dbContext, + IFeatureAccessService featureAccessService) : ITenantOnboardingService +{ + public async Task GetStatusAsync( + Guid tenantId, + CancellationToken cancellationToken = default) + { + var tenant = await dbContext.Tenants.AsNoTracking().SingleOrDefaultAsync(value => value.Id == tenantId, cancellationToken) + ?? throw new TenantExternalProviderException("Tenant was not found.", "tenant_not_found"); + var ownerActivated = tenant.OwnerUserId.HasValue && await ( + from membership in dbContext.TenantMemberships.AsNoTracking() + join user in dbContext.Users.AsNoTracking() on membership.UserId equals user.Id + where membership.TenantId == tenantId && membership.UserId == tenant.OwnerUserId && + membership.Role == TenantRole.TenantOwner && membership.Status == MembershipStatus.Active && + user.Status == Tiku.Domain.Identity.UserStatus.Active && !user.ForcePasswordChange + select membership.Id).AnyAsync(cancellationToken); + var now = DateTimeOffset.UtcNow; + var subscriptionActive = await dbContext.TenantSaasSubscriptions.AsNoTracking().AnyAsync(value => + value.TenantId == tenantId && + (value.Status == TenantSaasSubscriptionStatus.Trial || value.Status == TenantSaasSubscriptionStatus.Active) && + value.StartsAt <= now && value.CurrentPeriodEnd > now, cancellationToken); + var primaryDomainActive = await dbContext.TenantDomains.AsNoTracking().AnyAsync(value => + value.TenantId == tenantId && value.IsPrimary && value.Status == TenantDomainStatus.Active, cancellationToken); + var frontendPublished = await dbContext.TenantFrontendConfigs.AsNoTracking().AnyAsync(value => + value.TenantId == tenantId && value.PublishedAt != null, cancellationToken); + var loginMethods = await dbContext.TenantAuthPolicies.AsNoTracking() + .Where(value => value.TenantId == tenantId) + .Select(value => value.AllowedStudentLoginMethods) + .SingleOrDefaultAsync(cancellationToken) ?? ["password"]; + var hasLoginMethod = loginMethods.Any(value => !string.IsNullOrWhiteSpace(value)); + + var enabledFeatures = await featureAccessService.GetEnabledFeaturesAsync(tenantId, FeatureAccessOperation.Read, cancellationToken); + var paymentRequired = enabledFeatures.Contains(SaasFeatureCatalog.StudentStore); + var storageRequired = enabledFeatures.Overlaps(new[] + { + SaasFeatureCatalog.PrivateQuestionBank, + SaasFeatureCatalog.Video, + SaasFeatureCatalog.Handbook + }); + var smsRequired = loginMethods.Any(value => string.Equals(value, "sms", StringComparison.OrdinalIgnoreCase)); + var providers = await dbContext.TenantExternalProviders.AsNoTracking() + .Where(value => value.TenantId == tenantId && value.Status == TenantExternalProviderStatus.Active) + .Select(value => value.Capability) + .Distinct() + .ToArrayAsync(cancellationToken); + + var steps = new List + { + new("tenant_active", true, tenant.Status == TenantStatus.Active, tenant.Status.ToString()), + new("owner_activated", true, ownerActivated, tenant.OwnerUserId?.ToString()), + new("subscription_active", true, subscriptionActive, null), + new("primary_domain_active", true, primaryDomainActive, null), + new("frontend_config_published", true, frontendPublished, null), + new("student_login_configured", true, hasLoginMethod, string.Join(',', loginMethods)), + ProviderStep("object_storage_provider", TenantExternalProviderCapability.ObjectStorage, storageRequired, providers), + ProviderStep("sms_provider", TenantExternalProviderCapability.Sms, smsRequired, providers), + ProviderStep("payment_provider", TenantExternalProviderCapability.Payment, paymentRequired, providers), + ProviderStep("notification_provider", TenantExternalProviderCapability.Notification, false, providers) + }; + var required = steps.Count(value => value.Required); + var completed = steps.Count(value => value.Required && value.Completed); + return new TenantOnboardingStatus(tenantId, completed == required, completed, required, steps); + } + + private static TenantOnboardingStep ProviderStep( + string code, + TenantExternalProviderCapability capability, + bool required, + IReadOnlyCollection providers) => + new(code, required, providers.Contains(capability), providers.Contains(capability) ? "active" : "missing"); +} diff --git a/Tiku.Infrastructure/TenantAdmin/TenantAdminDirectService.cs b/Tiku.Infrastructure/TenantAdmin/TenantAdminDirectService.cs index 0ad0754..552507b 100644 --- a/Tiku.Infrastructure/TenantAdmin/TenantAdminDirectService.cs +++ b/Tiku.Infrastructure/TenantAdmin/TenantAdminDirectService.cs @@ -30,7 +30,8 @@ public sealed class TenantAdminDirectService( INotificationProvider notificationProvider, ICurrentAccessContext currentAccessContext, IAuthSessionStore sessionStore, - ISecurityEventPublisher securityEventPublisher) : ITenantAdminDirectService + ISecurityEventPublisher securityEventPublisher, + IFeatureAccessService featureAccessService) : ITenantAdminDirectService { public async Task GetOverviewAsync( TenantAdminActor actor, @@ -307,6 +308,9 @@ public sealed class TenantAdminDirectService( { var scope = await RequireDataScopeAsync(actor, cancellationToken); await AssertClassAsync(actor, scope, command.ClassId, cancellationToken); + await using var transaction = dbContext.Database.CurrentTransaction is null + ? await dbContext.Database.BeginTransactionAsync(cancellationToken) + : null; var memberType = ParseEnum(command.MemberType, TenantClassMemberType.Student, "invalid_class_member_type"); var status = ParseEnum(command.Status, TenantClassMemberStatus.Active, "invalid_class_member_status"); var user = await ResolveUserAsync(command.User, memberType == TenantClassMemberType.Student ? "student" : "teacher", cancellationToken); @@ -342,6 +346,10 @@ public sealed class TenantAdminDirectService( await AddAuditAsync(actor, "tenant.class_member.upserted", "tenant_class_members", item.Id, cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); + if (transaction is not null) + { + await transaction.CommitAsync(cancellationToken); + } return new ContentManagementResult(ToClassMemberItem(item, ToUserSummary(user))); } @@ -502,6 +510,9 @@ public sealed class TenantAdminDirectService( await AssertReferenceAsync(actor.TenantId, command.RegionId, "region_not_found", cancellationToken); await AssertReferenceAsync(actor.TenantId, command.SelectedSchoolId, "school_not_found", cancellationToken); await AssertReferenceAsync(actor.TenantId, command.SelectedMajorId, "major_not_found", cancellationToken); + await using var transaction = dbContext.Database.CurrentTransaction is null + ? await dbContext.Database.BeginTransactionAsync(cancellationToken) + : null; var user = await ResolveUserAsync(command.User, "student", cancellationToken); if (!scope.AllowsResource(actor.UserId, user.Id, command.RegionId)) { @@ -527,6 +538,10 @@ public sealed class TenantAdminDirectService( await AddAuditAsync(actor, "tenant.student.upserted", "student_profiles", profile.Id, cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); + if (transaction is not null) + { + await transaction.CommitAsync(cancellationToken); + } return new ContentManagementResult( ToStudentItem( @@ -572,6 +587,21 @@ public sealed class TenantAdminDirectService( throw new TenantAdminDirectException("Student membership was not found.", "student_not_found"); } + await using var transaction = dbContext.Database.CurrentTransaction is null + ? await dbContext.Database.BeginTransactionAsync(cancellationToken) + : null; + var wasCounted = await IsUserCountedForMetricAsync( + actor.TenantId, membership.UserId, SaasQuotaMetricCatalog.StudentCount, cancellationToken); + var otherMembershipCounted = await IsUserCountedForMetricAsync( + actor.TenantId, membership.UserId, SaasQuotaMetricCatalog.StudentCount, cancellationToken, membership.Id); + var willBeCounted = otherMembershipCounted || status == MembershipStatus.Active; + if (!wasCounted && willBeCounted) + { + await featureAccessService.ConsumeQuotaIfConfiguredAsync( + actor.TenantId, + SaasQuotaMetricCatalog.StudentCount, + cancellationToken: cancellationToken); + } membership.Status = status; if (status != MembershipStatus.Active) { @@ -581,6 +611,18 @@ public sealed class TenantAdminDirectService( await AddAuditAsync(actor, "tenant.student.status_updated", "tenant_memberships", membership.Id, cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); + if (wasCounted && !willBeCounted) + { + await featureAccessService.ReleaseQuotaAsync( + actor.TenantId, + SaasQuotaMetricCatalog.StudentCount, + 1, + cancellationToken); + } + if (transaction is not null) + { + await transaction.CommitAsync(cancellationToken); + } return new ContentManagementResult( new TenantAdminStudentStatusItem(membership.Id, membership.UserId, membership.Role, membership.Status, membership.UpdatedAt)); } @@ -607,6 +649,9 @@ public sealed class TenantAdminDirectService( var scope = await RequireDataScopeAsync(actor, cancellationToken); var previewItems = await BuildStudentImportPreviewAsync(actor, scope, command, cancellationToken); var invalidItems = previewItems.Where(item => !item.Valid).ToArray(); + await using var transaction = dbContext.Database.CurrentTransaction is null + ? await dbContext.Database.BeginTransactionAsync(cancellationToken) + : null; var createdOrUpdated = 0; var classAssigned = 0; var rowNo = 0; @@ -654,6 +699,10 @@ public sealed class TenantAdminDirectService( await AddAuditAsync(actor, "tenant.student.imported", "student_profiles", actor.TenantId, cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); + if (transaction is not null) + { + await transaction.CommitAsync(cancellationToken); + } return new TenantAdminStudentImportResult(command.Rows.Count, createdOrUpdated, classAssigned, invalidItems); } @@ -1127,6 +1176,9 @@ public sealed class TenantAdminDirectService( CancellationToken cancellationToken = default) { await RequireAllDataScopeAsync(actor, cancellationToken); + await using var transaction = dbContext.Database.CurrentTransaction is null + ? await dbContext.Database.BeginTransactionAsync(cancellationToken) + : null; var role = ParseEnum(command.Role, TenantRole.Student, "invalid_member_role"); var status = ParseEnum(command.Status, MembershipStatus.Active, "invalid_member_status"); await AssertGrantableAsync(actor, role, cancellationToken); @@ -1157,11 +1209,37 @@ public sealed class TenantAdminDirectService( var isNew = membership is null; var previousStatus = membership?.Status.ToString() ?? "none"; + var wasStaffCounted = await IsUserCountedForMetricAsync( + actor.TenantId, user.Id, SaasQuotaMetricCatalog.StaffCount, cancellationToken); + var wasStudentCounted = await IsUserCountedForMetricAsync( + actor.TenantId, user.Id, SaasQuotaMetricCatalog.StudentCount, cancellationToken); + Guid? excludedMembershipId = isNew ? null : membership!.Id; + var otherStaffCounted = await IsUserCountedForMetricAsync( + actor.TenantId, user.Id, SaasQuotaMetricCatalog.StaffCount, cancellationToken, excludedMembershipId); + var otherStudentCounted = await IsUserCountedForMetricAsync( + actor.TenantId, user.Id, SaasQuotaMetricCatalog.StudentCount, cancellationToken, excludedMembershipId); + var willStaffBeCounted = otherStaffCounted || (status == MembershipStatus.Active && role != TenantRole.Student); + var willStudentBeCounted = otherStudentCounted || (status == MembershipStatus.Active && role == TenantRole.Student); if (membership?.Role == TenantRole.TenantOwner && role != TenantRole.TenantOwner) { throw new TenantAdminDirectException("Tenant owner membership cannot be downgraded.", "tenant_owner_required"); } + if (!wasStaffCounted && willStaffBeCounted) + { + await featureAccessService.ConsumeQuotaIfConfiguredAsync( + actor.TenantId, + SaasQuotaMetricCatalog.StaffCount, + cancellationToken: cancellationToken); + } + if (!wasStudentCounted && willStudentBeCounted) + { + await featureAccessService.ConsumeQuotaIfConfiguredAsync( + actor.TenantId, + SaasQuotaMetricCatalog.StudentCount, + cancellationToken: cancellationToken); + } + membership ??= new TenantMembership { TenantId = actor.TenantId, @@ -1189,6 +1267,26 @@ public sealed class TenantAdminDirectService( actor.TenantId, user.Id, previousStatus, status.ToString(), $"tenant-member-{membership.Id:N}", cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); + if (wasStaffCounted && !willStaffBeCounted) + { + await featureAccessService.ReleaseQuotaAsync( + actor.TenantId, + SaasQuotaMetricCatalog.StaffCount, + 1, + cancellationToken); + } + if (wasStudentCounted && !willStudentBeCounted) + { + await featureAccessService.ReleaseQuotaAsync( + actor.TenantId, + SaasQuotaMetricCatalog.StudentCount, + 1, + cancellationToken); + } + if (transaction is not null) + { + await transaction.CommitAsync(cancellationToken); + } return new ContentManagementResult(ToMemberItem(membership, user)); } @@ -1198,6 +1296,9 @@ public sealed class TenantAdminDirectService( CancellationToken cancellationToken = default) { await RequireAllDataScopeAsync(actor, cancellationToken); + await using var transaction = dbContext.Database.CurrentTransaction is null + ? await dbContext.Database.BeginTransactionAsync(cancellationToken) + : null; var membership = await dbContext.TenantMemberships.FirstOrDefaultAsync( item => item.TenantId == actor.TenantId && item.Id == membershipId, cancellationToken); @@ -1213,6 +1314,11 @@ public sealed class TenantAdminDirectService( await AssertGrantableAsync(actor, membership.Role, cancellationToken); var previousStatus = membership.Status; + var metricCode = QuotaMetricForRole(membership.Role); + var wasCounted = await IsUserCountedForMetricAsync( + actor.TenantId, membership.UserId, metricCode, cancellationToken); + var otherMembershipCounted = await IsUserCountedForMetricAsync( + actor.TenantId, membership.UserId, metricCode, cancellationToken, membership.Id); membership.Status = MembershipStatus.Disabled; await RevokeSessionsAsync(actor.TenantId, membership.UserId, cancellationToken); await AddAuditAsync(actor, "tenant.member.disabled", "tenant_memberships", membership.Id, cancellationToken); @@ -1220,6 +1326,18 @@ public sealed class TenantAdminDirectService( actor.TenantId, membership.UserId, previousStatus.ToString(), MembershipStatus.Disabled.ToString(), $"tenant-member-{membership.Id:N}", cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); + if (previousStatus == MembershipStatus.Active && wasCounted && !otherMembershipCounted) + { + await featureAccessService.ReleaseQuotaAsync( + actor.TenantId, + metricCode, + 1, + cancellationToken); + } + if (transaction is not null) + { + await transaction.CommitAsync(cancellationToken); + } var user = await dbContext.Users.AsNoTracking().SingleAsync(item => item.Id == membership.UserId, cancellationToken); return new ContentManagementResult(ToMemberItem(membership, user)); } @@ -1995,6 +2113,14 @@ public sealed class TenantAdminDirectService( cancellationToken); if (membership is null) { + var metricCode = QuotaMetricForRole(role); + if (!await IsUserCountedForMetricAsync(tenantId, userId, metricCode, cancellationToken)) + { + await featureAccessService.ConsumeQuotaIfConfiguredAsync( + tenantId, + metricCode, + cancellationToken: cancellationToken); + } membership = new TenantMembership { TenantId = tenantId, @@ -2006,12 +2132,48 @@ public sealed class TenantAdminDirectService( } else { + if (membership.Status != MembershipStatus.Active) + { + var metricCode = QuotaMetricForRole(role); + if (!await IsUserCountedForMetricAsync(tenantId, userId, metricCode, cancellationToken)) + { + await featureAccessService.ConsumeQuotaIfConfiguredAsync( + tenantId, + metricCode, + cancellationToken: cancellationToken); + } + } membership.Status = MembershipStatus.Active; } return membership; } + private static string QuotaMetricForRole(TenantRole role) => role == TenantRole.Student + ? SaasQuotaMetricCatalog.StudentCount + : SaasQuotaMetricCatalog.StaffCount; + + private Task IsUserCountedForMetricAsync( + Guid tenantId, + Guid userId, + string metricCode, + CancellationToken cancellationToken, + Guid? excludedMembershipId = null) + { + var query = dbContext.TenantMemberships.AsNoTracking().Where(item => + item.TenantId == tenantId && + item.UserId == userId && + item.Status == MembershipStatus.Active); + if (excludedMembershipId.HasValue) + { + query = query.Where(item => item.Id != excludedMembershipId.Value); + } + + return metricCode == SaasQuotaMetricCatalog.StudentCount + ? query.AnyAsync(item => item.Role == TenantRole.Student, cancellationToken) + : query.AnyAsync(item => item.Role != TenantRole.Student, cancellationToken); + } + private async Task EnsureStudentProfileAsync( Guid tenantId, Guid userId, @@ -2347,7 +2509,7 @@ public sealed class TenantAdminDirectService( Code = permissionCode, Name = permissionCode, Area = BackendPermissionArea.Tenant, - Module = permissionCode.Split(':')[1], + PermissionModuleCode = PermissionModuleCatalog.ResolvePermissionModuleCode(permissionCode), IsSystem = true }); } diff --git a/Tiku.IntegrationTests/Api/ApiTestFactory.cs b/Tiku.IntegrationTests/Api/ApiTestFactory.cs index 774f998..1d5a450 100644 --- a/Tiku.IntegrationTests/Api/ApiTestFactory.cs +++ b/Tiku.IntegrationTests/Api/ApiTestFactory.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Npgsql; using System.Text.Json; using Tiku.Application.Commerce; +using Tiku.Application.PlatformBilling; using Tiku.Application.Auth; using Tiku.Application.Growth; using Tiku.Application.Storage; @@ -29,6 +30,7 @@ public sealed class ApiTestFactory( IObjectStorageService? objectStorageService = null, IReferralQrcodeGenerator? referralQrcodeGenerator = null, IPaymentProviderGateway? paymentProviderGateway = null, + IPlatformBillingPaymentGateway? platformBillingPaymentGateway = null, IDomainOwnershipVerifier? domainOwnershipVerifier = null, IDomainGatewayProvisioner? domainGatewayProvisioner = null, ISmsProvider? smsProvider = null, @@ -110,6 +112,12 @@ public sealed class ApiTestFactory( services.AddSingleton(paymentProviderGateway); } + if (platformBillingPaymentGateway is not null) + { + services.RemoveAll(); + services.AddSingleton(platformBillingPaymentGateway); + } + if (domainOwnershipVerifier is not null) { services.RemoveAll(); @@ -138,54 +146,160 @@ public sealed class ApiTestFactory( var dbContext = scope.ServiceProvider.GetRequiredService(); var tenants = entities.OfType().Where(tenant => tenant.Mode == TenantMode.Saas).ToArray(); var hasExplicitCapabilitySetup = entities.Any(entity => - entity is PlatformSaasPlan or ProductModule or PlanModuleEntitlement or TenantSubscription); + entity is SaasOffering or SaasOfferingVersion or TenantSaasSubscription); if (tenants.Length > 0 && !hasExplicitCapabilitySetup) { - const string integrationPlanCode = "integration-full-access"; - if (!await dbContext.PlatformSaasPlans.AnyAsync(plan => plan.Code == integrationPlanCode)) - { - dbContext.PlatformSaasPlans.Add(new PlatformSaasPlan + const string integrationOfferingCode = "integration-full-access"; + var existingFeatures = await dbContext.SaasFeatures.Select(feature => feature.Code).ToArrayAsync(); + dbContext.SaasFeatures.AddRange(SaasFeatureCatalog.All + .Except(existingFeatures, StringComparer.Ordinal) + .Select((code, index) => new SaasFeature { - Code = integrationPlanCode, - Name = "Integration Full Access" - }); - } - - var existingModules = await dbContext.ProductModules - .Select(module => module.Code) - .ToArrayAsync(); - foreach (var module in ProductModuleCatalog.All.Where(module => !existingModules.Contains(module.Key))) + Code = code, + Name = code, + Category = code.Split('.')[0], + IsCore = code == SaasFeatureCatalog.CoreBackoffice, + Status = SaasFeatureStatus.Active, + SortOrder = index * 10 + })); + var offering = await dbContext.SaasOfferings.SingleOrDefaultAsync(value => value.Code == integrationOfferingCode); + if (offering is null) { - dbContext.ProductModules.Add(new ProductModule { Code = module.Key, Name = module.Value }); + offering = new SaasOffering + { + Code = integrationOfferingCode, + Name = "Integration Full Access", + Type = SaasOfferingType.BasePlan, + Status = SaasOfferingStatus.Active + }; + dbContext.SaasOfferings.Add(offering); + } + var version = await dbContext.SaasOfferingVersions.SingleOrDefaultAsync(value => + value.OfferingId == offering.Id && value.Version == 1); + if (version is null) + { + version = new SaasOfferingVersion + { + OfferingId = offering.Id, + Version = 1, + Status = SaasOfferingVersionStatus.Draft, + OriginalAmountCents = 100, + AmountCents = 100, + EffectiveAt = DateTimeOffset.UtcNow.AddDays(-1) + }; + dbContext.SaasOfferingVersions.Add(version); } await dbContext.SaveChangesAsync(); - - var entitledModules = await dbContext.PlanModuleEntitlements - .Where(entitlement => entitlement.PlanCode == integrationPlanCode) - .Select(entitlement => entitlement.ModuleCode) + var entitledFeatures = await dbContext.SaasOfferingVersionFeatures + .Where(entitlement => entitlement.OfferingVersionId == version.Id) + .Select(entitlement => entitlement.FeatureCode) .ToArrayAsync(); - foreach (var moduleCode in ProductModuleCatalog.All.Keys.Except(entitledModules, StringComparer.Ordinal)) - { - dbContext.PlanModuleEntitlements.Add(new PlanModuleEntitlement + dbContext.SaasOfferingVersionFeatures.AddRange(SaasFeatureCatalog.All + .Where(code => code != SaasFeatureCatalog.CoreBackoffice) + .Except(entitledFeatures, StringComparer.Ordinal) + .Select(code => new SaasOfferingVersionFeature { - PlanCode = integrationPlanCode, - ModuleCode = moduleCode - }); - } + OfferingVersionId = version.Id, + FeatureCode = code + })); await dbContext.SaveChangesAsync(); + if (version.Status == SaasOfferingVersionStatus.Draft) + { + version.Status = SaasOfferingVersionStatus.Published; + version.PublishedAt = DateTimeOffset.UtcNow.AddDays(-1); + await dbContext.SaveChangesAsync(); + } var now = DateTimeOffset.UtcNow; - entities = entities.Concat(tenants.Select(tenant => new TenantSubscription + var subscriptionGraphs = tenants.SelectMany(tenant => { - TenantId = tenant.Id, - PlanCode = integrationPlanCode, - Status = TenantSubscriptionStatus.Active, - StartsAt = now.AddDays(-1), - ExpiresAt = now.AddYears(1) - })).ToArray(); + var subscription = new TenantSaasSubscription + { + TenantId = tenant.Id, + BaseOfferingVersionId = version.Id, + Status = TenantSaasSubscriptionStatus.Active, + StartsAt = now.AddDays(-1), + CurrentPeriodStart = now.AddDays(-1), + CurrentPeriodEnd = now.AddYears(1) + }; + return new object[] + { + subscription, + new TenantSaasSubscriptionItem + { + TenantId = tenant.Id, + SubscriptionId = subscription.Id, + OfferingVersionId = version.Id, + ItemType = TenantSaasSubscriptionItemType.BasePlan, + Status = TenantSaasSubscriptionItemStatus.Active, + StartsAt = subscription.CurrentPeriodStart, + EndsAt = subscription.CurrentPeriodEnd + } + }; + }); + entities = entities.Concat(subscriptionGraphs).ToArray(); } + var explicitPermissions = entities.OfType().ToArray(); + if (explicitPermissions.Length > 0) + { + var moduleCodes = explicitPermissions.Select(value => value.PermissionModuleCode) + .Distinct(StringComparer.Ordinal) + .ToArray(); + var existingModuleCodes = await dbContext.PermissionModules + .Where(value => moduleCodes.Contains(value.Code)) + .Select(value => value.Code) + .ToArrayAsync(); + var suppliedModuleCodes = entities.OfType().Select(value => value.Code).ToArray(); + var modules = moduleCodes + .Except(existingModuleCodes, StringComparer.Ordinal) + .Except(suppliedModuleCodes, StringComparer.Ordinal) + .Select(code => new PermissionModule + { + Code = code, + Name = code, + Area = explicitPermissions.First(value => value.PermissionModuleCode == code).Area, + RequiredFeatureCode = PermissionModuleCatalog.RequiredFeatures.GetValueOrDefault(code) + }) + .ToArray(); + var requiredFeatureCodes = modules.Select(value => value.RequiredFeatureCode) + .Where(value => value is not null) + .Cast() + .Distinct(StringComparer.Ordinal) + .ToArray(); + var existingRequiredFeatures = await dbContext.SaasFeatures + .Where(value => requiredFeatureCodes.Contains(value.Code)) + .Select(value => value.Code) + .ToArrayAsync(); + var suppliedFeatureCodes = entities.OfType().Select(value => value.Code).ToArray(); + dbContext.SaasFeatures.AddRange(requiredFeatureCodes + .Except(existingRequiredFeatures, StringComparer.Ordinal) + .Except(suppliedFeatureCodes, StringComparer.Ordinal) + .Select(code => new SaasFeature + { + Code = code, + Name = code, + Category = code.Split('.')[0], + Status = SaasFeatureStatus.Active + })); + dbContext.PermissionModules.AddRange(modules); + await dbContext.SaveChangesAsync(); + } + var offeringVersionsToFinalize = entities.OfType() + .Where(value => value.Status != SaasOfferingVersionStatus.Draft) + .Select(value => new { Version = value, TargetStatus = value.Status }) + .ToArray(); + foreach (var item in offeringVersionsToFinalize) + { + item.Version.Status = SaasOfferingVersionStatus.Draft; + } + dbContext.AddRange(entities); await dbContext.SaveChangesAsync(); + foreach (var item in offeringVersionsToFinalize) + { + item.Version.Status = item.TargetStatus; + } + await dbContext.SaveChangesAsync(); var backendMembers = entities .OfType() @@ -206,6 +320,34 @@ public sealed class ApiTestFactory( IEnumerable<(Guid TenantId, Guid UserId)> members) { var permissionCodes = BackendPermissions.Tenant.Order(StringComparer.Ordinal).ToArray(); + var featureCodes = PermissionModuleCatalog.RequiredFeatures.Values + .Where(value => value is not null) + .Cast() + .Append(SaasFeatureCatalog.CoreBackoffice) + .Distinct(StringComparer.Ordinal) + .ToArray(); + var existingFeatureCodes = await dbContext.SaasFeatures + .Where(value => featureCodes.Contains(value.Code)) + .Select(value => value.Code) + .ToArrayAsync(); + dbContext.SaasFeatures.AddRange(featureCodes.Except(existingFeatureCodes, StringComparer.Ordinal).Select(code => new SaasFeature + { + Code = code, + Name = code, + Category = code.Split('.')[0], + IsCore = code == SaasFeatureCatalog.CoreBackoffice, + Status = SaasFeatureStatus.Active + })); + var moduleCodes = permissionCodes.Select(PermissionModuleCatalog.ResolvePermissionModuleCode).Distinct(StringComparer.Ordinal).ToArray(); + var existingModuleCodes = await dbContext.PermissionModules.Where(value => moduleCodes.Contains(value.Code)).Select(value => value.Code).ToArrayAsync(); + dbContext.PermissionModules.AddRange(moduleCodes.Except(existingModuleCodes, StringComparer.Ordinal).Select(code => new PermissionModule + { + Code = code, + Name = code, + Area = BackendPermissionArea.Tenant, + RequiredFeatureCode = PermissionModuleCatalog.RequiredFeatures[code] + })); + await dbContext.SaveChangesAsync(); var existingPermissionCodes = await dbContext.BackendPermissions .Where(permission => permissionCodes.Contains(permission.Code)) .Select(permission => permission.Code) @@ -217,7 +359,7 @@ public sealed class ApiTestFactory( Code = permissionCode, Name = permissionCode, Area = BackendPermissionArea.Tenant, - Module = permissionCode.Split(':')[1], + PermissionModuleCode = PermissionModuleCatalog.ResolvePermissionModuleCode(permissionCode), IsSystem = true }); } diff --git a/Tiku.IntegrationTests/Api/AssetManagementEndpointTests.cs b/Tiku.IntegrationTests/Api/AssetManagementEndpointTests.cs index 03179b8..d3132ce 100644 --- a/Tiku.IntegrationTests/Api/AssetManagementEndpointTests.cs +++ b/Tiku.IntegrationTests/Api/AssetManagementEndpointTests.cs @@ -1,12 +1,15 @@ using System.Net; using System.Net.Http.Json; using System.Text.Json; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Tiku.Api.Contracts; +using Tiku.Application.Security; using Tiku.Application.Storage; using Tiku.Domain.Common; using Tiku.Domain.Content; using Tiku.Domain.Identity; +using Tiku.Domain.Platform; using Tiku.Domain.Tenancy; using Tiku.Infrastructure.Auth; using Tiku.Infrastructure.Persistence; @@ -125,6 +128,54 @@ public sealed class AssetManagementEndpointTests Assert.Equal(seed.UserId, asset.VerifiedBy); Assert.Equal(2048, asset.VerifiedSizeBytes); Assert.Equal(new string('a', 64), asset.VerifiedChecksumSha256); + Assert.False(dbContext.TenantFeatureUsages.Any(value => + value.TenantId == seed.TenantId && value.MetricCode == SaasQuotaMetricCatalog.StorageBytes)); + } + + [Fact] + public async Task Verified_upload_consumes_storage_quota_and_archive_releases_it() + { + var storage = new FakeObjectStorageService { MetadataSizeBytes = 6 }; + await using var factory = new ApiTestFactory(objectStorageService: storage); + var seed = await SeedAdminWithStorageQuotaAsync(factory, 10); + var firstAssetId = Guid.NewGuid(); + var secondAssetId = Guid.NewGuid(); + await factory.SeedAsync( + PendingAsset(firstAssetId, seed.TenantId, "first.pdf"), + PendingAsset(secondAssetId, seed.TenantId, "second.pdf")); + using var client = factory.CreateClient(); + await LoginAsync(client, seed); + + using var firstConfirm = await client.PostAsJsonAsync( + "/api/tenant-content/assets/uploads/confirm", + new AssetUploadConfirmDto { AssetId = firstAssetId }); + Assert.Equal(HttpStatusCode.OK, firstConfirm.StatusCode); + Assert.Equal(6, await StorageUsageAsync(factory, seed.TenantId)); + + storage.MetadataSizeBytes = 5; + using var exhausted = await client.PostAsJsonAsync( + "/api/tenant-content/assets/uploads/confirm", + new AssetUploadConfirmDto { AssetId = secondAssetId }); + Assert.Equal(HttpStatusCode.Conflict, exhausted.StatusCode); + var exhaustedBody = await ReadJsonAsync(exhausted); + Assert.Equal("feature_quota_exhausted", exhaustedBody.RootElement.GetProperty("code").GetString()); + Assert.Equal(6, await StorageUsageAsync(factory, seed.TenantId)); + + using var archive = await client.DeleteAsync($"/api/tenant-content/assets/{firstAssetId}"); + Assert.Equal(HttpStatusCode.OK, archive.StatusCode); + Assert.Equal(0, await StorageUsageAsync(factory, seed.TenantId)); + + using var secondConfirm = await client.PostAsJsonAsync( + "/api/tenant-content/assets/uploads/confirm", + new AssetUploadConfirmDto { AssetId = secondAssetId }); + Assert.Equal(HttpStatusCode.OK, secondConfirm.StatusCode); + Assert.Equal(5, await StorageUsageAsync(factory, seed.TenantId)); + + using var repeatedConfirm = await client.PostAsJsonAsync( + "/api/tenant-content/assets/uploads/confirm", + new AssetUploadConfirmDto { AssetId = secondAssetId }); + Assert.Equal(HttpStatusCode.OK, repeatedConfirm.StatusCode); + Assert.Equal(5, await StorageUsageAsync(factory, seed.TenantId)); } [Fact] @@ -281,6 +332,140 @@ public sealed class AssetManagementEndpointTests return (tenantId, userId, phone); } + private static async Task<(Guid TenantId, Guid UserId, string Phone)> SeedAdminWithStorageQuotaAsync( + ApiTestFactory factory, + long storageLimit) + { + var tenantId = Guid.NewGuid(); + var userId = Guid.NewGuid(); + var phone = "13900000001"; + var offeringId = Guid.NewGuid(); + var versionId = Guid.NewGuid(); + var subscriptionId = Guid.NewGuid(); + var now = DateTimeOffset.UtcNow; + await factory.SeedAsync( + new Tenant + { + Id = tenantId, + Slug = tenantId.ToString("N"), + Name = "Storage Quota Tenant", + Status = TenantStatus.Active, + BillingStatus = BillingStatus.Active + }, + new User + { + Id = userId, + Phone = phone, + Name = "Storage Quota Admin" + }.WithTestPassword(), + new TenantMembership + { + TenantId = tenantId, + UserId = userId, + Role = TenantRole.TenantAdmin, + Status = MembershipStatus.Active + }, + new SaasFeature + { + Code = SaasFeatureCatalog.PrivateQuestionBank, + Name = "Private question bank", + Category = "content", + Status = SaasFeatureStatus.Active + }, + new SaasOffering + { + Id = offeringId, + Code = $"storage-quota-{tenantId:N}", + Name = "Storage Quota Plan", + Type = SaasOfferingType.BasePlan, + Status = SaasOfferingStatus.Active + }, + new SaasOfferingVersion + { + Id = versionId, + OfferingId = offeringId, + Version = 1, + Status = SaasOfferingVersionStatus.Draft, + BillingCycle = PlatformBillingCycle.Monthly, + OriginalAmountCents = 100, + AmountCents = 100, + EffectiveAt = now.AddDays(-1) + }, + new SaasOfferingVersionFeature + { + OfferingVersionId = versionId, + FeatureCode = SaasFeatureCatalog.PrivateQuestionBank + }, + new SaasFeatureLimitDefinition + { + MetricCode = SaasQuotaMetricCatalog.StorageBytes, + FeatureCode = SaasFeatureCatalog.PrivateQuestionBank, + Name = "Storage bytes", + Unit = "byte", + Kind = SaasFeatureLimitKind.Current, + WarningPercent = 80, + IsHardLimit = true + }, + new SaasOfferingVersionLimit + { + OfferingVersionId = versionId, + MetricCode = SaasQuotaMetricCatalog.StorageBytes, + LimitValue = storageLimit + }, + new TenantSaasSubscription + { + Id = subscriptionId, + TenantId = tenantId, + BaseOfferingVersionId = versionId, + Status = TenantSaasSubscriptionStatus.Active, + StartsAt = now.AddDays(-1), + CurrentPeriodStart = now.AddDays(-1), + CurrentPeriodEnd = now.AddMonths(1) + }, + new TenantSaasSubscriptionItem + { + TenantId = tenantId, + SubscriptionId = subscriptionId, + OfferingVersionId = versionId, + ItemType = TenantSaasSubscriptionItemType.BasePlan, + Status = TenantSaasSubscriptionItemStatus.Active, + StartsAt = now.AddDays(-1), + EndsAt = now.AddMonths(1) + }); + + using var scope = factory.CreateSystemScope("Publish storage quota fixture"); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var version = await dbContext.SaasOfferingVersions.SingleAsync(value => value.Id == versionId); + version.Status = SaasOfferingVersionStatus.Published; + version.PublishedAt = now; + await dbContext.SaveChangesAsync(); + return (tenantId, userId, phone); + } + + private static ContentAsset PendingAsset(Guid id, Guid tenantId, string fileName) => new() + { + Id = id, + TenantId = tenantId, + Title = fileName, + FileName = fileName, + StorageProvider = AssetStorageProvider.AliyunOss, + Bucket = "tenant-assets", + ObjectKey = $"{tenantId:N}/assets/{fileName}", + MimeType = "application/pdf", + AssetType = ContentAssetType.Pdf, + UploadStatus = AssetUploadStatus.Pending, + SecurityScanStatus = AssetSecurityScanStatus.Pending + }; + + private static async Task StorageUsageAsync(ApiTestFactory factory, Guid tenantId) + { + using var scope = factory.CreateSystemScope("Read storage quota usage"); + return await scope.ServiceProvider.GetRequiredService().TenantFeatureUsages + .Where(value => value.TenantId == tenantId && value.MetricCode == SaasQuotaMetricCatalog.StorageBytes) + .Select(value => value.UsedValue) + .SingleOrDefaultAsync(); + } + private static async Task LoginAsync( HttpClient client, (Guid TenantId, Guid UserId, string Phone) seed) @@ -296,7 +481,7 @@ public sealed class AssetManagementEndpointTests private sealed class FakeObjectStorageService : IObjectStorageService { - public long? MetadataSizeBytes { get; init; } + public long? MetadataSizeBytes { get; set; } public string? MetadataChecksumSha256 { get; init; } diff --git a/Tiku.IntegrationTests/Api/AuthSessionLifecycleTests.cs b/Tiku.IntegrationTests/Api/AuthSessionLifecycleTests.cs index c73633a..b351e12 100644 --- a/Tiku.IntegrationTests/Api/AuthSessionLifecycleTests.cs +++ b/Tiku.IntegrationTests/Api/AuthSessionLifecycleTests.cs @@ -167,7 +167,7 @@ public sealed class AuthSessionLifecycleTests Code = permissionCode, Name = permissionCode, Area = BackendPermissionArea.Tenant, - Module = "test" + PermissionModuleCode = "tenant_dashboard" }, role, new TenantBackendRolePermission diff --git a/Tiku.IntegrationTests/Api/AuthorizationManifestTests.cs b/Tiku.IntegrationTests/Api/AuthorizationManifestTests.cs index 18a0fcb..04d1716 100644 --- a/Tiku.IntegrationTests/Api/AuthorizationManifestTests.cs +++ b/Tiku.IntegrationTests/Api/AuthorizationManifestTests.cs @@ -16,8 +16,8 @@ namespace Tiku.IntegrationTests.Api; public sealed class AuthorizationManifestTests { - private const int ExpectedActionCount = 332; - private const string ExpectedSha256 = "a80fe477ba3021625e17c9fc639e5109bab678178f8024a51c3c732bf5a46d3f"; + private const int ExpectedActionCount = 358; + private const string ExpectedSha256 = "5a26d6f0817b2939eef921d1883ad0dc8f82405a1593b82df951e97654a0225d"; [Fact] public void Controller_authorization_surface_matches_reviewed_manifest() @@ -61,6 +61,13 @@ public sealed class AuthorizationManifestTests Assert.NotNull(metadata); Assert.False(string.IsNullOrWhiteSpace(metadata.AuditAction)); Assert.Contains(metadata.Realm, new[] { "authenticated", "tenant", "platform" }); + if (metadata.Realm == "tenant" && + metadata.Module is { } module && + PermissionModuleCatalog.RequiredFeatures.TryGetValue(module, out var requiredFeature) && + requiredFeature is not null) + { + Assert.Contains(requiredFeature, metadata.RequiredFeatures); + } } } @@ -88,6 +95,37 @@ public sealed class AuthorizationManifestTests } } + [Theory] + [InlineData("questions", SaasFeatureCatalog.PrivateQuestionBank)] + [InlineData("vocabulary", SaasFeatureCatalog.Vocabulary)] + [InlineData("handbook", SaasFeatureCatalog.Handbook)] + [InlineData("scoreline", SaasFeatureCatalog.Scoreline)] + [InlineData("videos", SaasFeatureCatalog.Video)] + public void Content_import_route_maps_to_an_explicit_feature(string importType, string expectedFeature) + { + Assert.Equal(expectedFeature, SaasFeatureCatalog.ResolveContentImportFeature(importType)); + } + + [Fact] + public void Tenant_content_permissions_are_split_by_purchasable_feature() + { + var expected = new Dictionary(StringComparer.Ordinal) + { + [BackendPermissions.TenantContentManage] = SaasFeatureCatalog.PrivateQuestionBank, + [BackendPermissions.TenantVocabularyManage] = SaasFeatureCatalog.Vocabulary, + [BackendPermissions.TenantHandbookManage] = SaasFeatureCatalog.Handbook, + [BackendPermissions.TenantVideoManage] = SaasFeatureCatalog.Video, + [BackendPermissions.TenantScorelineManage] = SaasFeatureCatalog.Scoreline, + [BackendPermissions.TenantSiteContentManage] = SaasFeatureCatalog.SiteContent + }; + + foreach (var pair in expected) + { + var module = PermissionModuleCatalog.ResolvePermissionModuleCode(pair.Key); + Assert.Equal(pair.Value, PermissionModuleCatalog.RequiredFeatures[module]); + } + } + private static string Describe(Type controller, MethodInfo action) { var controllerRoute = controller.GetCustomAttribute()?.Template ?? string.Empty; diff --git a/Tiku.IntegrationTests/Api/BackofficeUiBootstrapTests.cs b/Tiku.IntegrationTests/Api/BackofficeUiBootstrapTests.cs index 3ca9adb..5aa7767 100644 --- a/Tiku.IntegrationTests/Api/BackofficeUiBootstrapTests.cs +++ b/Tiku.IntegrationTests/Api/BackofficeUiBootstrapTests.cs @@ -47,7 +47,7 @@ public sealed class BackofficeUiBootstrapTests Code = BackendPermissions.TenantDashboardView, Name = "Tenant dashboard", Area = BackendPermissionArea.Tenant, - Module = "tenant_dashboard", + PermissionModuleCode = "tenant_dashboard", IsSystem = true }, new TenantBackendRole diff --git a/Tiku.IntegrationTests/Api/CapabilityAuthorizationTests.cs b/Tiku.IntegrationTests/Api/CapabilityAuthorizationTests.cs index a19e349..d57da37 100644 --- a/Tiku.IntegrationTests/Api/CapabilityAuthorizationTests.cs +++ b/Tiku.IntegrationTests/Api/CapabilityAuthorizationTests.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.DependencyInjection; using System.Text.Json; using Tiku.Application.Jobs; using Tiku.Application.Security; -using Tiku.Domain.Commerce; using Tiku.Domain.Operations; using Tiku.Domain.Platform; using Tiku.Domain.Tenancy; @@ -14,81 +13,182 @@ namespace Tiku.IntegrationTests.Api; public sealed class CapabilityAuthorizationTests { [Fact] - public async Task Background_job_rechecks_capability_after_enqueue_before_execution() + public async Task Background_job_rechecks_feature_access_after_enqueue_before_execution() { await using var factory = new ApiTestFactory(); var tenantId = Guid.NewGuid(); - await factory.SeedAsync( - new Tenant { Id = tenantId, Slug = tenantId.ToString("N"), Name = "Job Capability Tenant" }, - new PlatformSaasPlan { Code = "job-capability-test", Name = "Job Capability Test" }, - new PlanModuleEntitlement { PlanCode = "job-capability-test", ModuleCode = "content" }, - new TenantSubscription - { - TenantId = tenantId, - PlanCode = "job-capability-test", - Status = TenantSubscriptionStatus.Active, - StartsAt = DateTimeOffset.UtcNow.AddDays(-1), - ExpiresAt = DateTimeOffset.UtcNow.AddDays(30) - }); + var fixture = CreateSubscriptionFixture(tenantId, SaasFeatureCatalog.PrivateQuestionBank, includeEntitlement: true); + await factory.SeedAsync(fixture.Entities); - using var scope = factory.CreateSystemScope("Verify job capability at consumption"); + using var scope = factory.CreateSystemScope("Verify job feature access at consumption"); var jobs = scope.ServiceProvider.GetRequiredService(); var job = await jobs.EnqueueAsync(new CreateBackgroundJobCommand( tenantId, "content_export", - JsonSerializer.SerializeToElement(new { exportType = "capability-test" }))); + JsonSerializer.SerializeToElement(new { exportType = "feature-test" }))); var dbContext = scope.ServiceProvider.GetRequiredService(); - dbContext.TenantModuleOverrides.Add(new TenantModuleOverride + dbContext.TenantFeatureOverrides.Add(new TenantFeatureOverride { TenantId = tenantId, - ModuleCode = "content", - Mode = TenantModuleOverrideMode.Disabled, + FeatureCode = SaasFeatureCatalog.PrivateQuestionBank, + Mode = TenantFeatureOverrideMode.Disabled, Reason = "Integration test revocation" }); await dbContext.SaveChangesAsync(); Assert.True(await jobs.ProcessRequestedAsync( - job.Id, tenantId, job.JobType, "capability-test-worker")); + job.Id, tenantId, job.JobType, "feature-test-worker")); var stored = await dbContext.BackgroundJobs.AsNoTracking().SingleAsync(item => item.Id == job.Id); Assert.Equal(BackgroundJobStatus.Failed, stored.Status); - Assert.Equal("Tenant capability was revoked before job execution.", stored.LastError); + Assert.Equal("Tenant feature entitlement was revoked before job execution.", stored.LastError); } [Fact] - public async Task Entitlement_is_database_backed_and_past_due_is_read_only() + public async Task Feature_access_is_database_backed_supports_overrides_and_makes_past_due_read_only() { await using var factory = new ApiTestFactory(); var tenantId = Guid.NewGuid(); - await factory.SeedAsync( - new Tenant { Id = tenantId, Slug = tenantId.ToString("N"), Name = "Capability Tenant" }, - new PlatformSaasPlan { Code = "capability-test", Name = "Capability Test" }, - new TenantSubscription - { - TenantId = tenantId, - PlanCode = "capability-test", - Status = TenantSubscriptionStatus.Active, - StartsAt = DateTimeOffset.UtcNow.AddDays(-1), - ExpiresAt = DateTimeOffset.UtcNow.AddDays(30) - }); + var fixture = CreateSubscriptionFixture(tenantId, SaasFeatureCatalog.PrivateQuestionBank, includeEntitlement: true); + await factory.SeedAsync(fixture.Entities); - using var scope = factory.CreateSystemScope("Verify capability authorization"); - var evaluator = scope.ServiceProvider.GetRequiredService(); - Assert.False(await evaluator.IsAllowedAsync(tenantId, "content", CapabilityOperation.Read)); + await factory.SeedAsync(new SaasFeature + { + Code = SaasFeatureCatalog.Video, + Name = "Video", + Category = "content", + Status = SaasFeatureStatus.Active + }); + + using var scope = factory.CreateSystemScope("Verify feature authorization"); + var access = scope.ServiceProvider.GetRequiredService(); + var missing = await access.EvaluateAsync( + tenantId, + SaasFeatureCatalog.Video, + FeatureAccessOperation.Read); + Assert.False(missing.Allowed); + Assert.Equal("feature_not_purchased", missing.DenialCode); var dbContext = scope.ServiceProvider.GetRequiredService(); - dbContext.PlanModuleEntitlements.Add(new PlanModuleEntitlement - { - PlanCode = "capability-test", - ModuleCode = "content" - }); - await dbContext.SaveChangesAsync(); - Assert.True(await evaluator.IsAllowedAsync(tenantId, "content", CapabilityOperation.Write)); + Assert.True((await access.EvaluateAsync( + tenantId, + SaasFeatureCatalog.PrivateQuestionBank, + FeatureAccessOperation.Write)).Allowed); - var subscription = dbContext.TenantSubscriptions.Single(item => item.TenantId == tenantId); - subscription.Status = TenantSubscriptionStatus.PastDue; + var featureOverride = new TenantFeatureOverride + { + TenantId = tenantId, + FeatureCode = SaasFeatureCatalog.PrivateQuestionBank, + Mode = TenantFeatureOverrideMode.Disabled, + Reason = "Integration test disable" + }; + dbContext.TenantFeatureOverrides.Add(featureOverride); await dbContext.SaveChangesAsync(); - Assert.True(await evaluator.IsAllowedAsync(tenantId, "content", CapabilityOperation.Read)); - Assert.False(await evaluator.IsAllowedAsync(tenantId, "content", CapabilityOperation.Write)); + var disabled = await access.EvaluateAsync( + tenantId, + SaasFeatureCatalog.PrivateQuestionBank, + FeatureAccessOperation.Read); + Assert.False(disabled.Allowed); + Assert.Equal("feature_disabled", disabled.DenialCode); + + featureOverride.Mode = TenantFeatureOverrideMode.Enabled; + await dbContext.SaveChangesAsync(); + Assert.True((await access.EvaluateAsync( + tenantId, + SaasFeatureCatalog.PrivateQuestionBank, + FeatureAccessOperation.Write)).Allowed); + + var subscription = await dbContext.TenantSaasSubscriptions.SingleAsync(item => item.Id == fixture.SubscriptionId); + subscription.Status = TenantSaasSubscriptionStatus.PastDue; + await dbContext.SaveChangesAsync(); + Assert.True((await access.EvaluateAsync( + tenantId, + SaasFeatureCatalog.PrivateQuestionBank, + FeatureAccessOperation.Read)).Allowed); + var pastDueWrite = await access.EvaluateAsync( + tenantId, + SaasFeatureCatalog.PrivateQuestionBank, + FeatureAccessOperation.Write); + Assert.False(pastDueWrite.Allowed); + Assert.Equal("subscription_read_only", pastDueWrite.DenialCode); } + + private static SubscriptionFixture CreateSubscriptionFixture( + Guid tenantId, + string featureCode, + bool includeEntitlement) + { + var offeringId = Guid.NewGuid(); + var versionId = Guid.NewGuid(); + var subscriptionId = Guid.NewGuid(); + var now = DateTimeOffset.UtcNow; + var entities = new List + { + new Tenant + { + Id = tenantId, + Slug = tenantId.ToString("N"), + Name = "Feature Access Tenant" + }, + new SaasFeature + { + Code = featureCode, + Name = "Private Question Bank", + Category = "content", + Status = SaasFeatureStatus.Active + }, + new SaasOffering + { + Id = offeringId, + Code = $"feature-test-{tenantId:N}", + Name = "Feature Access Test", + Type = SaasOfferingType.BasePlan, + Status = SaasOfferingStatus.Active + }, + new SaasOfferingVersion + { + Id = versionId, + OfferingId = offeringId, + Version = 1, + Status = SaasOfferingVersionStatus.Published, + PublishedAt = now.AddDays(-1), + EffectiveAt = now.AddDays(-1) + }, + new TenantSaasSubscription + { + Id = subscriptionId, + TenantId = tenantId, + BaseOfferingVersionId = versionId, + Status = TenantSaasSubscriptionStatus.Active, + StartsAt = now.AddDays(-1), + CurrentPeriodStart = now.AddDays(-1), + CurrentPeriodEnd = now.AddDays(30) + }, + new TenantSaasSubscriptionItem + { + TenantId = tenantId, + SubscriptionId = subscriptionId, + OfferingVersionId = versionId, + ItemType = TenantSaasSubscriptionItemType.BasePlan, + Status = TenantSaasSubscriptionItemStatus.Active, + StartsAt = now.AddDays(-1), + EndsAt = now.AddDays(30) + } + }; + if (includeEntitlement) + { + entities.Add(new SaasOfferingVersionFeature + { + OfferingVersionId = versionId, + FeatureCode = featureCode + }); + } + + return new SubscriptionFixture(versionId, subscriptionId, entities.ToArray()); + } + + private sealed record SubscriptionFixture( + Guid VersionId, + Guid SubscriptionId, + object[] Entities); } diff --git a/Tiku.IntegrationTests/Api/CurrentQuotaEnforcementTests.cs b/Tiku.IntegrationTests/Api/CurrentQuotaEnforcementTests.cs new file mode 100644 index 0000000..115e806 --- /dev/null +++ b/Tiku.IntegrationTests/Api/CurrentQuotaEnforcementTests.cs @@ -0,0 +1,335 @@ +using System.Net; +using System.Net.Http.Json; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Tiku.Api.Contracts; +using Tiku.Application.Security; +using Tiku.Domain.Common; +using Tiku.Domain.Content; +using Tiku.Domain.Identity; +using Tiku.Domain.Platform; +using Tiku.Domain.QuestionBanks; +using Tiku.Domain.Tenancy; +using Tiku.Infrastructure.Persistence; + +namespace Tiku.IntegrationTests.Api; + +public sealed class CurrentQuotaEnforcementTests +{ + [Fact] + public async Task Reconciliation_uses_audited_tenant_scope_and_idempotently_records_actual_values_above_limits() + { + await using var factory = new ApiTestFactory(); + var seed = await SeedLimitedTenantAsync(factory); + var teacherId = Guid.NewGuid(); + var studentA = Guid.NewGuid(); + var studentB = Guid.NewGuid(); + await factory.SeedAsync( + new User { Id = teacherId, Phone = "13940000001", Name = "Reconcile Teacher" }, + new User { Id = studentA, Phone = "13940000002", Name = "Reconcile Student A" }, + new User { Id = studentB, Phone = "13940000003", Name = "Reconcile Student B" }, + new TenantMembership { TenantId = seed.TenantId, UserId = teacherId, Role = TenantRole.Teacher, Status = MembershipStatus.Active }, + new TenantMembership { TenantId = seed.TenantId, UserId = studentA, Role = TenantRole.Student, Status = MembershipStatus.Active }, + new TenantMembership { TenantId = seed.TenantId, UserId = studentB, Role = TenantRole.Student, Status = MembershipStatus.Active }, + new Question { TenantId = seed.TenantId, Type = "choice", Status = QuestionStatus.Published }, + new Question { TenantId = seed.TenantId, Type = "choice", Status = QuestionStatus.Draft }, + new ContentAsset + { + TenantId = seed.TenantId, + AssetKey = $"reconcile-{Guid.NewGuid():N}", + Status = ContentStatus.Active, + VerifiedSizeBytes = 2 + }); + + using var scope = factory.CreateSystemScope("Run feature usage reconciliation test"); + var service = scope.ServiceProvider.GetRequiredService(); + var request = new ReconcileFeatureUsageRequest( + seed.TenantId, + SystemScopeCallerType.Test, + nameof(CurrentQuotaEnforcementTests), + "Verify current feature usage reconciliation", + $"quota-reconcile-{Guid.NewGuid():N}"); + var first = await service.ReconcileTenantAsync(request); + var firstVersions = await ReadUsageVersionsAsync(factory, seed.TenantId); + var second = await service.ReconcileTenantAsync(request with { CorrelationId = $"{request.CorrelationId}-again" }); + var secondVersions = await ReadUsageVersionsAsync(factory, seed.TenantId); + + Assert.Equal(4, first.Count); + Assert.All(first, item => Assert.True(item.Exceeded)); + Assert.Equal(2, first.Single(item => item.MetricCode == SaasQuotaMetricCatalog.StaffCount).ActualValue); + Assert.Equal(2, first.Single(item => item.MetricCode == SaasQuotaMetricCatalog.StudentCount).ActualValue); + Assert.Equal(2, first.Single(item => item.MetricCode == SaasQuotaMetricCatalog.PrivateQuestionCount).ActualValue); + Assert.Equal(2, first.Single(item => item.MetricCode == SaasQuotaMetricCatalog.StorageBytes).ActualValue); + Assert.Equal(first, second); + Assert.Equal(firstVersions.Count, secondVersions.Count); + Assert.All(firstVersions, item => Assert.Equal(item.Value, secondVersions[item.Key])); + using var verificationScope = factory.CreateSystemScope("Verify reconciliation audit"); + Assert.True(await verificationScope.ServiceProvider.GetRequiredService().AuditLogs.AnyAsync(item => + item.TenantId == seed.TenantId && + item.Action == "system_scope.completed" && + item.TargetId == request.CorrelationId)); + } + + [Fact] + public async Task Private_question_quota_is_atomic_and_archiving_releases_capacity() + { + await using var factory = new ApiTestFactory(); + var seed = await SeedLimitedTenantAsync(factory); + using var client = factory.CreateClient(); + client.UseAccessToken(await client.LoginAsTenantAsync(seed.TenantId, seed.Phone)); + + Task CreateAsync(string content) => client.PostAsJsonAsync( + "/api/tenant-content/questions", + new DirectQuestionWriteDto + { + Type = "choice", + Content = content, + Status = "Published" + }); + + var responses = await Task.WhenAll(CreateAsync("quota question A"), CreateAsync("quota question B")); + Assert.Equal(1, responses.Count(response => response.StatusCode == HttpStatusCode.OK)); + Assert.Equal(1, responses.Count(response => response.StatusCode == HttpStatusCode.Conflict)); + var created = responses.Single(response => response.StatusCode == HttpStatusCode.OK); + var createdJson = await JsonDocument.ParseAsync(await created.Content.ReadAsStreamAsync()); + var questionId = createdJson.RootElement.GetProperty("item").GetProperty("id").GetGuid(); + + var archive = await client.PatchAsJsonAsync( + "/api/tenant-content/questions", + new DirectQuestionWriteDto + { + QuestionId = questionId, + Type = "choice", + Content = "archived question", + Status = "Archived" + }); + var replacement = await CreateAsync("replacement question"); + + Assert.Equal(HttpStatusCode.OK, archive.StatusCode); + Assert.Equal(HttpStatusCode.OK, replacement.StatusCode); + using var scope = factory.CreateSystemScope("Verify current question quota"); + var usage = await scope.ServiceProvider.GetRequiredService() + .TenantFeatureUsages.AsNoTracking() + .SingleAsync(item => item.TenantId == seed.TenantId && item.MetricCode == SaasQuotaMetricCatalog.PrivateQuestionCount); + Assert.Equal(1, usage.UsedValue); + } + + [Fact] + public async Task Staff_and_student_status_transitions_consume_and_release_current_quotas() + { + await using var factory = new ApiTestFactory(); + var seed = await SeedLimitedTenantAsync(factory); + using var client = factory.CreateClient(); + client.UseAccessToken(await client.LoginAsTenantAsync(seed.TenantId, seed.Phone)); + + var firstTeacher = await client.PutAsJsonAsync( + "/api/tenant-admin/members", + Member("teacher", "13910000001", "Teacher One")); + var secondTeacher = await client.PutAsJsonAsync( + "/api/tenant-admin/members", + Member("teacher", "13910000002", "Teacher Two")); + Assert.Equal(HttpStatusCode.OK, firstTeacher.StatusCode); + Assert.Equal(HttpStatusCode.Conflict, secondTeacher.StatusCode); + + var firstStudent = await client.PutAsJsonAsync( + "/api/tenant-admin/students", + Student("13920000001", "Student One")); + var blockedStudent = await client.PutAsJsonAsync( + "/api/tenant-admin/students", + Student("13920000002", "Student Two")); + Assert.Equal(HttpStatusCode.OK, firstStudent.StatusCode); + Assert.Equal(HttpStatusCode.Conflict, blockedStudent.StatusCode); + var studentJson = await JsonDocument.ParseAsync(await firstStudent.Content.ReadAsStreamAsync()); + var studentId = studentJson.RootElement.GetProperty("item").GetProperty("userId").GetGuid(); + + var disableStudent = await client.PostAsJsonAsync( + "/api/tenant-admin/students/status", + new UpdateTenantAdminStudentStatusDto + { + UserId = studentId, + Status = "Disabled", + Reason = "quota release test" + }); + var replacementStudent = await client.PutAsJsonAsync( + "/api/tenant-admin/students", + Student("13920000002", "Student Two")); + + Assert.Equal(HttpStatusCode.OK, disableStudent.StatusCode); + Assert.Equal(HttpStatusCode.OK, replacementStudent.StatusCode); + using var scope = factory.CreateSystemScope("Verify current membership quotas"); + var usages = await scope.ServiceProvider.GetRequiredService() + .TenantFeatureUsages.AsNoTracking() + .Where(item => item.TenantId == seed.TenantId) + .ToDictionaryAsync(item => item.MetricCode, item => item.UsedValue); + Assert.Equal(1, usages[SaasQuotaMetricCatalog.StaffCount]); + Assert.Equal(1, usages[SaasQuotaMetricCatalog.StudentCount]); + } + + [Fact] + public async Task Missing_current_quota_does_not_block_existing_creation_flows() + { + await using var factory = new ApiTestFactory(); + var tenantId = Guid.NewGuid(); + var userId = Guid.NewGuid(); + var phone = "13730000001"; + await factory.SeedAsync( + new Tenant { Id = tenantId, Slug = tenantId.ToString("N"), Name = "Unlimited Tenant" }, + new User { Id = userId, Phone = phone, Name = "Unlimited Admin" }.WithTestPassword(), + new TenantMembership + { + TenantId = tenantId, + UserId = userId, + Role = TenantRole.TenantAdmin, + Status = MembershipStatus.Active + }); + using var client = factory.CreateClient(); + client.UseAccessToken(await client.LoginAsTenantAsync(tenantId, phone)); + + var teacher = await client.PutAsJsonAsync( + "/api/tenant-admin/members", + Member("teacher", "13930000001", "Unlimited Teacher")); + var student = await client.PutAsJsonAsync( + "/api/tenant-admin/students", + Student("13930000002", "Unlimited Student")); + var question = await client.PostAsJsonAsync( + "/api/tenant-content/questions", + new DirectQuestionWriteDto { Type = "choice", Content = "unlimited question", Status = "Published" }); + + Assert.Equal(HttpStatusCode.OK, teacher.StatusCode); + Assert.Equal(HttpStatusCode.OK, student.StatusCode); + Assert.Equal(HttpStatusCode.OK, question.StatusCode); + } + + private static UpsertTenantAdminMemberDto Member(string role, string phone, string name) => new() + { + Role = role, + Status = "Active", + User = new TenantAdminUserLookupDto { Phone = phone, Name = name } + }; + + private static UpsertTenantAdminStudentDto Student(string phone, string name) => new() + { + User = new TenantAdminUserLookupDto { Phone = phone, Name = name } + }; + + private static async Task<(Guid TenantId, string Phone)> SeedLimitedTenantAsync(ApiTestFactory factory) + { + var tenantId = Guid.NewGuid(); + var adminId = Guid.NewGuid(); + var offeringId = Guid.NewGuid(); + var versionId = Guid.NewGuid(); + var subscriptionId = Guid.NewGuid(); + var phone = $"136{Random.Shared.Next(10_000_000, 99_999_999)}"; + var now = DateTimeOffset.UtcNow; + await factory.SeedAsync( + new Tenant + { + Id = tenantId, + Slug = tenantId.ToString("N"), + Name = "Current Quota Tenant", + Status = TenantStatus.Active, + BillingStatus = BillingStatus.Active + }, + new User { Id = adminId, Phone = phone, Name = "Quota Admin" }.WithTestPassword(), + new TenantMembership + { + TenantId = tenantId, + UserId = adminId, + Role = TenantRole.TenantAdmin, + Status = MembershipStatus.Active + }, + Feature(SaasFeatureCatalog.CoreBackoffice, isCore: true), + Feature(SaasFeatureCatalog.PrivateQuestionBank), + Feature(SaasFeatureCatalog.StudentManagement), + new SaasOffering + { + Id = offeringId, + Code = $"current-quota-{tenantId:N}", + Name = "Current Quota Plan", + Type = SaasOfferingType.BasePlan, + Status = SaasOfferingStatus.Active + }, + new SaasOfferingVersion + { + Id = versionId, + OfferingId = offeringId, + Version = 1, + Status = SaasOfferingVersionStatus.Published, + PublishedAt = now.AddDays(-1), + EffectiveAt = now.AddDays(-1) + }, + Entitlement(versionId, SaasFeatureCatalog.PrivateQuestionBank), + Entitlement(versionId, SaasFeatureCatalog.StudentManagement), + LimitDefinition(SaasQuotaMetricCatalog.StaffCount, SaasFeatureCatalog.CoreBackoffice), + LimitDefinition(SaasQuotaMetricCatalog.StudentCount, SaasFeatureCatalog.StudentManagement), + LimitDefinition(SaasQuotaMetricCatalog.PrivateQuestionCount, SaasFeatureCatalog.PrivateQuestionBank), + LimitDefinition(SaasQuotaMetricCatalog.StorageBytes, SaasFeatureCatalog.PrivateQuestionBank), + Limit(versionId, SaasQuotaMetricCatalog.StaffCount), + Limit(versionId, SaasQuotaMetricCatalog.StudentCount), + Limit(versionId, SaasQuotaMetricCatalog.PrivateQuestionCount), + Limit(versionId, SaasQuotaMetricCatalog.StorageBytes), + new TenantSaasSubscription + { + Id = subscriptionId, + TenantId = tenantId, + BaseOfferingVersionId = versionId, + Status = TenantSaasSubscriptionStatus.Active, + StartsAt = now.AddDays(-1), + CurrentPeriodStart = now.AddDays(-1), + CurrentPeriodEnd = now.AddMonths(1) + }, + new TenantSaasSubscriptionItem + { + TenantId = tenantId, + SubscriptionId = subscriptionId, + OfferingVersionId = versionId, + ItemType = TenantSaasSubscriptionItemType.BasePlan, + Status = TenantSaasSubscriptionItemStatus.Active, + StartsAt = now.AddDays(-1), + EndsAt = now.AddMonths(1) + }); + return (tenantId, phone); + } + + private static SaasFeature Feature(string code, bool isCore = false) => new() + { + Code = code, + Name = code, + Category = "integration", + Status = SaasFeatureStatus.Active, + IsCore = isCore + }; + + private static SaasOfferingVersionFeature Entitlement(Guid versionId, string featureCode) => new() + { + OfferingVersionId = versionId, + FeatureCode = featureCode + }; + + private static SaasFeatureLimitDefinition LimitDefinition(string metricCode, string featureCode) => new() + { + MetricCode = metricCode, + FeatureCode = featureCode, + Name = metricCode, + Unit = "count", + Kind = SaasFeatureLimitKind.Current, + WarningPercent = 80, + IsHardLimit = true + }; + + private static SaasOfferingVersionLimit Limit(Guid versionId, string metricCode) => new() + { + OfferingVersionId = versionId, + MetricCode = metricCode, + LimitValue = 1 + }; + + private static async Task> ReadUsageVersionsAsync(ApiTestFactory factory, Guid tenantId) + { + using var scope = factory.CreateSystemScope("Read reconciled feature usage versions"); + return await scope.ServiceProvider.GetRequiredService().TenantFeatureUsages.AsNoTracking() + .Where(item => item.TenantId == tenantId) + .ToDictionaryAsync(item => item.MetricCode, item => item.Version); + } +} diff --git a/Tiku.IntegrationTests/Api/DatabasePermissionServiceAuthorizationTests.cs b/Tiku.IntegrationTests/Api/DatabasePermissionServiceAuthorizationTests.cs index f1a006e..1ccb9f3 100644 --- a/Tiku.IntegrationTests/Api/DatabasePermissionServiceAuthorizationTests.cs +++ b/Tiku.IntegrationTests/Api/DatabasePermissionServiceAuthorizationTests.cs @@ -41,14 +41,14 @@ public sealed class DatabasePermissionServiceAuthorizationTests Code = BackendPermissions.TenantCommissionManage, Name = "Commission", Area = BackendPermissionArea.Tenant, - Module = "commission" + PermissionModuleCode = "tenant_commission" }, new BackendPermission { Code = BackendPermissions.TenantCrmManage, Name = "CRM", Area = BackendPermissionArea.Tenant, - Module = "crm" + PermissionModuleCode = "tenant_crm" }, new TenantBackendRole { diff --git a/Tiku.IntegrationTests/Api/PlatformAdminEndpointTests.cs b/Tiku.IntegrationTests/Api/PlatformAdminEndpointTests.cs index 6fb7219..d112337 100644 --- a/Tiku.IntegrationTests/Api/PlatformAdminEndpointTests.cs +++ b/Tiku.IntegrationTests/Api/PlatformAdminEndpointTests.cs @@ -19,7 +19,7 @@ namespace Tiku.IntegrationTests.Api; public sealed class PlatformAdminEndpointTests { [Fact] - public async Task Platform_admin_can_manage_tenant_subscription_domain_recheck_and_audit() + public async Task Platform_admin_can_publish_immutable_saas_offering_and_manage_tenant_operations() { await using var factory = new ApiTestFactory(configurationOverrides: new Dictionary { @@ -48,13 +48,6 @@ public sealed class PlatformAdminEndpointTests VerifiedAt = DateTimeOffset.UtcNow, DnsVerifiedAt = DateTimeOffset.UtcNow, TlsReadyAt = DateTimeOffset.UtcNow - }, - new PlatformSaasPlan - { - Code = "standard", - Name = "Standard", - BaseAmountCents = 99900, - Status = PlatformSaasPlanStatus.Active }); using var client = factory.CreateClient(); @@ -62,27 +55,52 @@ public sealed class PlatformAdminEndpointTests var overview = await client.GetAsync("/api/platform-admin/overview"); var tenants = await client.GetAsync("/api/platform-admin/tenants?search=six-a"); - var planModules = await client.PutAsJsonAsync( - "/api/platform-admin/plans/standard/modules", - new ReplacePlatformPlanModulesDto { ModuleCodes = ["content", "job", "settings"] }); - var subscription = await client.PostAsJsonAsync( - "/api/platform-admin/subscriptions", - new UpsertPlatformSubscriptionDto - { - TenantId = tenantId, - PlanCode = "standard", - Status = TenantSubscriptionStatus.Active, - StartsAt = DateTimeOffset.UtcNow.AddDays(-1), - ExpiresAt = DateTimeOffset.UtcNow.AddDays(30), - AmountCents = 99900 - }); - var moduleOverride = await client.PutAsJsonAsync( - $"/api/platform-admin/tenants/{tenantId}/module-overrides/content", - new UpsertPlatformTenantModuleOverrideDto - { - Mode = TenantModuleOverrideMode.Disabled, - Reason = "integration test capability revocation" - }); + var feature = await client.PutAsJsonAsync( + "/api/platform-admin/saas/features", + new UpsertSaasFeatureDto( + null, + SaasFeatureCatalog.Exam, + "考试", + "learning", + "租户考试模块", + 60_000, + "CNY", + SaasFeatureStatus.Active, + 30)); + var offering = await client.PutAsJsonAsync( + "/api/platform-admin/saas/offerings", + new UpsertSaasOfferingDto( + null, + $"standard-{Guid.NewGuid():N}", + "标准套餐", + SaasOfferingType.BasePlan, + SaasOfferingStatus.Draft, + "集成测试套餐", + 10)); + Assert.Equal(HttpStatusCode.OK, feature.StatusCode); + Assert.Equal(HttpStatusCode.OK, offering.StatusCode); + var offeringJson = await JsonDocument.ParseAsync(await offering.Content.ReadAsStreamAsync()); + var offeringId = offeringJson.RootElement.GetProperty("id").GetGuid(); + var versionRequest = new UpsertSaasOfferingVersionDto( + null, + offeringId, + PlatformBillingCycle.Yearly, + 72_000, + 60_000, + "CNY", + null, + [SaasFeatureCatalog.Exam], + new Dictionary(), + JsonDefaults.Object()); + var version = await client.PutAsJsonAsync("/api/platform-admin/saas/offering-versions", versionRequest); + Assert.Equal(HttpStatusCode.OK, version.StatusCode); + var versionJson = await JsonDocument.ParseAsync(await version.Content.ReadAsStreamAsync()); + var versionId = versionJson.RootElement.GetProperty("id").GetGuid(); + var published = await client.PostAsync($"/api/platform-admin/saas/offering-versions/{versionId}/publish", null); + var immutableUpdate = await client.PutAsJsonAsync( + "/api/platform-admin/saas/offering-versions", + versionRequest with { Id = versionId, AmountCents = 50_000 }); + var catalog = await client.GetAsync("/api/platform-admin/saas/catalog"); var recheck = await client.PostAsync($"/api/platform-admin/domains/{domainId}/recheck", null); var suspended = await client.PatchAsJsonAsync( "/api/platform-admin/tenants/status", @@ -100,9 +118,9 @@ public sealed class PlatformAdminEndpointTests Assert.Equal(HttpStatusCode.OK, overview.StatusCode); Assert.Equal(HttpStatusCode.OK, tenants.StatusCode); - Assert.Equal(HttpStatusCode.OK, planModules.StatusCode); - Assert.Equal(HttpStatusCode.OK, subscription.StatusCode); - Assert.Equal(HttpStatusCode.OK, moduleOverride.StatusCode); + Assert.Equal(HttpStatusCode.OK, published.StatusCode); + Assert.Equal(HttpStatusCode.Conflict, immutableUpdate.StatusCode); + Assert.Equal(HttpStatusCode.OK, catalog.StatusCode); Assert.Equal(HttpStatusCode.OK, recheck.StatusCode); Assert.Equal(HttpStatusCode.OK, suspended.StatusCode); Assert.Equal(HttpStatusCode.NotFound, runtimeAfterSuspend.StatusCode); @@ -117,10 +135,16 @@ public sealed class PlatformAdminEndpointTests log.Action == "platform.tenant.status_changed")); Assert.True(await dbContext.AuditLogs.AnyAsync(log => log.ActorUserId == platform.UserId && - log.Action == "platform.plan.modules.replaced")); + log.Action == "platform.saas.feature.upserted")); Assert.True(await dbContext.AuditLogs.AnyAsync(log => log.ActorUserId == platform.UserId && - log.Action == "platform.tenant.module_override.updated")); + log.Action == "platform.saas.offering_version.published")); + Assert.Equal( + 60_000, + await dbContext.SaasOfferingVersions + .Where(item => item.Id == versionId) + .Select(item => item.AmountCents) + .SingleAsync()); } [Fact] @@ -153,7 +177,7 @@ public sealed class PlatformAdminEndpointTests using var client = factory.CreateClient(); client.UseAccessToken(await client.LoginAsTenantAsync(tenantId, phone)); - var response = await client.GetAsync("/api/platform-admin/overview"); + var response = await client.GetAsync("/api/platform-admin/saas/catalog"); Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); } @@ -178,23 +202,22 @@ public sealed class PlatformAdminEndpointTests Status = TenantStatus.Active, BillingStatus = BillingStatus.PastDue }, - new TenantInvoice + new PlatformBillingInvoice { Id = invoiceId, TenantId = tenantId, InvoiceNo = "INV-DUNNING-1", - Status = TenantInvoiceStatus.Overdue, - TotalCents = 10_000, - BalanceCents = 10_000 + Status = PlatformBillingInvoiceStatus.Overdue, + TotalAmountCents = 10_000 }, - new TenantInvoiceReminder + new PlatformBillingInvoiceReminder { Id = reminderId, TenantId = tenantId, InvoiceId = invoiceId, - ReminderType = TenantInvoiceReminderType.Overdue, - Channel = TenantInvoiceReminderChannel.Wechat, - Status = TenantInvoiceReminderStatus.Failed, + ReminderType = PlatformBillingInvoiceReminderType.Overdue, + Channel = PlatformBillingInvoiceReminderChannel.Wechat, + Status = PlatformBillingInvoiceReminderStatus.Failed, ReminderDate = DateOnly.FromDateTime(DateTime.UtcNow.Date), BalanceCentsSnapshot = 10_000 }); @@ -202,12 +225,12 @@ public sealed class PlatformAdminEndpointTests client.UseAccessToken(await client.LoginAsPlatformAsync(platform.Email)); var upsertResponse = await client.PutAsJsonAsync( - "/api/platform-admin/dunning-notification-channels", - new UpsertPlatformDunningChannelDto + "/api/platform-admin/saas/dunning/channels", + new UpsertPlatformBillingDunningChannelDto { ChannelCode = "wecom-overdue", Name = "企业微信逾期提醒", - Provider = PlatformDunningProvider.Wecom, + Provider = PlatformBillingDunningProvider.Wecom, WebhookUrl = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=secret-key", SecretRef = "platform_secrets:dunning:wecom:default", ReminderTypes = ["overdue"], @@ -218,14 +241,14 @@ public sealed class PlatformAdminEndpointTests var upsertBody = await upsertResponse.Content.ReadAsStringAsync(); var channelJson = JsonDocument.Parse(upsertBody); var channelId = channelJson.RootElement.GetProperty("id").GetGuid(); - await factory.SeedAsync(new PlatformDunningNotificationEvent + await factory.SeedAsync(new PlatformBillingDunningNotificationEvent { TenantId = tenantId, ChannelId = channelId, ReminderId = reminderId, InvoiceId = invoiceId, - Provider = PlatformDunningProvider.Wecom, - Status = PlatformDunningNotificationStatus.Failed, + Provider = PlatformBillingDunningProvider.Wecom, + Status = PlatformBillingDunningNotificationStatus.Failed, Attempts = 2, LastError = "timeout", LastHttpCode = 500, @@ -233,21 +256,21 @@ public sealed class PlatformAdminEndpointTests RequestPayload = JsonSerializer.SerializeToElement(new { phone = "13800001111", amount = 10000 }) }); - var channelsResponse = await client.GetAsync("/api/platform-admin/dunning-notification-channels?search=wecom"); - var eventsResponse = await client.GetAsync("/api/platform-admin/dunning-notification-events?status=failed"); + var channelsResponse = await client.GetAsync("/api/platform-admin/saas/dunning/channels?search=wecom"); + var eventsResponse = await client.GetAsync("/api/platform-admin/saas/dunning/events?status=failed"); var eventsJson = await JsonDocument.ParseAsync(await eventsResponse.Content.ReadAsStreamAsync()); var eventId = eventsJson.RootElement.GetProperty("items")[0].GetProperty("id").GetGuid(); - var detailResponse = await client.GetAsync($"/api/platform-admin/dunning-notification-events/detail?eventId={eventId}"); + var detailResponse = await client.GetAsync($"/api/platform-admin/saas/dunning/events/detail?eventId={eventId}"); var retryResponse = await client.PostAsJsonAsync( - "/api/platform-admin/dunning-notification-events/retry", - new RetryPlatformDunningEventDto + "/api/platform-admin/saas/dunning/events/retry", + new RetryPlatformBillingDunningEventDto { EventId = eventId, Reason = "manual retry" }); var disableResponse = await client.PostAsJsonAsync( - "/api/platform-admin/dunning-notification-channels/disable", - new DisablePlatformDunningChannelDto + "/api/platform-admin/saas/dunning/channels/disable", + new DisablePlatformBillingDunningChannelDto { ChannelId = channelId, Reason = "disable test" @@ -264,17 +287,17 @@ public sealed class PlatformAdminEndpointTests using var scope = factory.CreateSystemScope("Verify platform dunning side effects"); var dbContext = scope.ServiceProvider.GetRequiredService(); - var storedEvent = await dbContext.PlatformDunningNotificationEvents.AsNoTracking().SingleAsync(item => item.Id == eventId); - var storedChannel = await dbContext.PlatformDunningNotificationChannels.AsNoTracking().SingleAsync(item => item.Id == channelId); - Assert.Equal(PlatformDunningNotificationStatus.Pending, storedEvent.Status); + var storedEvent = await dbContext.PlatformBillingDunningNotificationEvents.AsNoTracking().SingleAsync(item => item.Id == eventId); + var storedChannel = await dbContext.PlatformBillingDunningNotificationChannels.AsNoTracking().SingleAsync(item => item.Id == channelId); + Assert.Equal(PlatformBillingDunningNotificationStatus.Pending, storedEvent.Status); Assert.Null(storedEvent.LastError); Assert.False(storedChannel.Enabled); Assert.True(await dbContext.AuditLogs.AnyAsync(log => log.ActorUserId == platform.UserId && - log.Action == "platform.dunning_event.retry_requested")); + log.Action == "platform.billing_dunning_event.retry_requested")); Assert.True(await dbContext.AuditLogs.AnyAsync(log => log.ActorUserId == platform.UserId && - log.Action == "platform.dunning_channel.disabled")); + log.Action == "platform.billing_dunning_channel.disabled")); } private static async Task<(Guid UserId, string Email)> SeedPlatformAdminAsync(ApiTestFactory factory) @@ -282,16 +305,29 @@ public sealed class PlatformAdminEndpointTests var userId = Guid.NewGuid(); var roleId = Guid.NewGuid(); var email = $"platform-{Guid.NewGuid():N}@example.test"; + var modules = BackendPermissions.Platform + .Select(PermissionModuleCatalog.ResolvePermissionModuleCode) + .Distinct(StringComparer.Ordinal) + .Select(code => new PermissionModule + { + Code = code, + Name = code, + Area = BackendPermissionArea.Platform, + RequiredFeatureCode = PermissionModuleCatalog.RequiredFeatures[code] + }) + .Cast() + .ToList(); var permissions = BackendPermissions.Platform.Select(code => new BackendPermission { Code = code, Name = code, Area = BackendPermissionArea.Platform, - Module = code.Split(':')[1], + PermissionModuleCode = PermissionModuleCatalog.ResolvePermissionModuleCode(code), IsSystem = true }).Cast().ToList(); await factory.SeedAsync( [ + ..modules, ..permissions, new User { diff --git a/Tiku.IntegrationTests/Api/PlatformBillingCallbackTests.cs b/Tiku.IntegrationTests/Api/PlatformBillingCallbackTests.cs new file mode 100644 index 0000000..c10f9d4 --- /dev/null +++ b/Tiku.IntegrationTests/Api/PlatformBillingCallbackTests.cs @@ -0,0 +1,272 @@ +using System.Net; +using System.Net.Http.Json; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Tiku.Application.Commerce; +using Tiku.Application.PlatformBilling; +using Tiku.Domain.Common; +using Tiku.Domain.Platform; +using Tiku.Domain.Tenancy; +using Tiku.Infrastructure.Persistence; + +namespace Tiku.IntegrationTests.Api; + +public sealed class PlatformBillingCallbackTests +{ + [Theory] + [InlineData("wechat_pay", "application/json", "{\"code\":\"SUCCESS\"")] + [InlineData("alipay", "text/plain", "success")] + public async Task Valid_provider_callback_settles_once( + string provider, + string expectedMediaType, + string expectedBody) + { + var fixture = CreateFixture(provider); + var gateway = new FakePlatformBillingPaymentGateway(fixture.Notification); + await using var factory = new ApiTestFactory(platformBillingPaymentGateway: gateway); + await factory.SeedAsync(fixture.Entities); + await PublishVersionAsync(factory, fixture.VersionId); + using var client = factory.CreateClient(); + + var first = await client.PostAsJsonAsync($"/api/platform-billing/callbacks/{provider}", new { eventId = fixture.Notification.EventId }); + var repeated = await client.PostAsJsonAsync($"/api/platform-billing/callbacks/{provider}", new { eventId = fixture.Notification.EventId }); + + Assert.Equal(HttpStatusCode.OK, first.StatusCode); + Assert.Equal(HttpStatusCode.OK, repeated.StatusCode); + Assert.Equal(expectedMediaType, first.Content.Headers.ContentType?.MediaType); + Assert.Contains(expectedBody, await first.Content.ReadAsStringAsync(), StringComparison.Ordinal); + Assert.All(gateway.ParsedProviders, value => Assert.Equal(provider, value)); + + using var scope = factory.CreateSystemScope("Verify platform payment callback idempotency"); + var db = scope.ServiceProvider.GetRequiredService(); + Assert.Equal(PlatformBillingPaymentStatus.Succeeded, + (await db.PlatformBillingPayments.AsNoTracking().SingleAsync(value => value.Id == fixture.PaymentId)).Status); + Assert.Single(await db.PlatformBillingPaymentEvents.AsNoTracking() + .Where(value => value.PaymentId == fixture.PaymentId) + .ToArrayAsync()); + Assert.Single(await db.PlatformBillingInvoices.AsNoTracking() + .Where(value => value.OrderId == fixture.OrderId) + .ToArrayAsync()); + Assert.Single(await db.TenantSaasSubscriptions.AsNoTracking() + .Where(value => value.TenantId == fixture.TenantId) + .ToArrayAsync()); + } + + [Fact] + public async Task Invalid_signature_is_rejected_without_settlement() + { + var fixture = CreateFixture(PaymentProviders.WechatPay); + var invalid = fixture.Notification with { SignatureValid = false }; + await using var factory = new ApiTestFactory( + platformBillingPaymentGateway: new FakePlatformBillingPaymentGateway(invalid)); + await factory.SeedAsync(fixture.Entities); + await PublishVersionAsync(factory, fixture.VersionId); + using var client = factory.CreateClient(); + + var response = await client.PostAsJsonAsync( + "/api/platform-billing/callbacks/wechat_pay", + new { eventId = invalid.EventId }); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.Equal("platform_billing_notification_invalid", await ReadCodeAsync(response)); + await AssertPaymentRemainsPendingAsync(factory, fixture.PaymentId); + } + + [Fact] + public async Task Callback_amount_mismatch_is_rejected_without_settlement() + { + var fixture = CreateFixture(PaymentProviders.Alipay); + var mismatched = fixture.Notification with { AmountCents = fixture.Notification.AmountCents + 1 }; + await using var factory = new ApiTestFactory( + platformBillingPaymentGateway: new FakePlatformBillingPaymentGateway(mismatched)); + await factory.SeedAsync(fixture.Entities); + await PublishVersionAsync(factory, fixture.VersionId); + using var client = factory.CreateClient(); + + var response = await client.PostAsJsonAsync( + "/api/platform-billing/callbacks/alipay", + new { eventId = mismatched.EventId }); + + Assert.Equal(HttpStatusCode.Conflict, response.StatusCode); + Assert.Equal("platform_billing_payment_amount_mismatch", await ReadCodeAsync(response)); + await AssertPaymentRemainsPendingAsync(factory, fixture.PaymentId); + } + + private static CallbackFixture CreateFixture(string provider) + { + const int amountCents = 8_800; + var tenantId = Guid.NewGuid(); + var feature = new SaasFeature + { + Code = $"callback.feature.{Guid.NewGuid():N}", + Name = "Callback feature", + Category = "integration", + Status = SaasFeatureStatus.Active + }; + var offering = new SaasOffering + { + Id = Guid.NewGuid(), + Code = $"callback-offering-{Guid.NewGuid():N}", + Name = "Callback offering", + Type = SaasOfferingType.BasePlan, + Status = SaasOfferingStatus.Active + }; + var version = new SaasOfferingVersion + { + Id = Guid.NewGuid(), + OfferingId = offering.Id, + Version = 1, + Status = SaasOfferingVersionStatus.Draft, + BillingCycle = PlatformBillingCycle.Monthly, + OriginalAmountCents = amountCents, + AmountCents = amountCents, + Currency = "CNY", + EffectiveAt = DateTimeOffset.UtcNow.AddDays(-1) + }; + var order = new PlatformBillingOrder + { + Id = Guid.NewGuid(), + TenantId = tenantId, + QuoteId = Guid.NewGuid(), + OrderNo = $"SO{Guid.NewGuid():N}", + IdempotencyKey = $"order-{Guid.NewGuid():N}", + Purpose = PlatformBillingOrderPurpose.NewSubscription, + Status = PlatformBillingOrderStatus.PendingPayment, + OriginalAmountCents = amountCents, + TotalAmountCents = amountCents, + Currency = "CNY", + ExpiresAt = DateTimeOffset.UtcNow.AddHours(1), + Snapshot = JsonDefaults.Object() + }; + var quote = new PlatformBillingQuote + { + Id = order.QuoteId, + TenantId = tenantId, + QuoteNo = $"SQ{Guid.NewGuid():N}", + IdempotencyKey = $"quote-{Guid.NewGuid():N}", + Purpose = PlatformBillingOrderPurpose.NewSubscription, + Status = PlatformBillingQuoteStatus.Converted, + OriginalAmountCents = amountCents, + TotalAmountCents = amountCents, + Currency = "CNY", + ExpiresAt = DateTimeOffset.UtcNow.AddHours(1), + FeatureSnapshot = JsonDefaults.Array(), + LimitSnapshot = JsonDefaults.Object() + }; + var orderItem = new PlatformBillingOrderItem + { + Id = Guid.NewGuid(), + TenantId = tenantId, + OrderId = order.Id, + OfferingVersionId = version.Id, + ItemType = PlatformBillingItemType.BasePlan, + UnitAmountCents = amountCents, + AmountCents = amountCents, + Snapshot = JsonDefaults.Object() + }; + var payment = new PlatformBillingPayment + { + Id = Guid.NewGuid(), + TenantId = tenantId, + OrderId = order.Id, + PaymentNo = $"SP{Guid.NewGuid():N}", + IdempotencyKey = $"payment-{Guid.NewGuid():N}", + Provider = provider, + Method = provider == PaymentProviders.Alipay ? "web" : "jsapi", + AmountCents = amountCents, + Status = PlatformBillingPaymentStatus.Pending + }; + var notification = new PaymentNotificationResult( + provider, + "payment.succeeded", + $"event-{Guid.NewGuid():N}", + order.OrderNo, + $"trade-{Guid.NewGuid():N}", + amountCents, + true, + true, + DateTimeOffset.UtcNow, + JsonDefaults.Object()); + object[] entities = + [ + new Tenant + { + Id = tenantId, + Slug = $"callback-{tenantId:N}", + Name = "Callback tenant", + Status = TenantStatus.Active, + BillingStatus = BillingStatus.Trial + }, + feature, + offering, + version, + new SaasOfferingVersionFeature { OfferingVersionId = version.Id, FeatureCode = feature.Code }, + quote, + order, + orderItem, + payment + ]; + return new CallbackFixture(tenantId, version.Id, order.Id, payment.Id, notification, entities); + } + + private static async Task PublishVersionAsync(ApiTestFactory factory, Guid versionId) + { + using var scope = factory.CreateSystemScope("Publish callback plan fixture"); + var db = scope.ServiceProvider.GetRequiredService(); + var version = await db.SaasOfferingVersions.SingleAsync(value => value.Id == versionId); + version.Status = SaasOfferingVersionStatus.Published; + version.PublishedAt = DateTimeOffset.UtcNow; + await db.SaveChangesAsync(); + } + + private static async Task AssertPaymentRemainsPendingAsync(ApiTestFactory factory, Guid paymentId) + { + using var scope = factory.CreateSystemScope("Verify rejected platform callback"); + var db = scope.ServiceProvider.GetRequiredService(); + Assert.Equal(PlatformBillingPaymentStatus.Pending, + (await db.PlatformBillingPayments.AsNoTracking().SingleAsync(value => value.Id == paymentId)).Status); + Assert.False(await db.PlatformBillingPaymentEvents.AnyAsync(value => value.PaymentId == paymentId)); + } + + private static async Task ReadCodeAsync(HttpResponseMessage response) + { + using var payload = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync()); + return payload.RootElement.GetProperty("code").GetString() ?? string.Empty; + } + + private sealed record CallbackFixture( + Guid TenantId, + Guid VersionId, + Guid OrderId, + Guid PaymentId, + PaymentNotificationResult Notification, + object[] Entities); + + private sealed class FakePlatformBillingPaymentGateway(PaymentNotificationResult notification) + : IPlatformBillingPaymentGateway + { + public List ParsedProviders { get; } = []; + + public Task CreatePaymentAsync( + string provider, + CreatePaymentProviderRequest request, + CancellationToken cancellationToken = default) => + Task.FromResult(new CreatePaymentProviderResult( + provider, + request.Method, + "pending", + null, + JsonDefaults.Object(), + JsonDefaults.Object())); + + public Task ParseNotificationAsync( + string provider, + PaymentNotificationRequest request, + CancellationToken cancellationToken = default) + { + ParsedProviders.Add(provider); + return Task.FromResult(notification); + } + } +} diff --git a/Tiku.IntegrationTests/Api/SaasBillingLifecycleTests.cs b/Tiku.IntegrationTests/Api/SaasBillingLifecycleTests.cs new file mode 100644 index 0000000..93abc53 --- /dev/null +++ b/Tiku.IntegrationTests/Api/SaasBillingLifecycleTests.cs @@ -0,0 +1,962 @@ +using System.Net; +using System.Net.Http.Json; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Tiku.Api.Contracts; +using Tiku.Application.PlatformBilling; +using Tiku.Application.Security; +using Tiku.Domain.Common; +using Tiku.Domain.Identity; +using Tiku.Domain.Operations; +using Tiku.Domain.Platform; +using Tiku.Domain.Tenancy; +using Tiku.Infrastructure.Persistence; + +namespace Tiku.IntegrationTests.Api; + +public sealed class SaasBillingLifecycleTests +{ + [Fact] + public async Task Platform_can_publish_feature_limit_and_immutable_offering_version() + { + await using var factory = PlatformFactory(); + var platform = await SeedPlatformAdminAsync(factory); + using var client = factory.CreateClient(); + client.UseAccessToken(await client.LoginAsPlatformAsync(platform.Email)); + + var feature = await client.PutAsJsonAsync( + "/api/platform-admin/saas/features", + new UpsertSaasFeatureDto( + null, + SaasFeatureCatalog.PrivateQuestionBank, + "私有题库", + "content", + "租户私有题库", + 20_000, + "CNY", + SaasFeatureStatus.Active, + 10)); + var limit = await client.PutAsJsonAsync( + "/api/platform-admin/saas/feature-limits", + new UpsertSaasFeatureLimitDto( + null, + SaasQuotaMetricCatalog.PrivateQuestionCount, + SaasFeatureCatalog.PrivateQuestionBank, + "私有题目数", + "count", + SaasFeatureLimitKind.Current, + 80, + true)); + var offering = await client.PutAsJsonAsync( + "/api/platform-admin/saas/offerings", + new UpsertSaasOfferingDto( + null, + $"private-bank-{Guid.NewGuid():N}", + "题库基础套餐", + SaasOfferingType.BasePlan, + SaasOfferingStatus.Draft, + null, + 10)); + + Assert.Equal(HttpStatusCode.OK, feature.StatusCode); + Assert.Equal(HttpStatusCode.OK, limit.StatusCode); + Assert.Equal(HttpStatusCode.OK, offering.StatusCode); + var offeringId = await ReadGuidAsync(offering, "id"); + var versionRequest = new UpsertSaasOfferingVersionDto( + null, + offeringId, + PlatformBillingCycle.Yearly, + 24_000, + 20_000, + "CNY", + null, + [SaasFeatureCatalog.PrivateQuestionBank], + new Dictionary + { + [SaasQuotaMetricCatalog.PrivateQuestionCount] = 10_000 + }, + JsonDefaults.Object()); + var draft = await client.PutAsJsonAsync("/api/platform-admin/saas/offering-versions", versionRequest); + + Assert.Equal(HttpStatusCode.OK, draft.StatusCode); + var versionId = await ReadGuidAsync(draft, "id"); + var publish = await client.PostAsync($"/api/platform-admin/saas/offering-versions/{versionId}/publish", null); + var mutatePublished = await client.PutAsJsonAsync( + "/api/platform-admin/saas/offering-versions", + versionRequest with { Id = versionId, AmountCents = 19_000 }); + + Assert.Equal(HttpStatusCode.OK, publish.StatusCode); + Assert.Equal(HttpStatusCode.Conflict, mutatePublished.StatusCode); + Assert.Equal("saas_offering_version_immutable", await ReadCodeAsync(mutatePublished)); + + using var scope = factory.CreateSystemScope("Verify immutable SaaS catalog version"); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var stored = await dbContext.SaasOfferingVersions.AsNoTracking().SingleAsync(value => value.Id == versionId); + Assert.Equal(SaasOfferingVersionStatus.Published, stored.Status); + Assert.Equal(20_000, stored.AmountCents); + Assert.True(await dbContext.SaasOfferingVersionLimits.AnyAsync(value => + value.OfferingVersionId == versionId && + value.MetricCode == SaasQuotaMetricCatalog.PrivateQuestionCount && + value.LimitValue == 10_000)); + } + + [Fact] + public async Task Tenant_can_purchase_manual_plan_and_other_tenant_cannot_read_order() + { + await using var factory = PlatformFactory(); + var platform = await SeedPlatformAdminAsync(factory); + var platformTenantId = Guid.NewGuid(); + var tenantA = await SeedTenantAdminWithoutSubscriptionAsync(factory, "billing-a"); + await factory.SeedAsync(new Tenant + { + Id = platformTenantId, + Slug = $"platform-{platformTenantId:N}", + Name = "Platform Owner", + Mode = TenantMode.PlatformOwned, + Status = TenantStatus.Active + }); + var catalog = await SeedPublishedPlanAsync(factory, SaasFeatureCatalog.Exam, limit: null); + + using var tenantClient = factory.CreateClient(); + tenantClient.UseAccessToken(await tenantClient.LoginAsTenantAsync(tenantA.TenantId, tenantA.Phone)); + var catalogResponse = await tenantClient.GetAsync("/api/tenant-billing/catalog"); + var quoteIdempotencyKey = $"quote-{Guid.NewGuid():N}"; + var quoteRequest = new CreatePlatformBillingQuoteDto( + catalog.VersionId, + [], + PlatformBillingOrderPurpose.NewSubscription, + quoteIdempotencyKey); + var quoteResponse = await tenantClient.PostAsJsonAsync( + "/api/tenant-billing/quotes", + quoteRequest); + var repeatedQuoteResponse = await tenantClient.PostAsJsonAsync( + "/api/tenant-billing/quotes", + quoteRequest); + Assert.Equal(HttpStatusCode.OK, catalogResponse.StatusCode); + Assert.Equal(HttpStatusCode.OK, quoteResponse.StatusCode); + Assert.Equal(HttpStatusCode.OK, repeatedQuoteResponse.StatusCode); + var quoteId = await ReadGuidAsync(quoteResponse, "id"); + Assert.Equal(quoteId, await ReadGuidAsync(repeatedQuoteResponse, "id")); + + var idempotencyKey = $"order-{Guid.NewGuid():N}"; + var orderResponse = await tenantClient.PostAsJsonAsync( + "/api/tenant-billing/orders", + new CreatePlatformBillingOrderDto(quoteId, idempotencyKey)); + var repeatedOrder = await tenantClient.PostAsJsonAsync( + "/api/tenant-billing/orders", + new CreatePlatformBillingOrderDto(quoteId, idempotencyKey)); + Assert.Equal(HttpStatusCode.OK, orderResponse.StatusCode); + Assert.Equal(HttpStatusCode.OK, repeatedOrder.StatusCode); + var orderNo = await ReadStringAsync(orderResponse, "orderNo"); + Assert.Equal(orderNo, await ReadStringAsync(repeatedOrder, "orderNo")); + + var paymentResponse = await tenantClient.PostAsJsonAsync( + $"/api/tenant-billing/orders/{orderNo}/payments", + new CreatePlatformBillingPaymentDto( + "manual", + "bank_transfer", + $"payment-{Guid.NewGuid():N}", + null, + null, + null)); + Assert.Equal(HttpStatusCode.OK, paymentResponse.StatusCode); + var paymentId = await ReadGuidAsync(paymentResponse, "id"); + + using var platformClient = factory.CreateClient(); + platformClient.UseAccessToken(await platformClient.LoginAsPlatformAsync(platform.Email)); + var confirmation = new ConfirmManualPlatformPaymentDto( + paymentId, + $"manual-{Guid.NewGuid():N}", + DateTimeOffset.UtcNow, + "integration test receipt"); + var confirm = await platformClient.PostAsJsonAsync( + "/api/platform-admin/saas/payments/manual/confirm", + confirmation); + var repeatedConfirm = await platformClient.PostAsJsonAsync( + "/api/platform-admin/saas/payments/manual/confirm", + confirmation); + Assert.Equal(HttpStatusCode.OK, confirm.StatusCode); + Assert.Equal(HttpStatusCode.OK, repeatedConfirm.StatusCode); + + var subscription = await tenantClient.GetAsync("/api/tenant-billing/subscription"); + Assert.Equal(HttpStatusCode.OK, subscription.StatusCode); + Assert.Equal("Active", await ReadStringAsync(subscription, "status")); + + var tenantB = await SeedTenantAdminWithoutSubscriptionAsync(factory, "billing-b"); + using var tenantBClient = factory.CreateClient(); + tenantBClient.UseAccessToken(await tenantBClient.LoginAsTenantAsync(tenantB.TenantId, tenantB.Phone)); + var crossTenantOrder = await tenantBClient.GetAsync($"/api/tenant-billing/orders/{orderNo}"); + Assert.Equal(HttpStatusCode.NotFound, crossTenantOrder.StatusCode); + + using var scope = factory.CreateSystemScope("Verify SaaS billing settlement idempotency"); + var dbContext = scope.ServiceProvider.GetRequiredService(); + Assert.Single(await dbContext.TenantSaasSubscriptions.AsNoTracking() + .Where(value => value.TenantId == tenantA.TenantId) + .ToArrayAsync()); + Assert.Single(await dbContext.PlatformBillingInvoices.AsNoTracking() + .Where(value => value.TenantId == tenantA.TenantId) + .ToArrayAsync()); + Assert.Single(await dbContext.PlatformBillingPaymentEvents.AsNoTracking() + .Where(value => value.TenantId == tenantA.TenantId && value.PaymentId == paymentId) + .ToArrayAsync()); + } + + [Fact] + public async Task Tenant_role_cannot_bind_permission_for_unpurchased_feature() + { + await using var factory = new ApiTestFactory(); + var tenant = await SeedTenantAdminWithoutSubscriptionAsync(factory, "role-feature"); + using var client = factory.CreateClient(); + client.UseAccessToken(await client.LoginAsTenantAsync(tenant.TenantId, tenant.Phone)); + var roleResponse = await client.PostAsJsonAsync( + "/api/backoffice/tenant/roles", + new UpsertBackofficeRoleDto + { + Code = $"content_editor_{Guid.NewGuid():N}", + Name = "内容编辑", + Status = BackendRoleStatus.Active, + DataScope = JsonDefaults.Object() + }); + Assert.Equal(HttpStatusCode.OK, roleResponse.StatusCode); + var roleId = await ReadGuidAsync(roleResponse, "id"); + + var bind = await client.PutAsJsonAsync( + $"/api/backoffice/tenant/roles/{roleId}/bindings", + new ReplaceRoleBindingsDto + { + PermissionCodes = [BackendPermissions.TenantContentManage], + MenuCodes = [] + }); + + Assert.Equal(HttpStatusCode.BadRequest, bind.StatusCode); + Assert.Equal("feature_not_available", await ReadCodeAsync(bind)); + using var scope = factory.CreateSystemScope("Verify rejected tenant role binding"); + var dbContext = scope.ServiceProvider.GetRequiredService(); + Assert.False(await dbContext.TenantBackendRolePermissions.AnyAsync(value => + value.TenantId == tenant.TenantId && + value.RoleId == roleId && + value.PermissionCode == BackendPermissions.TenantContentManage)); + } + + [Fact] + public async Task Video_only_subscription_can_bind_video_permission_without_private_question_permission() + { + await using var factory = new ApiTestFactory(); + var tenant = await SeedTenantAdminWithoutSubscriptionAsync(factory, "video-role-feature"); + var graph = CreatePublishedPlan(SaasFeatureCatalog.Video); + var now = DateTimeOffset.UtcNow; + await factory.SeedAsync( + graph.Offering, + graph.Version, + graph.Entitlement, + new TenantSaasSubscription + { + Id = graph.SubscriptionId, + TenantId = tenant.TenantId, + BaseOfferingVersionId = graph.Version.Id, + Status = TenantSaasSubscriptionStatus.Active, + StartsAt = now.AddDays(-1), + CurrentPeriodStart = now.AddDays(-1), + CurrentPeriodEnd = now.AddMonths(1) + }, + new TenantSaasSubscriptionItem + { + TenantId = tenant.TenantId, + SubscriptionId = graph.SubscriptionId, + OfferingVersionId = graph.Version.Id, + ItemType = TenantSaasSubscriptionItemType.BasePlan, + Status = TenantSaasSubscriptionItemStatus.Active, + StartsAt = now.AddDays(-1), + EndsAt = now.AddMonths(1) + }); + await PublishFixtureVersionAsync(factory, graph.Version.Id); + + using var client = factory.CreateClient(); + client.UseAccessToken(await client.LoginAsTenantAsync(tenant.TenantId, tenant.Phone)); + var roleResponse = await client.PostAsJsonAsync( + "/api/backoffice/tenant/roles", + new UpsertBackofficeRoleDto + { + Code = $"video_editor_{Guid.NewGuid():N}", + Name = "视频编辑", + Status = BackendRoleStatus.Active, + DataScope = JsonDefaults.Object() + }); + Assert.Equal(HttpStatusCode.OK, roleResponse.StatusCode); + var roleId = await ReadGuidAsync(roleResponse, "id"); + + var videoBinding = await client.PutAsJsonAsync( + $"/api/backoffice/tenant/roles/{roleId}/bindings", + new ReplaceRoleBindingsDto + { + PermissionCodes = [BackendPermissions.TenantVideoManage], + MenuCodes = [] + }); + var questionBinding = await client.PutAsJsonAsync( + $"/api/backoffice/tenant/roles/{roleId}/bindings", + new ReplaceRoleBindingsDto + { + PermissionCodes = [BackendPermissions.TenantContentManage], + MenuCodes = [] + }); + + Assert.Equal(HttpStatusCode.OK, videoBinding.StatusCode); + Assert.Equal(HttpStatusCode.BadRequest, questionBinding.StatusCode); + Assert.Equal("feature_not_available", await ReadCodeAsync(questionBinding)); + } + + [Fact] + public async Task Quota_warns_at_eighty_percent_and_concurrent_consumers_cannot_exceed_limit() + { + await using var factory = new ApiTestFactory(); + var tenantId = Guid.NewGuid(); + await SeedQuotaSubscriptionAsync(factory, tenantId, limit: 10); + + using (var scope = factory.CreateSystemScope("Seed SaaS quota usage")) + { + var featureAccess = scope.ServiceProvider.GetRequiredService(); + Assert.True(await featureAccess.TryConsumeQuotaAsync( + tenantId, + SaasQuotaMetricCatalog.ExportCount, + 8)); + var atWarning = Assert.Single(await featureAccess.GetQuotaSummaryAsync(tenantId)); + Assert.Equal(80, atWarning.UsedPercent); + Assert.True(atWarning.Warning); + Assert.False(atWarning.Exceeded); + } + + async Task ConsumeFinalCapacityAsync() + { + using var scope = factory.CreateSystemScope("Race SaaS quota consumption"); + return await scope.ServiceProvider.GetRequiredService() + .TryConsumeQuotaAsync(tenantId, SaasQuotaMetricCatalog.ExportCount, 2); + } + + var results = await Task.WhenAll(ConsumeFinalCapacityAsync(), ConsumeFinalCapacityAsync()); + Assert.Equal(1, results.Count(value => value)); + + using var verificationScope = factory.CreateSystemScope("Verify SaaS quota atomicity"); + var finalUsage = Assert.Single(await verificationScope.ServiceProvider + .GetRequiredService() + .GetQuotaSummaryAsync(tenantId)); + Assert.Equal(10, finalUsage.UsedValue); + Assert.True(finalUsage.Warning); + Assert.True(finalUsage.Exceeded); + } + + [Fact] + public async Task Onboarding_is_ready_when_required_delivery_steps_are_complete() + { + await using var factory = new ApiTestFactory(); + var tenantId = Guid.NewGuid(); + var ownerId = Guid.NewGuid(); + var phone = $"139{Random.Shared.Next(10_000_000, 99_999_999)}"; + var plan = CreatePublishedPlan(SaasFeatureCatalog.Exam); + var now = DateTimeOffset.UtcNow; + await factory.SeedAsync( + new Tenant + { + Id = tenantId, + Slug = tenantId.ToString("N"), + Name = "Onboarding Tenant", + OwnerUserId = ownerId, + Status = TenantStatus.Active, + BillingStatus = BillingStatus.Active + }, + new User + { + Id = ownerId, + Phone = phone, + Name = "Tenant Owner", + ForcePasswordChange = false + }.WithTestPassword(), + new TenantMembership + { + TenantId = tenantId, + UserId = ownerId, + Role = TenantRole.TenantOwner, + Status = MembershipStatus.Active + }, + plan.Feature, + plan.Offering, + plan.Version, + plan.Entitlement, + new TenantSaasSubscription + { + Id = plan.SubscriptionId, + TenantId = tenantId, + BaseOfferingVersionId = plan.Version.Id, + Status = TenantSaasSubscriptionStatus.Active, + StartsAt = now.AddDays(-1), + CurrentPeriodStart = now.AddDays(-1), + CurrentPeriodEnd = now.AddMonths(1) + }, + new TenantSaasSubscriptionItem + { + TenantId = tenantId, + SubscriptionId = plan.SubscriptionId, + OfferingVersionId = plan.Version.Id, + ItemType = TenantSaasSubscriptionItemType.BasePlan, + Status = TenantSaasSubscriptionItemStatus.Active, + StartsAt = now.AddDays(-1), + EndsAt = now.AddMonths(1) + }, + new TenantDomain + { + TenantId = tenantId, + Host = $"{tenantId:N}.example.test", + Status = TenantDomainStatus.Active, + IsPrimary = true, + VerifiedAt = now, + DnsVerifiedAt = now, + TlsReadyAt = now + }, + new TenantFrontendConfig + { + TenantId = tenantId, + PublishedAt = now, + ConfigVersion = 1 + }, + new TenantAuthPolicy + { + TenantId = tenantId, + AllowedStudentLoginMethods = ["password"] + }); + await PublishFixtureVersionAsync(factory, plan.Version.Id); + + using var client = factory.CreateClient(); + client.UseAccessToken(await client.LoginAsTenantAsync(tenantId, phone)); + var response = await client.GetAsync("/api/tenant-onboarding/status"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + using var payload = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync()); + Assert.True(payload.RootElement.GetProperty("readyForStudentTraffic").GetBoolean()); + Assert.Equal( + payload.RootElement.GetProperty("requiredSteps").GetInt32(), + payload.RootElement.GetProperty("completedRequiredSteps").GetInt32()); + var requiredIncomplete = payload.RootElement.GetProperty("steps").EnumerateArray() + .Where(value => value.GetProperty("required").GetBoolean() && !value.GetProperty("completed").GetBoolean()) + .ToArray(); + Assert.Empty(requiredIncomplete); + } + + [Fact] + public async Task Subscription_lifecycle_activates_scheduled_plan_once_at_period_boundary() + { + await using var factory = new ApiTestFactory(); + var tenantId = Guid.NewGuid(); + var subscriptionId = Guid.NewGuid(); + var periodEnd = DateTimeOffset.UtcNow.AddMinutes(-5); + var oldFeature = new SaasFeature + { + Code = $"lifecycle.old.{Guid.NewGuid():N}", + Name = "Old plan feature", + Category = "integration", + Status = SaasFeatureStatus.Active + }; + var newFeature = new SaasFeature + { + Code = $"lifecycle.new.{Guid.NewGuid():N}", + Name = "New plan feature", + Category = "integration", + Status = SaasFeatureStatus.Active + }; + var oldOffering = new SaasOffering + { + Code = $"lifecycle-old-{Guid.NewGuid():N}", + Name = "Old lifecycle plan", + Type = SaasOfferingType.BasePlan, + Status = SaasOfferingStatus.Active + }; + var newOffering = new SaasOffering + { + Code = $"lifecycle-new-{Guid.NewGuid():N}", + Name = "New lifecycle plan", + Type = SaasOfferingType.BasePlan, + Status = SaasOfferingStatus.Active + }; + var oldVersion = CreatePublishedVersion(oldOffering.Id, 1); + var newVersion = CreatePublishedVersion(newOffering.Id, 1); + var nextPeriodEnd = periodEnd.AddMonths(1); + await factory.SeedAsync( + new Tenant + { + Id = tenantId, + Slug = $"lifecycle-{tenantId:N}", + Name = "Lifecycle Tenant", + Status = TenantStatus.Active, + BillingStatus = BillingStatus.Active + }, + oldFeature, + newFeature, + oldOffering, + newOffering, + oldVersion, + newVersion, + new SaasOfferingVersionFeature + { + OfferingVersionId = oldVersion.Id, + FeatureCode = oldFeature.Code + }, + new SaasOfferingVersionFeature + { + OfferingVersionId = newVersion.Id, + FeatureCode = newFeature.Code + }, + new TenantSaasSubscription + { + Id = subscriptionId, + TenantId = tenantId, + BaseOfferingVersionId = oldVersion.Id, + ScheduledBaseOfferingVersionId = newVersion.Id, + Status = TenantSaasSubscriptionStatus.Active, + StartsAt = periodEnd.AddMonths(-1), + CurrentPeriodStart = periodEnd.AddMonths(-1), + CurrentPeriodEnd = periodEnd + }, + new TenantSaasSubscriptionItem + { + TenantId = tenantId, + SubscriptionId = subscriptionId, + OfferingVersionId = oldVersion.Id, + ItemType = TenantSaasSubscriptionItemType.BasePlan, + Status = TenantSaasSubscriptionItemStatus.Active, + StartsAt = periodEnd.AddMonths(-1), + EndsAt = periodEnd + }, + new TenantSaasSubscriptionItem + { + TenantId = tenantId, + SubscriptionId = subscriptionId, + OfferingVersionId = newVersion.Id, + ItemType = TenantSaasSubscriptionItemType.BasePlan, + Status = TenantSaasSubscriptionItemStatus.Scheduled, + StartsAt = periodEnd, + EndsAt = nextPeriodEnd + }); + + async Task RunLifecycleAsync(string reason) + { + using var lifecycleScope = factory.CreateSystemScope(reason); + return await lifecycleScope.ServiceProvider + .GetRequiredService() + .ProcessDueAsync(periodEnd.AddMinutes(1)); + } + + var concurrentResults = await Task.WhenAll( + RunLifecycleAsync("Run first concurrent SaaS subscription lifecycle worker"), + RunLifecycleAsync("Run second concurrent SaaS subscription lifecycle worker")); + Assert.Equal(1, concurrentResults.Count(value => value == 1)); + Assert.Equal(1, concurrentResults.Count(value => value == 0)); + Assert.Equal(0, await RunLifecycleAsync("Repeat completed SaaS subscription lifecycle worker")); + + using var verificationScope = factory.CreateSystemScope("Verify scheduled SaaS subscription activation"); + var dbContext = verificationScope.ServiceProvider.GetRequiredService(); + var subscription = await dbContext.TenantSaasSubscriptions.AsNoTracking() + .SingleAsync(value => value.Id == subscriptionId); + var items = await dbContext.TenantSaasSubscriptionItems.AsNoTracking() + .Where(value => value.SubscriptionId == subscriptionId) + .OrderBy(value => value.StartsAt) + .ToArrayAsync(); + Assert.Equal(newVersion.Id, subscription.BaseOfferingVersionId); + Assert.Null(subscription.ScheduledBaseOfferingVersionId); + Assert.Equal(TenantSaasSubscriptionStatus.Active, subscription.Status); + Assert.Equal(periodEnd, subscription.CurrentPeriodStart); + Assert.Equal(nextPeriodEnd, subscription.CurrentPeriodEnd); + Assert.Equal(1, subscription.LifecycleVersion); + Assert.Equal(TenantSaasSubscriptionItemStatus.Expired, items[0].Status); + Assert.Equal(TenantSaasSubscriptionItemStatus.Active, items[1].Status); + Assert.Single(await dbContext.AuditLogs.AsNoTracking().Where(value => + value.TenantId == tenantId && + value.Action == "platform_billing.subscription.scheduled_plan_activated").ToArrayAsync()); + } + + [Fact] + public async Task Subscription_lifecycle_applies_cancel_trial_past_due_and_expired_transitions_idempotently() + { + await using var factory = new ApiTestFactory(); + var asOf = DateTimeOffset.UtcNow; + var feature = new SaasFeature + { + Code = $"lifecycle.status.{Guid.NewGuid():N}", + Name = "Lifecycle status feature", + Category = "integration", + Status = SaasFeatureStatus.Active + }; + var offering = new SaasOffering + { + Code = $"lifecycle-status-{Guid.NewGuid():N}", + Name = "Lifecycle status plan", + Type = SaasOfferingType.BasePlan, + Status = SaasOfferingStatus.Active + }; + var version = CreatePublishedVersion(offering.Id, 1); + var fixtures = new[] + { + CreateLifecycleFixture(version.Id, TenantSaasSubscriptionStatus.Active, asOf.AddDays(-1), cancelAtPeriodEnd: true), + CreateLifecycleFixture(version.Id, TenantSaasSubscriptionStatus.Trial, asOf.AddDays(-1)), + CreateLifecycleFixture(version.Id, TenantSaasSubscriptionStatus.Active, asOf.AddDays(-8)), + CreateLifecycleFixture(version.Id, TenantSaasSubscriptionStatus.PastDue, asOf.AddDays(-8)) + }; + await factory.SeedAsync( + [ + feature, + offering, + version, + new SaasOfferingVersionFeature + { + OfferingVersionId = version.Id, + FeatureCode = feature.Code + }, + ..fixtures.SelectMany(value => new object[] { value.Tenant, value.Subscription, value.Item }) + ]); + + using var lifecycleScope = factory.CreateSystemScope("Run SaaS subscription terminal lifecycle transitions"); + var lifecycle = lifecycleScope.ServiceProvider.GetRequiredService(); + Assert.Equal(4, await lifecycle.ProcessDueAsync(asOf)); + Assert.Equal(1, await lifecycle.ProcessDueAsync(asOf)); + Assert.Equal(0, await lifecycle.ProcessDueAsync(asOf)); + + using var verificationScope = factory.CreateSystemScope("Verify SaaS subscription terminal lifecycle transitions"); + var dbContext = verificationScope.ServiceProvider.GetRequiredService(); + var subscriptions = await dbContext.TenantSaasSubscriptions.AsNoTracking() + .Where(value => fixtures.Select(fixture => fixture.Subscription.Id).Contains(value.Id)) + .ToDictionaryAsync(value => value.Id); + Assert.Equal(TenantSaasSubscriptionStatus.Cancelled, subscriptions[fixtures[0].Subscription.Id].Status); + Assert.Equal(TenantSaasSubscriptionStatus.Expired, subscriptions[fixtures[1].Subscription.Id].Status); + Assert.Equal(TenantSaasSubscriptionStatus.Expired, subscriptions[fixtures[2].Subscription.Id].Status); + Assert.Equal(TenantSaasSubscriptionStatus.Expired, subscriptions[fixtures[3].Subscription.Id].Status); + Assert.Equal(1, subscriptions[fixtures[0].Subscription.Id].LifecycleVersion); + Assert.Equal(1, subscriptions[fixtures[1].Subscription.Id].LifecycleVersion); + Assert.Equal(2, subscriptions[fixtures[2].Subscription.Id].LifecycleVersion); + Assert.Equal(1, subscriptions[fixtures[3].Subscription.Id].LifecycleVersion); + } + + private static ApiTestFactory PlatformFactory() => new(configurationOverrides: new Dictionary + { + ["Tenancy:Resolution:PlatformHosts:0"] = "localhost" + }); + + private static async Task<(Guid UserId, string Email)> SeedPlatformAdminAsync(ApiTestFactory factory) + { + var userId = Guid.NewGuid(); + var roleId = Guid.NewGuid(); + var email = $"saas-platform-{Guid.NewGuid():N}@example.test"; + var modules = BackendPermissions.Platform + .Select(PermissionModuleCatalog.ResolvePermissionModuleCode) + .Distinct(StringComparer.Ordinal) + .Select(code => new PermissionModule + { + Code = code, + Name = code, + Area = BackendPermissionArea.Platform, + RequiredFeatureCode = PermissionModuleCatalog.RequiredFeatures[code] + }) + .Cast(); + var permissions = BackendPermissions.Platform.Select(code => new BackendPermission + { + Code = code, + Name = code, + Area = BackendPermissionArea.Platform, + PermissionModuleCode = PermissionModuleCatalog.ResolvePermissionModuleCode(code), + IsSystem = true + }).Cast(); + await factory.SeedAsync( + [ + ..modules, + ..permissions, + new User + { + Id = userId, + Email = email, + NormalizedEmail = email.ToUpperInvariant(), + UserName = email, + NormalizedUserName = email.ToUpperInvariant(), + Name = "SaaS Platform Admin", + PrimaryRole = "platform_admin", + RawProfile = JsonDefaults.Object() + }.WithTestPassword(), + new PlatformBackendRole + { + Id = roleId, + Code = $"saas_platform_admin_{Guid.NewGuid():N}", + Name = "SaaS Platform Admin", + Status = BackendRoleStatus.Active, + IsSystem = true + }, + ..BackendPermissions.Platform.Select(code => new PlatformBackendRolePermission + { + RoleId = roleId, + PermissionCode = code + }), + new PlatformBackendUserRole + { + UserId = userId, + RoleId = roleId + } + ]); + return (userId, email); + } + + private static async Task<(Guid TenantId, Guid UserId, string Phone)> SeedTenantAdminWithoutSubscriptionAsync( + ApiTestFactory factory, + string slugPrefix) + { + var tenantId = Guid.NewGuid(); + var userId = Guid.NewGuid(); + var phone = $"138{Random.Shared.Next(10_000_000, 99_999_999)}"; + await factory.SeedAsync( + new Tenant + { + Id = tenantId, + Slug = tenantId.ToString("N"), + Name = $"{slugPrefix} tenant", + Status = TenantStatus.Active, + BillingStatus = BillingStatus.Trial + }, + new User + { + Id = userId, + Phone = phone, + Name = $"{slugPrefix} admin" + }.WithTestPassword(), + new TenantMembership + { + TenantId = tenantId, + UserId = userId, + Role = TenantRole.TenantAdmin, + Status = MembershipStatus.Active + }, + new SaasOffering + { + Code = $"fixture-suppress-default-{Guid.NewGuid():N}", + Name = "Fixture only", + Type = SaasOfferingType.AddOn, + Status = SaasOfferingStatus.Draft + }); + return (tenantId, userId, phone); + } + + private static async Task<(Guid OfferingId, Guid VersionId)> SeedPublishedPlanAsync( + ApiTestFactory factory, + string featureCode, + (string MetricCode, long Value)? limit) + { + var graph = CreatePublishedPlan(featureCode); + var entities = new List { graph.Feature, graph.Offering, graph.Version, graph.Entitlement }; + if (limit is { } quota) + { + entities.Add(new SaasFeatureLimitDefinition + { + MetricCode = quota.MetricCode, + FeatureCode = featureCode, + Name = quota.MetricCode, + Unit = "count", + Kind = SaasFeatureLimitKind.Period, + WarningPercent = 80, + IsHardLimit = true + }); + entities.Add(new SaasOfferingVersionLimit + { + OfferingVersionId = graph.Version.Id, + MetricCode = quota.MetricCode, + LimitValue = quota.Value + }); + } + await factory.SeedAsync([.. entities]); + await PublishFixtureVersionAsync(factory, graph.Version.Id); + return (graph.Offering.Id, graph.Version.Id); + } + + private static async Task SeedQuotaSubscriptionAsync(ApiTestFactory factory, Guid tenantId, long limit) + { + var graph = CreatePublishedPlan(SaasFeatureCatalog.PrivateQuestionBank); + var now = DateTimeOffset.UtcNow; + await factory.SeedAsync( + new Tenant + { + Id = tenantId, + Slug = $"quota-{tenantId:N}", + Name = "Quota Tenant", + Status = TenantStatus.Active, + BillingStatus = BillingStatus.Active + }, + graph.Feature, + graph.Offering, + graph.Version, + graph.Entitlement, + new SaasFeatureLimitDefinition + { + MetricCode = SaasQuotaMetricCatalog.ExportCount, + FeatureCode = SaasFeatureCatalog.PrivateQuestionBank, + Name = "Export count", + Unit = "count", + Kind = SaasFeatureLimitKind.Period, + WarningPercent = 80, + IsHardLimit = true + }, + new SaasOfferingVersionLimit + { + OfferingVersionId = graph.Version.Id, + MetricCode = SaasQuotaMetricCatalog.ExportCount, + LimitValue = limit + }, + new TenantSaasSubscription + { + Id = graph.SubscriptionId, + TenantId = tenantId, + BaseOfferingVersionId = graph.Version.Id, + Status = TenantSaasSubscriptionStatus.Active, + StartsAt = now.AddDays(-1), + CurrentPeriodStart = now.AddDays(-1), + CurrentPeriodEnd = now.AddMonths(1) + }, + new TenantSaasSubscriptionItem + { + TenantId = tenantId, + SubscriptionId = graph.SubscriptionId, + OfferingVersionId = graph.Version.Id, + ItemType = TenantSaasSubscriptionItemType.BasePlan, + Status = TenantSaasSubscriptionItemStatus.Active, + StartsAt = now.AddDays(-1), + EndsAt = now.AddMonths(1) + }); + await PublishFixtureVersionAsync(factory, graph.Version.Id); + } + + private static PublishedPlanGraph CreatePublishedPlan(string featureCode) + { + var offering = new SaasOffering + { + Id = Guid.NewGuid(), + Code = $"plan-{Guid.NewGuid():N}", + Name = "Integration SaaS Plan", + Type = SaasOfferingType.BasePlan, + Status = SaasOfferingStatus.Active + }; + var version = new SaasOfferingVersion + { + Id = Guid.NewGuid(), + OfferingId = offering.Id, + Version = 1, + Status = SaasOfferingVersionStatus.Draft, + BillingCycle = PlatformBillingCycle.Monthly, + OriginalAmountCents = 1_000, + AmountCents = 1_000, + Currency = "CNY", + EffectiveAt = DateTimeOffset.UtcNow.AddDays(-1), + PublishedAt = null + }; + return new PublishedPlanGraph( + new SaasFeature + { + Code = featureCode, + Name = featureCode, + Category = "integration", + Status = SaasFeatureStatus.Active + }, + offering, + version, + new SaasOfferingVersionFeature + { + OfferingVersionId = version.Id, + FeatureCode = featureCode + }, + Guid.NewGuid()); + } + + private static SaasOfferingVersion CreatePublishedVersion(Guid offeringId, int version) => new() + { + OfferingId = offeringId, + Version = version, + Status = SaasOfferingVersionStatus.Published, + BillingCycle = PlatformBillingCycle.Monthly, + OriginalAmountCents = 1_000, + AmountCents = 1_000, + Currency = "CNY", + EffectiveAt = DateTimeOffset.UtcNow.AddDays(-1), + PublishedAt = DateTimeOffset.UtcNow.AddDays(-1) + }; + + private static LifecycleFixture CreateLifecycleFixture( + Guid offeringVersionId, + TenantSaasSubscriptionStatus status, + DateTimeOffset periodEnd, + bool cancelAtPeriodEnd = false) + { + var tenantId = Guid.NewGuid(); + var subscriptionId = Guid.NewGuid(); + var periodStart = periodEnd.AddMonths(-1); + return new( + new Tenant + { + Id = tenantId, + Slug = $"lifecycle-status-{tenantId:N}", + Name = "Lifecycle Status Tenant", + Status = TenantStatus.Active, + BillingStatus = status == TenantSaasSubscriptionStatus.PastDue + ? BillingStatus.PastDue + : BillingStatus.Active + }, + new TenantSaasSubscription + { + Id = subscriptionId, + TenantId = tenantId, + BaseOfferingVersionId = offeringVersionId, + Status = status, + StartsAt = periodStart, + CurrentPeriodStart = periodStart, + CurrentPeriodEnd = periodEnd, + CancelAtPeriodEnd = cancelAtPeriodEnd + }, + new TenantSaasSubscriptionItem + { + TenantId = tenantId, + SubscriptionId = subscriptionId, + OfferingVersionId = offeringVersionId, + ItemType = TenantSaasSubscriptionItemType.BasePlan, + Status = TenantSaasSubscriptionItemStatus.Active, + StartsAt = periodStart, + EndsAt = periodEnd + }); + } + + private static async Task PublishFixtureVersionAsync(ApiTestFactory factory, Guid versionId) + { + using var scope = factory.CreateSystemScope("Publish integration SaaS plan fixture"); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var version = await dbContext.SaasOfferingVersions.SingleAsync(value => value.Id == versionId); + version.Status = SaasOfferingVersionStatus.Published; + version.PublishedAt = DateTimeOffset.UtcNow; + version.EffectiveAt ??= version.PublishedAt; + await dbContext.SaveChangesAsync(); + } + + private static async Task ReadGuidAsync(HttpResponseMessage response, string propertyName) + { + using var payload = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync()); + return payload.RootElement.GetProperty(propertyName).GetGuid(); + } + + private static async Task ReadStringAsync(HttpResponseMessage response, string propertyName) + { + using var payload = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync()); + return payload.RootElement.GetProperty(propertyName).GetString() + ?? throw new InvalidOperationException($"Response property '{propertyName}' was null."); + } + + private static Task ReadCodeAsync(HttpResponseMessage response) => ReadStringAsync(response, "code"); + + private sealed record PublishedPlanGraph( + SaasFeature Feature, + SaasOffering Offering, + SaasOfferingVersion Version, + SaasOfferingVersionFeature Entitlement, + Guid SubscriptionId); + + private sealed record LifecycleFixture( + Tenant Tenant, + TenantSaasSubscription Subscription, + TenantSaasSubscriptionItem Item); +} diff --git a/Tiku.IntegrationTests/Api/TenantPublicEndpointTests.cs b/Tiku.IntegrationTests/Api/TenantPublicEndpointTests.cs index eb37baa..f47bd3e 100644 --- a/Tiku.IntegrationTests/Api/TenantPublicEndpointTests.cs +++ b/Tiku.IntegrationTests/Api/TenantPublicEndpointTests.cs @@ -174,7 +174,7 @@ public sealed class TenantPublicEndpointTests Slug = "master", Name = "升本刷题通", Status = TenantStatus.Active, - Mode = TenantMode.PlatformOwned + Mode = TenantMode.Saas }, new TenantDomain { diff --git a/Tiku.IntegrationTests/MigrationExecutionTests.cs b/Tiku.IntegrationTests/MigrationExecutionTests.cs index 3d03734..b3a117c 100644 --- a/Tiku.IntegrationTests/MigrationExecutionTests.cs +++ b/Tiku.IntegrationTests/MigrationExecutionTests.cs @@ -26,7 +26,7 @@ public sealed class MigrationExecutionTests } [Fact] - public async Task Migration_script_contains_tenant_isolation_guard_triggers() + public async Task Migration_script_contains_database_guard_triggers() { await using var factory = new ApiTestFactory(); using var scope = factory.Services.CreateScope(); @@ -41,5 +41,14 @@ public sealed class MigrationExecutionTests Assert.Contains("drop trigger if exists trg_taxonomy_nodes_parent_platform_or_self_owner", script); Assert.Contains("create or replace function tiku_guard_taxonomy_parent_owner()", script); Assert.Contains("create trigger trg_taxonomy_nodes_parent_platform_or_self_owner", script); + Assert.Contains("drop trigger if exists trg_saas_offering_versions_published_immutable", script); + Assert.Contains("create or replace function tiku_guard_saas_offering_version_immutable()", script); + Assert.Contains("create trigger trg_saas_offering_versions_published_immutable", script); + Assert.Contains("create or replace function tiku_guard_saas_offering_version_child_immutable()", script); + Assert.Contains("create trigger trg_saas_offering_version_features_published_immutable", script); + Assert.Contains("create trigger trg_saas_offering_version_limits_published_immutable", script); + Assert.Contains("create trigger trg_tenant_saas_subscriptions_base_plan", script); + Assert.Contains("create trigger trg_tenant_saas_subscription_items_offering_type", script); + Assert.Contains("create or replace function tiku_guard_saas_subscription_offering_type()", script); } } diff --git a/Tiku.IntegrationTests/PersistenceModelTests.cs b/Tiku.IntegrationTests/PersistenceModelTests.cs index 8b66c5c..54205c4 100644 --- a/Tiku.IntegrationTests/PersistenceModelTests.cs +++ b/Tiku.IntegrationTests/PersistenceModelTests.cs @@ -89,8 +89,16 @@ public sealed class PersistenceModelTests Assert.Contains("activation_codes", tableNames); Assert.Contains("coupons", tableNames); Assert.Contains("coupon_redemptions", tableNames); - Assert.Contains("tenant_subscriptions", tableNames); - Assert.Contains("tenant_usage_records", tableNames); + Assert.Contains("saas_features", tableNames); + Assert.Contains("permission_modules", tableNames); + Assert.Contains("saas_offerings", tableNames); + Assert.Contains("saas_offering_versions", tableNames); + Assert.Contains("tenant_saas_subscriptions", tableNames); + Assert.Contains("tenant_saas_subscription_items", tableNames); + Assert.Contains("tenant_feature_usage", tableNames); + Assert.Contains("platform_billing_orders", tableNames); + Assert.Contains("platform_billing_payments", tableNames); + Assert.Contains("platform_billing_invoices", tableNames); Assert.Contains("commerce_refund_requests", tableNames); Assert.Contains("commerce_refund_events", tableNames); Assert.Contains("commerce_reconciliation_batches", tableNames); @@ -128,16 +136,13 @@ public sealed class PersistenceModelTests Assert.Contains("tenant_content_notifications", tableNames); Assert.Contains("tenant_theme_templates", tableNames); Assert.Contains("tenant_theme_configs", tableNames); - Assert.Contains("platform_saas_plans", tableNames); Assert.Contains("tenant_billing_profiles", tableNames); - Assert.Contains("tenant_invoices", tableNames); - Assert.Contains("tenant_invoice_items", tableNames); - Assert.Contains("tenant_invoice_payments", tableNames); - Assert.Contains("tenant_invoice_reminders", tableNames); + Assert.Contains("platform_billing_invoices", tableNames); + Assert.Contains("platform_billing_invoice_reminders", tableNames); Assert.Contains("platform_audit_alert_rules", tableNames); Assert.Contains("platform_audit_alerts", tableNames); - Assert.Contains("platform_dunning_notification_channels", tableNames); - Assert.Contains("platform_dunning_notification_events", tableNames); + Assert.Contains("platform_billing_dunning_notification_channels", tableNames); + Assert.Contains("platform_billing_dunning_notification_events", tableNames); Assert.Contains("pb_import_runs", tableNames); Assert.Contains("pb_raw_records", tableNames); Assert.Contains("pb_import_issues", tableNames); @@ -572,8 +577,7 @@ public sealed class PersistenceModelTests [InlineData(typeof(Payment), nameof(Payment.RawPayload), "'{}'::jsonb")] [InlineData(typeof(PaymentEvent), nameof(PaymentEvent.Payload), "'{}'::jsonb")] [InlineData(typeof(Entitlement), nameof(Entitlement.Metadata), "'{}'::jsonb")] - [InlineData(typeof(TenantSubscription), nameof(TenantSubscription.Metadata), "'{}'::jsonb")] - [InlineData(typeof(TenantUsageRecord), nameof(TenantUsageRecord.Metadata), "'{}'::jsonb")] + [InlineData(typeof(TenantSaasSubscription), nameof(TenantSaasSubscription.Metadata), "'{}'::jsonb")] [InlineData(typeof(CommerceRefundRequest), nameof(CommerceRefundRequest.Metadata), "'{}'::jsonb")] [InlineData(typeof(CommerceRefundEvent), nameof(CommerceRefundEvent.Details), "'{}'::jsonb")] [InlineData(typeof(CommerceReconciliationBatch), nameof(CommerceReconciliationBatch.Metadata), "'{}'::jsonb")] @@ -802,20 +806,18 @@ public sealed class PersistenceModelTests } [Theory] - [InlineData(typeof(PlatformSaasPlan), nameof(PlatformSaasPlan.IncludedQuotas), "'{}'::jsonb")] - [InlineData(typeof(PlatformSaasPlan), nameof(PlatformSaasPlan.OveragePrices), "'{}'::jsonb")] - [InlineData(typeof(PlatformSaasPlan), nameof(PlatformSaasPlan.FeatureFlags), "'{}'::jsonb")] + [InlineData(typeof(SaasOfferingVersion), nameof(SaasOfferingVersion.Metadata), "'{}'::jsonb")] + [InlineData(typeof(PlatformBillingQuote), nameof(PlatformBillingQuote.FeatureSnapshot), "'[]'::jsonb")] + [InlineData(typeof(PlatformBillingQuote), nameof(PlatformBillingQuote.LimitSnapshot), "'{}'::jsonb")] [InlineData(typeof(TenantBillingProfile), nameof(TenantBillingProfile.Metadata), "'{}'::jsonb")] - [InlineData(typeof(TenantInvoice), nameof(TenantInvoice.Metadata), "'{}'::jsonb")] - [InlineData(typeof(TenantInvoiceItem), nameof(TenantInvoiceItem.Metadata), "'{}'::jsonb")] - [InlineData(typeof(TenantInvoicePayment), nameof(TenantInvoicePayment.RawPayload), "'{}'::jsonb")] - [InlineData(typeof(TenantInvoiceReminder), nameof(TenantInvoiceReminder.Metadata), "'{}'::jsonb")] + [InlineData(typeof(PlatformBillingInvoice), nameof(PlatformBillingInvoice.BillingProfileSnapshot), "'{}'::jsonb")] + [InlineData(typeof(PlatformBillingInvoiceReminder), nameof(PlatformBillingInvoiceReminder.Metadata), "'{}'::jsonb")] [InlineData(typeof(PlatformAuditAlertRule), nameof(PlatformAuditAlertRule.Conditions), "'{}'::jsonb")] [InlineData(typeof(PlatformAuditAlertRule), nameof(PlatformAuditAlertRule.Metadata), "'{}'::jsonb")] [InlineData(typeof(PlatformAuditAlert), nameof(PlatformAuditAlert.Details), "'{}'::jsonb")] - [InlineData(typeof(PlatformDunningNotificationChannel), nameof(PlatformDunningNotificationChannel.Metadata), "'{}'::jsonb")] - [InlineData(typeof(PlatformDunningNotificationEvent), nameof(PlatformDunningNotificationEvent.RequestPayload), "'{}'::jsonb")] - [InlineData(typeof(PlatformDunningNotificationEvent), nameof(PlatformDunningNotificationEvent.Metadata), "'{}'::jsonb")] + [InlineData(typeof(PlatformBillingDunningNotificationChannel), nameof(PlatformBillingDunningNotificationChannel.Metadata), "'{}'::jsonb")] + [InlineData(typeof(PlatformBillingDunningNotificationEvent), nameof(PlatformBillingDunningNotificationEvent.RequestPayload), "'{}'::jsonb")] + [InlineData(typeof(PlatformBillingDunningNotificationEvent), nameof(PlatformBillingDunningNotificationEvent.Metadata), "'{}'::jsonb")] public void Platform_operations_json_properties_are_mapped_to_jsonb( Type entityType, string propertyName, @@ -836,27 +838,23 @@ public sealed class PersistenceModelTests { using var context = new TikuDbContext(Options); - AssertHasUniqueIndex(nameof(PlatformSaasPlan.Code)); - AssertHasUniqueIndex( - nameof(TenantInvoice.TenantId), - nameof(TenantInvoice.InvoiceNo)); - AssertHasUniqueIndex( - nameof(TenantInvoice.TenantId), - nameof(TenantInvoice.BillingPeriodStart), - nameof(TenantInvoice.BillingPeriodEnd)); - AssertHasUniqueIndex( - nameof(TenantInvoiceReminder.TenantId), - nameof(TenantInvoiceReminder.InvoiceId), - nameof(TenantInvoiceReminder.ReminderType), - nameof(TenantInvoiceReminder.Channel), - nameof(TenantInvoiceReminder.ReminderDate)); + AssertHasUniqueIndex(nameof(SaasFeature.Code)); + AssertHasUniqueIndex( + nameof(PlatformBillingInvoice.TenantId), + nameof(PlatformBillingInvoice.InvoiceNo)); + AssertHasUniqueIndex( + nameof(PlatformBillingInvoiceReminder.TenantId), + nameof(PlatformBillingInvoiceReminder.InvoiceId), + nameof(PlatformBillingInvoiceReminder.ReminderType), + nameof(PlatformBillingInvoiceReminder.Channel), + nameof(PlatformBillingInvoiceReminder.ReminderDate)); AssertHasUniqueIndex(nameof(PlatformAuditAlertRule.Code)); AssertHasUniqueIndex(nameof(PlatformAuditAlert.RuleId), nameof(PlatformAuditAlert.AuditLogId)); - AssertHasUniqueIndex(nameof(PlatformDunningNotificationChannel.ChannelCode)); - AssertHasUniqueIndex( - nameof(PlatformDunningNotificationEvent.TenantId), - nameof(PlatformDunningNotificationEvent.ChannelId), - nameof(PlatformDunningNotificationEvent.ReminderId)); + AssertHasUniqueIndex(nameof(PlatformBillingDunningNotificationChannel.ChannelCode)); + AssertHasUniqueIndex( + nameof(PlatformBillingDunningNotificationEvent.TenantId), + nameof(PlatformBillingDunningNotificationEvent.ChannelId), + nameof(PlatformBillingDunningNotificationEvent.ReminderId)); Assert.Equal( "text[]", @@ -865,12 +863,12 @@ public sealed class PersistenceModelTests .GetColumnType()); Assert.Equal( "uuid[]", - context.Model.FindEntityType(typeof(PlatformDunningNotificationChannel))! - .FindProperty(nameof(PlatformDunningNotificationChannel.TenantIds))! + context.Model.FindEntityType(typeof(PlatformBillingDunningNotificationChannel))! + .FindProperty(nameof(PlatformBillingDunningNotificationChannel.TenantIds))! .GetColumnType()); - var total = context.Model.FindEntityType(typeof(TenantInvoice))! - .FindProperty(nameof(TenantInvoice.TotalCents))!; + var total = context.Model.FindEntityType(typeof(PlatformBillingInvoice))! + .FindProperty(nameof(PlatformBillingInvoice.TotalAmountCents))!; Assert.Equal(typeof(int), total.ClrType); void AssertHasUniqueIndex(params string[] propertyNames) diff --git a/Tiku.IntegrationTests/PhaseThreeTenantIsolationTests.cs b/Tiku.IntegrationTests/PhaseThreeTenantIsolationTests.cs index 18e7e4d..031877e 100644 --- a/Tiku.IntegrationTests/PhaseThreeTenantIsolationTests.cs +++ b/Tiku.IntegrationTests/PhaseThreeTenantIsolationTests.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Tiku.Application.QuestionBanks; using Tiku.Application.Learning; +using Tiku.Application.Security; using Tiku.Domain.Common; using Tiku.Domain.Commerce; using Tiku.Domain.Catalog; @@ -9,6 +10,7 @@ using Tiku.Domain.Content; using Tiku.Domain.QuestionBanks; using Tiku.Domain.Identity; using Tiku.Domain.Learning; +using Tiku.Domain.Platform; using Tiku.Domain.Tenancy; using Tiku.Infrastructure.Persistence; using Tiku.IntegrationTests.Api; @@ -138,18 +140,22 @@ public sealed class PhaseThreeTenantIsolationTests } [Theory] - [InlineData(TenantSubscriptionStatus.Trial, true)] - [InlineData(TenantSubscriptionStatus.Active, true)] - [InlineData(TenantSubscriptionStatus.PastDue, false)] - [InlineData(TenantSubscriptionStatus.Cancelled, false)] + [InlineData(TenantSaasSubscriptionStatus.Trial, true)] + [InlineData(TenantSaasSubscriptionStatus.Active, true)] + [InlineData(TenantSaasSubscriptionStatus.PastDue, false)] + [InlineData(TenantSaasSubscriptionStatus.Cancelled, false)] public async Task Public_question_reference_requires_current_subscription( - TenantSubscriptionStatus subscriptionStatus, + TenantSaasSubscriptionStatus subscriptionStatus, bool allowed) { await using var factory = new ApiTestFactory(); var platformId = Guid.NewGuid(); var tenantId = Guid.NewGuid(); var questionId = Guid.NewGuid(); + var offeringId = Guid.NewGuid(); + var versionId = Guid.NewGuid(); + var subscriptionId = Guid.NewGuid(); + var now = DateTimeOffset.UtcNow; await factory.SeedAsync( Tenant(platformId, "platform", TenantMode.PlatformOwned), Tenant(tenantId, "tenant-a"), @@ -160,13 +166,42 @@ public sealed class PhaseThreeTenantIsolationTests Type = "choice", Status = QuestionStatus.Published }, - new TenantSubscription + new SaasFeature + { + Code = SaasFeatureCatalog.Practice, + Name = "Practice", + Category = "learning", + Status = SaasFeatureStatus.Active + }, + new SaasOffering { Id = offeringId, Code = "standard", Name = "Standard", Type = SaasOfferingType.BasePlan, Status = SaasOfferingStatus.Active }, + new SaasOfferingVersion + { + Id = versionId, + OfferingId = offeringId, + Status = SaasOfferingVersionStatus.Published, + OriginalAmountCents = 100, + AmountCents = 100, + PublishedAt = now.AddDays(-1) + }, + new SaasOfferingVersionFeature { OfferingVersionId = versionId, FeatureCode = SaasFeatureCatalog.Practice }, + new TenantSaasSubscription + { + Id = subscriptionId, + TenantId = tenantId, + BaseOfferingVersionId = versionId, + Status = subscriptionStatus, + StartsAt = now.AddDays(-1), + CurrentPeriodStart = now.AddDays(-1), + CurrentPeriodEnd = now.AddDays(1) + }, + new TenantSaasSubscriptionItem { TenantId = tenantId, - PlanCode = "standard", - Status = subscriptionStatus, - StartsAt = DateTimeOffset.UtcNow.AddDays(-1), - ExpiresAt = DateTimeOffset.UtcNow.AddDays(1) + SubscriptionId = subscriptionId, + OfferingVersionId = versionId, + Status = TenantSaasSubscriptionItemStatus.Active, + StartsAt = now.AddDays(-1), + EndsAt = now.AddDays(1) }); using var scope = factory.CreateTenantScope(tenantId, "tenant-a"); @@ -210,14 +245,7 @@ public sealed class PhaseThreeTenantIsolationTests await factory.SeedAsync( Tenant(platformId, "platform", TenantMode.PlatformOwned), Tenant(tenantId, "tenant-a"), - new User { Id = userId, Phone = "13800009999" }, - new TenantSubscription - { - TenantId = tenantId, - PlanCode = "standard", - Status = TenantSubscriptionStatus.Active, - ExpiresAt = DateTimeOffset.UtcNow.AddDays(30) - }); + new User { Id = userId, Phone = "13800009999" }); await factory.SeedQuestionWithVersionAsync( new Question { diff --git a/Tiku.UnitTests/Auth/AuthServiceTests.cs b/Tiku.UnitTests/Auth/AuthServiceTests.cs index b15d133..4675023 100644 --- a/Tiku.UnitTests/Auth/AuthServiceTests.cs +++ b/Tiku.UnitTests/Auth/AuthServiceTests.cs @@ -158,6 +158,7 @@ public sealed class AuthServiceTests services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); var provider = services.BuildServiceProvider(); @@ -167,6 +168,46 @@ public sealed class AuthServiceTests return fixture; } + private sealed class UnlimitedFeatureAccessService : IFeatureAccessService + { + public Task EvaluateAsync( + Guid tenantId, + string featureCode, + FeatureAccessOperation operation, + CancellationToken cancellationToken = default) => + Task.FromResult(new FeatureAccessDecision(true, null, featureCode, operation)); + + public Task> GetEnabledFeaturesAsync( + Guid tenantId, + FeatureAccessOperation operation = FeatureAccessOperation.Read, + CancellationToken cancellationToken = default) => + Task.FromResult>(new HashSet(SaasFeatureCatalog.All, StringComparer.Ordinal)); + + public Task> FilterPermissionCodesAsync( + Guid tenantId, + IEnumerable permissionCodes, + FeatureAccessOperation operation = FeatureAccessOperation.Read, + CancellationToken cancellationToken = default) => + Task.FromResult>(permissionCodes.ToHashSet(StringComparer.Ordinal)); + + public Task> GetQuotaSummaryAsync( + Guid tenantId, + CancellationToken cancellationToken = default) => + Task.FromResult>([]); + + public Task TryConsumeQuotaAsync( + Guid tenantId, + string metricCode, + long amount, + CancellationToken cancellationToken = default) => Task.FromResult(true); + + public Task ReleaseQuotaAsync( + Guid tenantId, + string metricCode, + long amount, + CancellationToken cancellationToken = default) => Task.CompletedTask; + } + private async Task SeedAsync(bool includeBackendPermission) { var tenant = new Tenant { Id = Guid.NewGuid(), Slug = Guid.NewGuid().ToString("N"), Name = "Test" }; @@ -198,7 +239,7 @@ public sealed class AuthServiceTests Code = BackendPermissions.TenantDashboardView, Name = "Dashboard", Area = BackendPermissionArea.Tenant, - Module = "dashboard" + PermissionModuleCode = "tenant_dashboard" }); DbContext.TenantBackendUserRoles.Add(new TenantBackendUserRole { diff --git a/Tiku.UnitTests/Auth/SmsVerificationServiceTests.cs b/Tiku.UnitTests/Auth/SmsVerificationServiceTests.cs index 3151a8b..d60f7d4 100644 --- a/Tiku.UnitTests/Auth/SmsVerificationServiceTests.cs +++ b/Tiku.UnitTests/Auth/SmsVerificationServiceTests.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Tiku.Application.Auth; +using Tiku.Application.Security; using Tiku.Domain.Tenancy; using Tiku.Infrastructure.Auth; using Tiku.Infrastructure.Persistence; @@ -147,14 +148,76 @@ public sealed class SmsVerificationServiceTests "device-two"))); } + [Fact] + public async Task Successful_sms_consumes_quota_once_without_compensation() + { + await using var context = CreateContext(); + var tenantId = await SeedTenantAsync(context); + var quota = new RecordingFeatureAccessService(); + var service = CreateService(context, featureAccessService: quota); + + await service.CreateCodeAsync(new SendSmsCodeRequest( + tenantId, + "13800000000", + SmsPurpose.Login, + "127.0.0.1", + "quota-success")); + + Assert.Equal([(tenantId, SaasQuotaMetricCatalog.SmsCount, 1L)], quota.Consumptions); + Assert.Empty(quota.Releases); + } + + [Fact] + public async Task Failed_sms_send_releases_reserved_quota() + { + await using var context = CreateContext(); + var tenantId = await SeedTenantAsync(context); + var quota = new RecordingFeatureAccessService(); + var service = CreateService(context, new FailingSmsProvider(), featureAccessService: quota); + + await Assert.ThrowsAsync(() => service.CreateCodeAsync(new SendSmsCodeRequest( + tenantId, + "13800000000", + SmsPurpose.Login, + "127.0.0.1", + "quota-failure"))); + + Assert.Equal([(tenantId, SaasQuotaMetricCatalog.SmsCount, 1L)], quota.Consumptions); + Assert.Equal([(tenantId, SaasQuotaMetricCatalog.SmsCount, 1L)], quota.Releases); + Assert.Equal(SmsVerificationStatus.Failed, Assert.Single(context.SmsVerificationCodes).Status); + } + + [Fact] + public async Task Exhausted_sms_quota_rejects_before_provider_send() + { + await using var context = CreateContext(); + var tenantId = await SeedTenantAsync(context); + var provider = new CapturingSmsProvider(); + var quota = new RecordingFeatureAccessService { AllowConsumption = false }; + var service = CreateService(context, provider, featureAccessService: quota); + + var exception = await Assert.ThrowsAsync(() => service.CreateCodeAsync(new SendSmsCodeRequest( + tenantId, + "13800000000", + SmsPurpose.Login, + "127.0.0.1", + "quota-exhausted"))); + + Assert.Equal("feature_quota_exhausted", exception.Code); + Assert.Empty(provider.Requests); + Assert.Empty(quota.Releases); + } + private static SmsVerificationService CreateService( TikuDbContext context, ISmsProvider? provider = null, - SmsSecurityOptions? options = null) + SmsSecurityOptions? options = null, + IFeatureAccessService? featureAccessService = null) { return new SmsVerificationService( context, provider ?? new CapturingSmsProvider(), + featureAccessService ?? new RecordingFeatureAccessService(), Options.Create(options ?? ValidOptions())); } @@ -228,4 +291,53 @@ public sealed class SmsVerificationServiceTests return Task.FromResult(new SmsProviderSendResult("test", "sent", "message-id")); } } + + private sealed class FailingSmsProvider : ISmsProvider + { + public Task SendAsync( + SmsProviderSendRequest request, + CancellationToken cancellationToken = default) + { + throw new SmsProviderException("provider failed", "sms_provider_send_failed"); + } + } + + private sealed class RecordingFeatureAccessService : IFeatureAccessService + { + public bool AllowConsumption { get; init; } = true; + public List<(Guid TenantId, string MetricCode, long Amount)> Consumptions { get; } = []; + public List<(Guid TenantId, string MetricCode, long Amount)> Releases { get; } = []; + + public Task TryConsumeQuotaAsync( + Guid tenantId, + string metricCode, + long amount, + CancellationToken cancellationToken = default) + { + Consumptions.Add((tenantId, metricCode, amount)); + return Task.FromResult(AllowConsumption); + } + + public Task ReleaseQuotaAsync( + Guid tenantId, + string metricCode, + long amount, + CancellationToken cancellationToken = default) + { + Releases.Add((tenantId, metricCode, amount)); + return Task.CompletedTask; + } + + public Task EvaluateAsync(Guid tenantId, string featureCode, FeatureAccessOperation operation, CancellationToken cancellationToken = default) => + throw new NotSupportedException(); + + public Task> GetEnabledFeaturesAsync(Guid tenantId, FeatureAccessOperation operation = FeatureAccessOperation.Read, CancellationToken cancellationToken = default) => + throw new NotSupportedException(); + + public Task> FilterPermissionCodesAsync(Guid tenantId, IEnumerable permissionCodes, FeatureAccessOperation operation = FeatureAccessOperation.Read, CancellationToken cancellationToken = default) => + throw new NotSupportedException(); + + public Task> GetQuotaSummaryAsync(Guid tenantId, CancellationToken cancellationToken = default) => + throw new NotSupportedException(); + } } diff --git a/Tiku.Worker/Program.cs b/Tiku.Worker/Program.cs index 1671419..3ff872b 100644 --- a/Tiku.Worker/Program.cs +++ b/Tiku.Worker/Program.cs @@ -1,5 +1,7 @@ using Tiku.Application; using Tiku.Application.Tenancy; +using Tiku.Application.PlatformBilling; +using Tiku.Application.Security; using Tiku.Infrastructure; using Tiku.Worker; using Tiku.Infrastructure.Messaging; @@ -52,6 +54,10 @@ else if (builder.Environment.IsProduction()) } builder.Services.AddOptions() .Bind(builder.Configuration.GetSection("TenantDomains")); +builder.Services.AddOptions() + .Bind(builder.Configuration.GetSection("SaasSubscriptions")); +builder.Services.AddOptions() + .Bind(builder.Configuration.GetSection("FeatureUsageReconciliation")); builder.Services.AddHostedService(); var host = builder.Build(); diff --git a/Tiku.Worker/Worker.cs b/Tiku.Worker/Worker.cs index e21a778..57515e1 100644 --- a/Tiku.Worker/Worker.cs +++ b/Tiku.Worker/Worker.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Options; using Tiku.Application.Jobs; +using Tiku.Application.PlatformBilling; using Tiku.Application.Security; using Tiku.Application.Tenancy; using Tiku.Infrastructure.Messaging; @@ -21,12 +22,16 @@ public class Worker( try { var domainCount = await ProcessTenantDomainsAsync(stoppingToken); + var subscriptionCount = await ProcessSaasSubscriptionsAsync(stoppingToken); + var usageReconciliationCount = await ProcessFeatureUsageReconciliationAsync(stoppingToken); var jobCount = await ProcessBackgroundJobsAsync(stoppingToken); - if (domainCount > 0 || jobCount > 0) + if (domainCount > 0 || subscriptionCount > 0 || usageReconciliationCount > 0 || jobCount > 0) { logger.LogInformation( - "Worker processed {DomainCount} pending tenant domains and {JobCount} background jobs.", + "Worker processed {DomainCount} pending tenant domains, {SubscriptionCount} SaaS subscriptions, {UsageReconciliationCount} tenant usage reconciliations, and {JobCount} background jobs.", domainCount, + subscriptionCount, + usageReconciliationCount, jobCount); } } @@ -68,4 +73,24 @@ public class Worker( includeImmediateJobs: !messagingOptions.IsConfigured, cancellationToken: stoppingToken); } + + private async Task ProcessSaasSubscriptionsAsync(CancellationToken stoppingToken) + { + await using var scope = scopeFactory.CreateAsyncScope(); + scope.ServiceProvider.GetRequiredService() + .InitializeSystem(null, "SaaS subscription lifecycle discovery worker"); + return await scope.ServiceProvider + .GetRequiredService() + .ProcessDueAsync(cancellationToken: stoppingToken); + } + + private async Task ProcessFeatureUsageReconciliationAsync(CancellationToken stoppingToken) + { + await using var scope = scopeFactory.CreateAsyncScope(); + scope.ServiceProvider.GetRequiredService() + .InitializeSystem(null, "Tenant feature usage reconciliation discovery worker"); + return await scope.ServiceProvider + .GetRequiredService() + .ProcessDueAsync(stoppingToken); + } } diff --git a/Tiku.Worker/appsettings.json b/Tiku.Worker/appsettings.json index c27c9c4..64d59a8 100644 --- a/Tiku.Worker/appsettings.json +++ b/Tiku.Worker/appsettings.json @@ -17,6 +17,16 @@ "GatewayBaseUrl": null, "GatewayApiKey": null }, + "SaasSubscriptions": { + "Enabled": true, + "BatchSize": 100, + "PastDueGraceDays": 7 + }, + "FeatureUsageReconciliation": { + "Enabled": true, + "BatchSize": 100, + "IntervalMinutes": 60 + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/docs/architecture/authentication-authorization-hardening-plan.md b/docs/architecture/authentication-authorization-hardening-plan.md index 8778e47..163623f 100644 --- a/docs/architecture/authentication-authorization-hardening-plan.md +++ b/docs/architecture/authentication-authorization-hardening-plan.md @@ -49,7 +49,7 @@ Tenant Active + DataScope / Resource Scope ``` -实现说明:固定 `ProductModuleCatalog`、`PlanModuleEntitlement`、可审计 `TenantModuleOverride` 与 `ICapabilityAccessEvaluator` 已进入数据库授权 Handler;未知或缺失目录模块 fail-closed。平台接口可替换套餐模块权益和设置带原因的租户覆盖,变更在同一事务写审计与 outbox 事件。迁移仅为从未配置权益的存量套餐补齐固定目录,已有显式权益不会被扩权。 +实现说明:`SaasFeature`、显式 `PermissionModule.RequiredFeatureCode`、不可变套餐版本、`TenantFeatureOverride` 与 `IFeatureAccessService` 已进入数据库授权链路。未知 Feature、未购买模块和失效订阅均 fail-closed;角色绑定保留历史权限,但鉴权和菜单只使用当前有效权限。 验收: diff --git a/docs/architecture/saas-product-and-api-roadmap.md b/docs/architecture/saas-product-and-api-roadmap.md index 08b22d3..472074a 100644 --- a/docs/architecture/saas-product-and-api-roadmap.md +++ b/docs/architecture/saas-product-and-api-roadmap.md @@ -6,11 +6,9 @@ 现有后端已经具备继续开发的基础:Host 租户解析、强租户隔离、共享与私有题库、RBAC、Provider 解耦、租户前端运行时配置、学生练习闭环、交易基础和 Worker 基座均已落地。 -后续重点应从“迁移旧 URL”转为“补齐 SaaS 产品闭环”。当前主要缺口是: +第九阶段已经完成 SaaS 产品与交付闭环。当前主要缺口是: -- 平台向租户销售套餐的商城、订单、支付、订阅和计量闭环。 - 教师发布作业、考试、批阅和查看班级结果的教学闭环。 -- 套餐业务能力、后台权限模块和前端菜单仍需彻底分层。 - Provider 自助配置、公共题库运营和高流量查询读模型仍需完善。 ## 三端边界 @@ -55,8 +53,8 @@ | 概念 | 用途 | | --- | --- | | `SaaSFeature` | 平台可销售的业务能力 | -| `PlanFeatureEntitlement` | 套餐包含哪些业务能力 | -| `FeatureLimit` | 员工席位、学生数、题目数、存储、导出和 AI 用量 | +| `SaasOfferingVersionFeature` | 不可变套餐版本包含哪些业务能力 | +| `SaasOfferingVersionLimit` | 套餐版本的员工、学生、题目、存储、导出和 AI 额度 | | `PermissionModule` | 后台权限页面的业务分组 | | `BackendPermission` | `view/create/update/import/export/approve/retry` 等操作权限 | | `BackendMenu` | 根据有效权限生成的前端导航,不作为鉴权依据 | @@ -114,16 +112,14 @@ 两类订单、支付账号、回调地址、审计和对账不得共用业务表或服务。 -## 缺失接口与模块 +## 已完成的 SaaS 商城与交付接口 ### 平台 SaaS 商城 -- 业务模块 CRUD、发布和下架。 -- 套餐 CRUD、不可变套餐版本、价格版本和套餐对比。 -- 附加包和额度定义。 -- 租户侧套餐目录、报价、结算、下单和支付。 -- 试用转正式、续费、升级、降级和取消。 -- SaaS 订单、支付、退款、账单、发票、用量和超额计费后台。 +- 平台模块、额度定义、基础套餐、附加包和不可变版本统一在 `/api/platform-admin/saas/**`。 +- 租户目录、报价、下单、支付、订阅变更、续费、取消、用量和发票统一在 `/api/tenant-billing/**`。 +- 人工、微信和支付宝平台收款使用平台主体 Provider,订单和回调与学生商城分离。 +- `/api/tenant-onboarding/status` 汇总 Owner、订阅、域名、登录方式、Provider 和前端发布状态。 租户自助账务接口建议统一在 `/api/tenant-billing/**`: @@ -188,34 +184,13 @@ POST /api/tenant-admin/providers/secrets/rotate ## 实施顺序 -### 9A:产品能力模型 +### 9A~9C:已完成 -- 拆分套餐业务能力、权限模块、菜单和额度。 -- 定义核心能力、可售卖能力和附加包。 -- 套餐改为不可变版本,并引入统一 `IFeatureAccessService`。 -- 所有租户后台授权同时经过套餐能力和 RBAC 判断。 -- 重新生成 OpenAPI 迁移清单,替换已过时的接口比较结果。 - -退出标准:套餐决定租户买了什么,角色决定员工能做什么,菜单只表达前端展示。 - -### 9B:平台 SaaS 商城 - -- 套餐、模块、附加包和价格后台。 -- 租户报价、下单、支付和订阅变更。 -- SaaS 订单、退款、账单、发票、用量和催缴。 -- 平台支付与租户学生支付彻底分离。 - -退出标准:租户可从选择套餐到支付开通自动完成。 - -### 9C:租户自助初始化 - -- 创建 Owner、选择套餐、创建订阅。 -- 域名验证和激活。 -- 登录、SMS、OSS、支付和通知 Provider 配置与连通性测试。 -- 品牌、主题、导航和首页发布。 -- 账务中心、用量、续费和升级。 - -退出标准:平台不需要直接修改数据库即可交付新租户。 +- `SaasFeature`、`PermissionModule`、`BackendPermission`、`BackendMenu` 已分层。 +- `SaasOfferingVersion` 发布后由 Application 和 PostgreSQL guard 双重禁止修改。 +- `IFeatureAccessService` 统一处理租户、订阅、Feature、覆盖、额度与权限过滤。 +- PlatformBilling 与 TenantCommerce 使用独立订单、支付、回调和 Provider 配置。 +- 平台创建租户及 Owner 后,租户可完成购买、开通和 onboarding。 ### 9D:教师教学与考试 diff --git a/docs/migration-roadmap.md b/docs/migration-roadmap.md index 738b91c..4507993 100644 --- a/docs/migration-roadmap.md +++ b/docs/migration-roadmap.md @@ -23,6 +23,7 @@ - 后台底座:平台/租户 RBAC、菜单、审计、平台后台、租户后台、交易运营、Worker 任务模型。 - 学生体验:视频搜索/播放/进度、题目解析视频、签到、积分流水、内容导入异步化。 - AI 基础:已引入 SK 包到 Infrastructure,已固定 provider-neutral 对话存储方向;业务功能仍待实现。 +- SaaS 商城:可售卖 Feature、不可变套餐版本、独立 PlatformBilling、租户自助账务、业务额度、订阅周期 Worker 和 onboarding 已落地。 阶段归档: @@ -33,6 +34,7 @@ - [第五阶段:后台能力与 Worker 基座](migration/phase-5-backoffice-worker-operations.md) - [第七阶段:学生端体验与内容消费闭环](migration/phase-7-student-experience-and-content-consumption.md) - [第八阶段:AI 底座与教师端对话](migration/phase-8-ai-foundation.md) +- [第九阶段:SaaS 模块商城与租户交付闭环](migration/phase-9-saas-marketplace-and-onboarding.md) - [API 契约基线](migration/contracts/README.md) 后续产品化开发统一按 [SaaS 题库产品边界与后续接口路线](architecture/saas-product-and-api-roadmap.md) 执行。该文档定义三端边界、平台 SaaS 商城与租户学生商城的双交易域、套餐能力与 RBAC 分层,以及阶段 9A~9F。 @@ -62,7 +64,13 @@ - `commerce_reconciliation` 接真实 provider bill downloader。 - `tenant_domain_recheck` 周期调度和告警联动。 -### 4. 后台运营细化 +### 4. 教师教学闭环 + +- 作业、考试、班级发布、批阅和教学报告。 +- 学生断点续答、自动交卷、主观题复核和成绩分析。 +- 教学导出通过 Worker 和对象存储交付。 + +### 5. 后台运营细化 - 租户 secrets 通用后台管理。 - 租户监督规则、跟进报表和洞察报表。 @@ -70,7 +78,7 @@ - 操作审计覆盖率补齐。 - 发票、催缴、佣金联动调账。 -### 5. 旧路径兼容评估 +### 6. 旧路径兼容评估 只有前端明确依赖且重写成本高时,才增加薄兼容 Controller。兼容层不得恢复: diff --git a/docs/migration/phase-9-saas-marketplace-and-onboarding.md b/docs/migration/phase-9-saas-marketplace-and-onboarding.md new file mode 100644 index 0000000..c2fc56f --- /dev/null +++ b/docs/migration/phase-9-saas-marketplace-and-onboarding.md @@ -0,0 +1,50 @@ +# 第九阶段:SaaS 模块商城与租户交付闭环 + +## 目标 + +- 套餐决定租户购买的业务 Feature。 +- 角色决定员工可执行的 Permission,菜单只用于导航展示。 +- 平台 SaaS 收费与租户学生商城完全分离。 +- 平台创建租户和 Owner 后,租户可自助购买、开通并完成学生端初始化。 + +## 已完成 + +- `SaasFeature`、`PermissionModule`、`SaasOffering` 与不可变 `SaasOfferingVersion`。 +- 基础套餐、附加包、模块清单、额度定义、租户覆盖和原子用量记录。 +- `IFeatureAccessService` 统一读写状态、Feature、权限过滤和额度判断。 +- 题库、词汇、手册、视频、分数线和站点内容使用独立 `PermissionModule` 与后台权限点,单独购买、授权和生成菜单。 +- `/api/platform-admin/saas/**` 商品、订单、支付、订阅、发票、催缴和人工收款管理。 +- `/api/tenant-billing/**` 目录、幂等报价、幂等下单、支付、续费、变更、取消、用量和发票。 +- `/api/platform-billing/callbacks/{provider}` 独立平台收款回调。 +- 平台创建租户与 Owner 的事务化开户,以及 `/api/tenant-onboarding/status`。 +- runtime/UI bootstrap 返回有效 Feature、登录方式、权限、菜单、订阅和额度摘要。 +- 员工、学生、私有题、存储、导入、导出和短信额度已接入真实写路径;当前量由 Worker 定期按业务事实校准。 +- Worker 自动应用周期末降级、取消、Trial 到期、PastDue 和 Expired 状态转换,多 Worker 通过乐观并发保证幂等。 +- 旧 `ProductModule`、可变套餐、旧订阅和重复平台发票模型已删除。 + +## 数据库边界 + +- 当前仓库只有一个 greenfield `InitialSchema`。 +- 发布套餐版本、Feature 清单和额度清单不可修改。 +- 订阅基础项必须引用基础套餐;订阅项类型必须匹配 Offering 类型。 +- 订阅项可绑定产生它的 `PlatformBillingOrderItem` 价格快照。 +- 平台支付事件按租户、Provider 和 Provider Event ID 幂等。 +- 每个订阅最多一个 Active 基础套餐项和一个 Scheduled 基础套餐项,周期末原子切换。 +- 报价、订单和支付均保存租户内唯一幂等键,客户端不能提交最终价格。 + +## 验收 + +```bash +dotnet build TIKU-BACKEND.slnx --no-restore +dotnet test Tiku.UnitTests/Tiku.UnitTests.csproj --no-build +dotnet test Tiku.IntegrationTests/Tiku.IntegrationTests.csproj --no-build +dotnet ef migrations script --project Tiku.Infrastructure --startup-project Tiku.DbMigrator +dotnet ef migrations has-pending-model-changes --project Tiku.Infrastructure --startup-project Tiku.DbMigrator +git diff --check +``` + +真实 PostgreSQL 测试覆盖套餐发布不可变、报价与订单幂等、人工/微信/支付宝收款、签名与金额拒绝、重复回调、跨租户订单隔离、未购模块权限拒绝、额度写路径与事实校准、订阅周期转换和 onboarding ready。 + +## 后续 + +第十阶段实现教师作业、考试、批阅和教学报告。教师 AI 对话与题目反馈审核继续独立排期。