import uuid from django.db import models class Account(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) uid = models.CharField(max_length=48, unique=True) email = models.EmailField() ldap_dn = models.CharField(max_length=1024, blank=True) ldap_uuid = models.CharField(max_length=64, unique=True, null=True, blank=True) oidc_issuer = models.CharField(max_length=2048, blank=True, default="") oidc_sub = models.CharField(max_length=512, unique=True, null=True) membership_level = models.PositiveSmallIntegerField(default=0) stripe_customer = models.CharField(max_length=128, unique=True, null=True) checkout_id = models.CharField(max_length=128, blank=True) checkout_key = models.UUIDField(default=uuid.uuid4) last_billing_sync = models.DateTimeField(null=True) last_shkeeper_sync = models.DateTimeField(null=True) last_ldap_sync = models.DateTimeField(null=True) created_at = models.DateTimeField(auto_now_add=True) class Registration(models.Model): token_hash = models.CharField(max_length=64, primary_key=True) uid = models.CharField(max_length=48) email = models.EmailField() expires_at = models.DateTimeField(db_index=True) class RateWindow(models.Model): key = models.CharField(max_length=64, primary_key=True) count = models.PositiveIntegerField(default=0) expires_at = models.DateTimeField(db_index=True) class Subscription(models.Model): stripe_id = models.CharField(max_length=128, primary_key=True) account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name="subscriptions") tier = models.CharField(max_length=48, blank=True) status = models.CharField(max_length=32) paid_until = models.DateTimeField(null=True) cancel_at_period_end = models.BooleanField(default=False) class ManagedGroup(models.Model): """Retain old mappings so removing a tier from config also revokes its group.""" account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name="managed_groups") dn = models.CharField(max_length=1024) class Meta: constraints = [models.UniqueConstraint(fields=["account", "dn"], name="unique_account_group")] class CryptoInvoice(models.Model): """Immutable prepaid order; one canonical invoice can grant one month only.""" id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) account = models.ForeignKey(Account, on_delete=models.PROTECT) tier = models.CharField(max_length=48) usd_amount = models.DecimalField(max_digits=12, decimal_places=2) crypto = models.CharField(max_length=32) provider_id = models.CharField(max_length=128, blank=True) wallet = models.CharField(max_length=512, blank=True) crypto_amount = models.CharField(max_length=128, blank=True) # SHKeeper invoices do not expire. Keep incomplete orders available for late # payments; never silently discard a deposit address with money on the way. status = models.CharField(max_length=24, default="creating") credited_at = models.DateTimeField(null=True) access_start = models.DateTimeField(null=True) access_end = models.DateTimeField(null=True, db_index=True) last_checked_at = models.DateTimeField(null=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: constraints = [models.UniqueConstraint(fields=["account"], condition=models.Q(credited_at__isnull=True), name="one_uncredited_crypto_invoice")] class WebhookEvent(models.Model): stripe_id = models.CharField(max_length=128, primary_key=True) account = models.ForeignKey(Account, on_delete=models.CASCADE, null=True) received_at = models.DateTimeField(auto_now_add=True) processed_at = models.DateTimeField(null=True)