Files
flatrender/services/identity/FlatRender.IdentitySvc/Infrastructure/Data/IdentityDbContext.cs
T
soroush.asadi 675b60d858
Build backend images / build content-svc (push) Failing after 1m2s
Build backend images / build file-svc (push) Failing after 3m11s
Build backend images / build gateway (push) Failing after 5m39s
Build backend images / build identity-svc (push) Failing after 38s
Build backend images / build notification-svc (push) Failing after 2m0s
Build backend images / build render-svc (push) Failing after 58s
Build backend images / build studio-svc (push) Failing after 58s
feat(auth+admin): Sign in with Google (OAuth) + Integrations config panel
Backend (identity-svc):
- oauth_config table (mig 22) + OAuthConfig entity
- OAuthService: admin config CRUD + Google authorization-code flow (build consent
  URL, exchange code, fetch userinfo, find/create RegisterMode.Google user, issue
  session via AuthService.IssueOAuthSessionAsync)
- AuthController: GET /v1/auth/google/{start,callback} (public); tokens handed to
  frontend via URL fragment
- AdminController: GET/PUT /v1/admin/oauth/{provider} (admin, secret masked)

Frontend:
- "ورود با گوگل" button on /auth → identity start endpoint
- /auth/callback reads fragment tokens → /api/auth/oauth-session sets httpOnly cookies
- /admin/integrations: Google client_id/secret/redirect_uri + enable, with setup guide
- nav + fa/en labels

Client ID/Secret are configured entirely in the admin panel — no redeploy needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 00:08:21 +03:30

618 lines
35 KiB
C#

using FlatRender.IdentitySvc.Domain.Entities;
using FlatRender.IdentitySvc.Domain.Enums;
using Microsoft.EntityFrameworkCore;
namespace FlatRender.IdentitySvc.Infrastructure.Data;
public class IdentityDbContext(DbContextOptions<IdentityDbContext> options) : DbContext(options)
{
// Tenants
public DbSet<Tenant> Tenants => Set<Tenant>();
public DbSet<TenantBranding> TenantBrandings => Set<TenantBranding>();
public DbSet<TenantApiKey> TenantApiKeys => Set<TenantApiKey>();
public DbSet<TenantWebhook> TenantWebhooks => Set<TenantWebhook>();
public DbSet<TenantWebhookDelivery> TenantWebhookDeliveries => Set<TenantWebhookDelivery>();
public DbSet<TenantUsageDaily> TenantUsageDailies => Set<TenantUsageDaily>();
// Users
public DbSet<User> Users => Set<User>();
public DbSet<UserSession> UserSessions => Set<UserSession>();
public DbSet<ConfirmationToken> ConfirmationTokens => Set<ConfirmationToken>();
public DbSet<PushSubscription> PushSubscriptions => Set<PushSubscription>();
public DbSet<MfaFactor> MfaFactors => Set<MfaFactor>();
// Billing
public DbSet<Plan> Plans => Set<Plan>();
public DbSet<UserPlan> UserPlans => Set<UserPlan>();
public DbSet<Payment> Payments => Set<Payment>();
public DbSet<Discount> Discounts => Set<Discount>();
public DbSet<UsedDiscount> UsedDiscounts => Set<UsedDiscount>();
// CRM
public DbSet<UserCrm> UserCrms => Set<UserCrm>();
// OAuth providers
public DbSet<OAuthConfig> OAuthConfigs => Set<OAuthConfig>();
// Gamification
public DbSet<Quest> Quests => Set<Quest>();
public DbSet<UserQuestProgress> UserQuestProgresses => Set<UserQuestProgress>();
public DbSet<Gift> Gifts => Set<Gift>();
public DbSet<EarnedGift> EarnedGifts => Set<EarnedGift>();
public DbSet<Avatar> Avatars => Set<Avatar>();
protected override void OnModelCreating(ModelBuilder mb)
{
mb.HasDefaultSchema("identity");
// Native PostgreSQL enums are registered on the EF provider via npgsql.MapEnum<T>()
// in Program.cs (the EF Core 9+ approach), which covers both the model and the
// runtime ADO type mapping. No HasPostgresEnum<T>() calls are needed here.
ConfigureTenants(mb);
ConfigureUsers(mb);
ConfigureBilling(mb);
ConfigureGamification(mb);
mb.Entity<UserCrm>(e =>
{
e.ToTable("user_crm");
e.HasKey(x => x.UserId);
});
mb.Entity<OAuthConfig>(e =>
{
e.ToTable("oauth_config");
e.HasKey(x => x.Provider);
});
}
private static void ConfigureTenants(ModelBuilder mb)
{
mb.Entity<Tenant>(e =>
{
e.ToTable("tenants");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.Slug).HasColumnName("slug").HasColumnType("citext").IsRequired();
e.Property(x => x.Name).HasColumnName("name").IsRequired();
e.Property(x => x.Kind).HasColumnName("kind");
e.Property(x => x.Status).HasColumnName("status");
e.Property(x => x.CustomDomain).HasColumnName("custom_domain").HasColumnType("citext");
e.Property(x => x.DomainVerified).HasColumnName("domain_verified");
e.Property(x => x.AllowedOrigins).HasColumnName("allowed_origins");
e.Property(x => x.ContactName).HasColumnName("contact_name");
e.Property(x => x.ContactEmail).HasColumnName("contact_email").HasColumnType("citext");
e.Property(x => x.ContactPhone).HasColumnName("contact_phone");
e.Property(x => x.BillingEmail).HasColumnName("billing_email").HasColumnType("citext");
e.Property(x => x.MaxUsers).HasColumnName("max_users");
e.Property(x => x.MaxStorageGb).HasColumnName("max_storage_gb");
e.Property(x => x.MonthlyRenderQty).HasColumnName("monthly_render_qty");
e.Property(x => x.MonthlyRenderSec).HasColumnName("monthly_render_sec");
e.Property(x => x.TrialEndsAt).HasColumnName("trial_ends_at");
e.Property(x => x.SuspendedAt).HasColumnName("suspended_at");
e.Property(x => x.SuspensionReason).HasColumnName("suspension_reason");
e.Property(x => x.Metadata).HasColumnName("metadata").HasColumnType("jsonb");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
e.Property(x => x.DeletedAt).HasColumnName("deleted_at");
e.HasIndex(x => x.Slug).IsUnique();
e.HasOne(x => x.Branding).WithOne(b => b.Tenant).HasForeignKey<TenantBranding>(b => b.TenantId);
e.HasMany(x => x.ApiKeys).WithOne(k => k.Tenant).HasForeignKey(k => k.TenantId);
e.HasMany(x => x.Webhooks).WithOne(w => w.Tenant).HasForeignKey(w => w.TenantId);
});
mb.Entity<TenantBranding>(e =>
{
e.ToTable("tenant_branding");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.DisplayName).HasColumnName("display_name");
e.Property(x => x.LogoUrl).HasColumnName("logo_url");
e.Property(x => x.LogoDarkUrl).HasColumnName("logo_dark_url");
e.Property(x => x.FaviconUrl).HasColumnName("favicon_url");
e.Property(x => x.OgImageUrl).HasColumnName("og_image_url");
e.Property(x => x.PrimaryColor).HasColumnName("primary_color");
e.Property(x => x.SecondaryColor).HasColumnName("secondary_color");
e.Property(x => x.AccentColor).HasColumnName("accent_color");
e.Property(x => x.BackgroundColor).HasColumnName("background_color");
e.Property(x => x.FontFamily).HasColumnName("font_family");
e.Property(x => x.EmailFromName).HasColumnName("email_from_name");
e.Property(x => x.EmailFromAddress).HasColumnName("email_from_address");
e.Property(x => x.EmailReplyTo).HasColumnName("email_reply_to");
e.Property(x => x.EmailFooterHtml).HasColumnName("email_footer_html");
e.Property(x => x.SupportUrl).HasColumnName("support_url");
e.Property(x => x.TermsUrl).HasColumnName("terms_url");
e.Property(x => x.PrivacyUrl).HasColumnName("privacy_url");
e.Property(x => x.EmbedEnabled).HasColumnName("embed_enabled");
e.Property(x => x.EmbedAllowedHosts).HasColumnName("embed_allowed_hosts");
e.Property(x => x.WatermarkText).HasColumnName("watermark_text");
e.Property(x => x.WatermarkImageUrl).HasColumnName("watermark_image_url");
e.Property(x => x.WatermarkEnabled).HasColumnName("watermark_enabled");
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
});
mb.Entity<TenantApiKey>(e =>
{
e.ToTable("tenant_api_keys");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.CreatedByUserId).HasColumnName("created_by_user_id");
e.Property(x => x.Name).HasColumnName("name");
e.Property(x => x.Environment).HasColumnName("environment");
e.Property(x => x.KeyPrefix).HasColumnName("key_prefix");
e.Property(x => x.KeyHash).HasColumnName("key_hash");
e.Property(x => x.Last4).HasColumnName("last4");
e.Property(x => x.Scopes).HasColumnName("scopes");
e.Property(x => x.AllowedIps).HasColumnName("allowed_ips");
e.Property(x => x.RateLimitRpm).HasColumnName("rate_limit_rpm");
e.Property(x => x.IsActive).HasColumnName("is_active");
e.Property(x => x.ExpiresAt).HasColumnName("expires_at");
e.Property(x => x.LastUsedAt).HasColumnName("last_used_at");
e.Property(x => x.UsageCount).HasColumnName("usage_count");
e.Property(x => x.RevokeReason).HasColumnName("revoke_reason");
e.Property(x => x.RevokedAt).HasColumnName("revoked_at");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
});
mb.Entity<TenantWebhook>(e =>
{
e.ToTable("tenant_webhooks");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.Name).HasColumnName("name");
e.Property(x => x.Url).HasColumnName("url");
e.Property(x => x.Events).HasColumnName("events");
e.Property(x => x.SecretHash).HasColumnName("secret_hash");
e.Property(x => x.IsActive).HasColumnName("is_active");
e.Property(x => x.LastTriggeredAt).HasColumnName("last_triggered_at");
e.Property(x => x.LastStatusCode).HasColumnName("last_status_code");
e.Property(x => x.ConsecutiveFailures).HasColumnName("consecutive_failures");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
e.HasMany(x => x.Deliveries).WithOne(d => d.Webhook).HasForeignKey(d => d.WebhookId);
});
mb.Entity<TenantWebhookDelivery>(e =>
{
e.ToTable("tenant_webhook_deliveries");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.WebhookId).HasColumnName("webhook_id");
e.Property(x => x.EventType).HasColumnName("event_type");
e.Property(x => x.RequestUrl).HasColumnName("request_url");
e.Property(x => x.RequestBody).HasColumnName("request_body");
e.Property(x => x.ResponseStatus).HasColumnName("response_status");
e.Property(x => x.ResponseBody).HasColumnName("response_body");
e.Property(x => x.DurationMs).HasColumnName("duration_ms");
e.Property(x => x.Attempt).HasColumnName("attempt");
e.Property(x => x.Succeeded).HasColumnName("succeeded");
e.Property(x => x.ErrorMessage).HasColumnName("error_message");
e.Property(x => x.DeliveredAt).HasColumnName("delivered_at");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
});
mb.Entity<TenantUsageDaily>(e =>
{
e.ToTable("tenant_usage_daily");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.UsageDate).HasColumnName("usage_date");
e.Property(x => x.RendersCompleted).HasColumnName("renders_completed");
e.Property(x => x.RenderSeconds).HasColumnName("render_seconds");
e.Property(x => x.StorageBytes).HasColumnName("storage_bytes");
e.Property(x => x.ApiCalls).HasColumnName("api_calls");
e.Property(x => x.ActiveUsers).HasColumnName("active_users");
e.Property(x => x.AmountBilledMinor).HasColumnName("amount_billed_minor");
e.Property(x => x.BillingCurrency).HasColumnName("billing_currency");
e.Property(x => x.BillingStatus).HasColumnName("billing_status");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.HasIndex(x => new { x.TenantId, x.UsageDate }).IsUnique();
});
}
private static void ConfigureUsers(ModelBuilder mb)
{
mb.Entity<User>(e =>
{
e.ToTable("users");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.Email).HasColumnName("email").HasColumnType("citext");
e.Property(x => x.EmailVerified).HasColumnName("email_verified");
e.Property(x => x.EmailVerifiedAt).HasColumnName("email_verified_at");
e.Property(x => x.PhoneNumber).HasColumnName("phone_number");
e.Property(x => x.PhoneCountryCode).HasColumnName("phone_country_code");
e.Property(x => x.PhoneVerified).HasColumnName("phone_verified");
e.Property(x => x.PhoneVerifiedAt).HasColumnName("phone_verified_at");
e.Property(x => x.PasswordHash).HasColumnName("password_hash");
e.Property(x => x.PasswordSetAt).HasColumnName("password_set_at");
e.Property(x => x.LastPasswordResetDate).HasColumnName("last_password_reset_date");
e.Property(x => x.RegisterMode).HasColumnName("register_mode");
e.Property(x => x.ExternalProvider).HasColumnName("external_provider");
e.Property(x => x.ExternalProviderId).HasColumnName("external_provider_id");
e.Property(x => x.FullName).HasColumnName("full_name");
e.Property(x => x.AvatarUrl).HasColumnName("avatar_url");
e.Property(x => x.BirthDate).HasColumnName("birth_date");
e.Property(x => x.Gender).HasColumnName("gender");
e.Property(x => x.NationalCode).HasColumnName("national_code");
e.Property(x => x.CountryCode).HasColumnName("country_code");
e.Property(x => x.CompanyName).HasColumnName("company_name");
e.Property(x => x.WebsiteName).HasColumnName("website_name");
e.Property(x => x.Slogan).HasColumnName("slogan");
e.Property(x => x.AboutMe).HasColumnName("about_me");
e.Property(x => x.MethodOfIntroduction).HasColumnName("method_of_introduction");
e.Property(x => x.BalanceMinor).HasColumnName("balance_minor");
e.Property(x => x.AffiliateBalanceMinor).HasColumnName("affiliate_balance_minor");
e.Property(x => x.AffiliateOwnerId).HasColumnName("affiliate_owner_id");
e.Property(x => x.ProfitPercentage).HasColumnName("profit_percentage");
e.Property(x => x.LoyaltyScore).HasColumnName("loyalty_score");
e.Property(x => x.PurplePoint).HasColumnName("purple_point");
e.Property(x => x.DailyRemainRenderCount).HasColumnName("daily_remain_render_count");
e.Property(x => x.MaxDailyRenderCount).HasColumnName("max_daily_render_count");
e.Property(x => x.ParallelRenderingCeiling).HasColumnName("parallel_rendering_ceiling");
e.Property(x => x.UserDailyFreeChargeSec).HasColumnName("user_daily_free_charge_sec");
e.Property(x => x.DailyFreeChargeResetDate).HasColumnName("daily_free_charge_reset_date");
e.Property(x => x.MaxPreviewDurationSec).HasColumnName("max_preview_duration_sec");
e.Property(x => x.ForceRenderQueue).HasColumnName("force_render_queue");
e.Property(x => x.RemoveWatermarkService).HasColumnName("remove_watermark_service");
e.Property(x => x.TelegramId).HasColumnName("telegram_id");
e.Property(x => x.TelegramTellMe).HasColumnName("telegram_tell_me");
e.Property(x => x.EmailTellMe).HasColumnName("email_tell_me");
e.Property(x => x.SmsTellMe).HasColumnName("sms_tell_me");
e.Property(x => x.PushTellMe).HasColumnName("push_tell_me");
e.Property(x => x.StorageEndpoint).HasColumnName("storage_endpoint");
e.Property(x => x.UsedStorageBytes).HasColumnName("used_storage_bytes");
e.Property(x => x.IsAdmin).HasColumnName("is_admin");
e.Property(x => x.IsTenantAdmin).HasColumnName("is_tenant_admin");
e.Property(x => x.BanAccount).HasColumnName("ban_account");
e.Property(x => x.BanReason).HasColumnName("ban_reason");
e.Property(x => x.UnblockDate).HasColumnName("unblock_date");
e.Property(x => x.LastActiveDate).HasColumnName("last_active_date");
e.Property(x => x.LastLoginAt).HasColumnName("last_login_at");
e.Property(x => x.LastLoginIp).HasColumnName("last_login_ip");
e.Property(x => x.RegisteredWithMobileApp).HasColumnName("registered_with_mobile_app");
e.Property(x => x.RegisterDate).HasColumnName("register_date");
e.Property(x => x.Metadata).HasColumnName("metadata").HasColumnType("jsonb");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
e.Property(x => x.DeletedAt).HasColumnName("deleted_at");
e.HasIndex(x => new { x.TenantId, x.Email }).IsUnique();
e.HasMany(x => x.Sessions).WithOne(s => s.User).HasForeignKey(s => s.UserId).OnDelete(DeleteBehavior.Cascade);
e.HasMany(x => x.MfaFactors).WithOne(m => m.User).HasForeignKey(m => m.UserId).OnDelete(DeleteBehavior.Cascade);
e.HasMany(x => x.PushSubscriptions).WithOne(p => p.User).HasForeignKey(p => p.UserId).OnDelete(DeleteBehavior.Cascade);
});
mb.Entity<UserSession>(e =>
{
e.ToTable("user_sessions");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.UserId).HasColumnName("user_id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.RefreshTokenHash).HasColumnName("refresh_token_hash");
e.Property(x => x.DeviceId).HasColumnName("device_id");
e.Property(x => x.DeviceName).HasColumnName("device_name");
e.Property(x => x.UserAgent).HasColumnName("user_agent");
e.Property(x => x.IpAddress).HasColumnName("ip_address");
e.Property(x => x.IssuedAt).HasColumnName("issued_at");
e.Property(x => x.ExpiresAt).HasColumnName("expires_at");
e.Property(x => x.RevokedAt).HasColumnName("revoked_at");
e.Property(x => x.LastUsedAt).HasColumnName("last_used_at");
e.HasIndex(x => x.RefreshTokenHash).IsUnique();
});
mb.Entity<ConfirmationToken>(e =>
{
e.ToTable("confirmation_tokens");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.UserId).HasColumnName("user_id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.Purpose).HasColumnName("purpose");
e.Property(x => x.Identifier).HasColumnName("identifier").HasColumnType("citext");
e.Property(x => x.NextIdentifier).HasColumnName("next_identifier").HasColumnType("citext");
e.Property(x => x.TokenHash).HasColumnName("token_hash");
e.Property(x => x.Code).HasColumnName("code");
e.Property(x => x.IsConsumed).HasColumnName("is_consumed");
e.Property(x => x.ConsumedAt).HasColumnName("consumed_at");
e.Property(x => x.TryCount).HasColumnName("try_count");
e.Property(x => x.MaxTries).HasColumnName("max_tries");
e.Property(x => x.RequestIp).HasColumnName("request_ip");
e.Property(x => x.ExpiresAt).HasColumnName("expires_at");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
});
mb.Entity<PushSubscription>(e =>
{
e.ToTable("push_subscriptions");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.UserId).HasColumnName("user_id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.Endpoint).HasColumnName("endpoint");
e.Property(x => x.P256dhKey).HasColumnName("p256dh_key");
e.Property(x => x.AuthKey).HasColumnName("auth_key");
e.Property(x => x.UserAgent).HasColumnName("user_agent");
e.Property(x => x.IsActive).HasColumnName("is_active");
e.Property(x => x.LastUsedAt).HasColumnName("last_used_at");
e.Property(x => x.FailureCount).HasColumnName("failure_count");
e.Property(x => x.LastFailureAt).HasColumnName("last_failure_at");
e.Property(x => x.LastFailureStatus).HasColumnName("last_failure_status");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.HasIndex(x => new { x.UserId, x.Endpoint }).IsUnique();
});
mb.Entity<MfaFactor>(e =>
{
e.ToTable("mfa_factors");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.UserId).HasColumnName("user_id");
e.Property(x => x.FactorType).HasColumnName("factor_type");
e.Property(x => x.SecretEncrypted).HasColumnName("secret_encrypted");
e.Property(x => x.IsVerified).HasColumnName("is_verified");
e.Property(x => x.IsPrimary).HasColumnName("is_primary");
e.Property(x => x.Label).HasColumnName("label");
e.Property(x => x.LastUsedAt).HasColumnName("last_used_at");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
});
}
private static void ConfigureBilling(ModelBuilder mb)
{
mb.Entity<Plan>(e =>
{
e.ToTable("plans");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.Scope).HasColumnName("scope");
e.Property(x => x.Code).HasColumnName("code");
e.Property(x => x.Name).HasColumnName("name");
e.Property(x => x.Description).HasColumnName("description");
e.Property(x => x.PriceMinor).HasColumnName("price_minor");
e.Property(x => x.BeforePriceMinor).HasColumnName("before_price_minor");
e.Property(x => x.Currency).HasColumnName("currency");
e.Property(x => x.DiscountPercentage).HasColumnName("discount_percentage");
e.Property(x => x.BillingPeriod).HasColumnName("billing_period");
e.Property(x => x.MonthsDuration).HasColumnName("months_duration");
e.Property(x => x.SecondsCharge).HasColumnName("seconds_charge");
e.Property(x => x.MonthlyRendersQuota).HasColumnName("monthly_renders_quota");
e.Property(x => x.StorageGb).HasColumnName("storage_gb");
e.Property(x => x.ParallelRenders).HasColumnName("parallel_renders");
e.Property(x => x.MaxResolution).HasColumnName("max_resolution");
e.Property(x => x.MinVideoLengthSec).HasColumnName("min_video_length_sec");
e.Property(x => x.RenderSpeedFactor).HasColumnName("render_speed_factor");
e.Property(x => x.Sort).HasColumnName("sort");
e.Property(x => x.Icon).HasColumnName("icon");
e.Property(x => x.Cover).HasColumnName("cover");
e.Property(x => x.Color).HasColumnName("color");
e.Property(x => x.IsFeatured).HasColumnName("is_featured");
e.Property(x => x.Features).HasColumnName("features").HasColumnType("jsonb");
e.Property(x => x.IsActive).HasColumnName("is_active");
e.Property(x => x.AvailableFrom).HasColumnName("available_from");
e.Property(x => x.AvailableUntil).HasColumnName("available_until");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
e.Property(x => x.DeletedAt).HasColumnName("deleted_at");
});
mb.Entity<UserPlan>(e =>
{
e.ToTable("user_plans");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.UserId).HasColumnName("user_id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.PlanId).HasColumnName("plan_id");
e.Property(x => x.PlanCode).HasColumnName("plan_code");
e.Property(x => x.PlanName).HasColumnName("plan_name");
e.Property(x => x.PriceMinorPaid).HasColumnName("price_minor_paid");
e.Property(x => x.Currency).HasColumnName("currency");
e.Property(x => x.InitialSecondsCharge).HasColumnName("initial_seconds_charge");
e.Property(x => x.RemainChargeSec).HasColumnName("remain_charge_sec");
e.Property(x => x.AddedChargeFromPastPlan).HasColumnName("added_charge_from_past_plan");
e.Property(x => x.MonthlyRendersUsed).HasColumnName("monthly_renders_used");
e.Property(x => x.MonthlyRendersResetAt).HasColumnName("monthly_renders_reset_at");
e.Property(x => x.RegisterDate).HasColumnName("register_date");
e.Property(x => x.StartsAt).HasColumnName("starts_at");
e.Property(x => x.ExpiresAt).HasColumnName("expires_at");
e.Property(x => x.CancelledAt).HasColumnName("cancelled_at");
e.Property(x => x.CancelReason).HasColumnName("cancel_reason");
e.Property(x => x.AutoRenew).HasColumnName("auto_renew");
e.Property(x => x.PaymentId).HasColumnName("payment_id");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
e.HasOne(x => x.User).WithMany(u => u.Plans).HasForeignKey(x => x.UserId);
e.HasOne(x => x.Plan).WithMany(p => p.UserPlans).HasForeignKey(x => x.PlanId);
});
mb.Entity<Payment>(e =>
{
e.ToTable("payments");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.UserId).HasColumnName("user_id");
e.Property(x => x.Gateway).HasColumnName("gateway");
e.Property(x => x.Status).HasColumnName("status");
e.Property(x => x.Action).HasColumnName("action");
e.Property(x => x.AmountMinor).HasColumnName("amount_minor");
e.Property(x => x.Currency).HasColumnName("currency");
e.Property(x => x.BalanceReducerMinor).HasColumnName("balance_reducer_minor");
e.Property(x => x.DiscountValueMinor).HasColumnName("discount_value_minor");
e.Property(x => x.GatewayToken).HasColumnName("gateway_token");
e.Property(x => x.GatewayOrderId).HasColumnName("gateway_order_id");
e.Property(x => x.GatewayTrackId).HasColumnName("gateway_track_id");
e.Property(x => x.GatewayResponse).HasColumnName("gateway_response").HasColumnType("jsonb");
e.Property(x => x.CardLast4).HasColumnName("card_last4");
e.Property(x => x.CardHash).HasColumnName("card_hash");
e.Property(x => x.Title).HasColumnName("title");
e.Property(x => x.Description).HasColumnName("description");
e.Property(x => x.OwnerUserId).HasColumnName("owner_user_id");
e.Property(x => x.AffiliateProfitMinor).HasColumnName("affiliate_profit_minor");
e.Property(x => x.UsedDiscountId).HasColumnName("used_discount_id");
e.Property(x => x.PlanId).HasColumnName("plan_id");
e.Property(x => x.RenderJobId).HasColumnName("render_job_id");
e.Property(x => x.UserProjectId).HasColumnName("user_project_id");
e.Property(x => x.ConfirmedAt).HasColumnName("confirmed_at");
e.Property(x => x.FailedAt).HasColumnName("failed_at");
e.Property(x => x.FailureReason).HasColumnName("failure_reason");
e.Property(x => x.RefundedAt).HasColumnName("refunded_at");
e.Property(x => x.RefundAmountMinor).HasColumnName("refund_amount_minor");
e.Property(x => x.RefundReason).HasColumnName("refund_reason");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
e.HasOne(x => x.User).WithMany(u => u.Payments).HasForeignKey(x => x.UserId);
});
mb.Entity<Discount>(e =>
{
e.ToTable("discounts");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.Name).HasColumnName("name");
e.Property(x => x.Code).HasColumnName("code").HasColumnType("citext");
e.Property(x => x.Kind).HasColumnName("kind");
e.Property(x => x.Value).HasColumnName("value");
e.Property(x => x.OwnerUserId).HasColumnName("owner_user_id");
e.Property(x => x.OwnerProfitPercentage).HasColumnName("owner_profit_percentage");
e.Property(x => x.OnlyOwner).HasColumnName("only_owner");
e.Property(x => x.MaxUseCount).HasColumnName("max_use_count");
e.Property(x => x.UsedCount).HasColumnName("used_count");
e.Property(x => x.MinPurchaseMinor).HasColumnName("min_purchase_minor");
e.Property(x => x.AppliesToPlanIds).HasColumnName("applies_to_plan_ids");
e.Property(x => x.StartsAt).HasColumnName("starts_at");
e.Property(x => x.ExpiresAt).HasColumnName("expires_at");
e.Property(x => x.IsActive).HasColumnName("is_active");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
e.HasIndex(x => new { x.TenantId, x.Code }).IsUnique();
});
mb.Entity<UsedDiscount>(e =>
{
e.ToTable("used_discounts");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.DiscountId).HasColumnName("discount_id");
e.Property(x => x.UserId).HasColumnName("user_id");
e.Property(x => x.PaymentId).HasColumnName("payment_id");
e.Property(x => x.Code).HasColumnName("code").HasColumnType("citext");
e.Property(x => x.AmountDiscountedMinor).HasColumnName("amount_discounted_minor");
e.Property(x => x.UseDate).HasColumnName("use_date");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
});
}
private static void ConfigureGamification(ModelBuilder mb)
{
mb.Entity<Quest>(e =>
{
e.ToTable("quests");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.Title).HasColumnName("title");
e.Property(x => x.Challenge).HasColumnName("challenge");
e.Property(x => x.Why).HasColumnName("why");
e.Property(x => x.Hint).HasColumnName("hint");
e.Property(x => x.Aphorism).HasColumnName("aphorism");
e.Property(x => x.Icon).HasColumnName("icon");
e.Property(x => x.QuestType).HasColumnName("quest_type");
e.Property(x => x.TargetEvent).HasColumnName("target_event");
e.Property(x => x.TargetCount).HasColumnName("target_count");
e.Property(x => x.Metadata).HasColumnName("metadata").HasColumnType("jsonb");
e.Property(x => x.PrizeType).HasColumnName("prize_type");
e.Property(x => x.PrizeAmount).HasColumnName("prize_amount");
e.Property(x => x.LevelLimit).HasColumnName("level_limit");
e.Property(x => x.StartUrl).HasColumnName("start_url");
e.Property(x => x.PostActionName).HasColumnName("post_action_name");
e.Property(x => x.OrderValue).HasColumnName("order_value");
e.Property(x => x.StartsAt).HasColumnName("starts_at");
e.Property(x => x.ExpiresAt).HasColumnName("expires_at");
e.Property(x => x.IsActive).HasColumnName("is_active");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
e.HasMany(x => x.Progress).WithOne(p => p.Quest).HasForeignKey(p => p.QuestId);
});
mb.Entity<UserQuestProgress>(e =>
{
e.ToTable("user_quest_progress");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.UserId).HasColumnName("user_id");
e.Property(x => x.QuestId).HasColumnName("quest_id");
e.Property(x => x.CurrentCount).HasColumnName("current_count");
e.Property(x => x.TextValue).HasColumnName("text_value");
e.Property(x => x.IsCompleted).HasColumnName("is_completed");
e.Property(x => x.CompletedAt).HasColumnName("completed_at");
e.Property(x => x.PrizeClaimed).HasColumnName("prize_claimed");
e.Property(x => x.PrizeClaimedAt).HasColumnName("prize_claimed_at");
e.Property(x => x.PeriodStart).HasColumnName("period_start");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.Property(x => x.UpdatedAt).HasColumnName("updated_at");
e.HasIndex(x => new { x.UserId, x.QuestId, x.PeriodStart }).IsUnique();
});
mb.Entity<Gift>(e =>
{
e.ToTable("gifts");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.TenantId).HasColumnName("tenant_id");
e.Property(x => x.Name).HasColumnName("name");
e.Property(x => x.Description).HasColumnName("description");
e.Property(x => x.Icon).HasColumnName("icon");
e.Property(x => x.GiftType).HasColumnName("gift_type");
e.Property(x => x.PrizeType).HasColumnName("prize_type");
e.Property(x => x.Value).HasColumnName("value");
e.Property(x => x.Unit).HasColumnName("unit");
e.Property(x => x.AssignedByUserId).HasColumnName("assigned_by_user_id");
e.Property(x => x.IsActive).HasColumnName("is_active");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.HasMany(x => x.EarnedGifts).WithOne(eg => eg.Gift).HasForeignKey(eg => eg.GiftId);
});
mb.Entity<EarnedGift>(e =>
{
e.ToTable("earned_gifts");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.UserId).HasColumnName("user_id");
e.Property(x => x.GiftId).HasColumnName("gift_id");
e.Property(x => x.NotificationId).HasColumnName("notification_id");
e.Property(x => x.Source).HasColumnName("source");
e.Property(x => x.SourceRef).HasColumnName("source_ref");
e.Property(x => x.EarnedAt).HasColumnName("earned_at");
e.Property(x => x.ExpiresAt).HasColumnName("expires_at");
e.Property(x => x.IsUsed).HasColumnName("is_used");
e.Property(x => x.UsedAt).HasColumnName("used_at");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
});
mb.Entity<Avatar>(e =>
{
e.ToTable("avatars");
e.HasKey(x => x.Id);
e.Property(x => x.Id).HasColumnName("id");
e.Property(x => x.Code).HasColumnName("code");
e.Property(x => x.Url).HasColumnName("url");
e.Property(x => x.Description).HasColumnName("description");
e.Property(x => x.Sort).HasColumnName("sort");
e.Property(x => x.IsActive).HasColumnName("is_active");
e.Property(x => x.CreatedAt).HasColumnName("created_at");
e.HasIndex(x => x.Code).IsUnique();
});
}
}