Files
www/membership/members/management/commands/sync_memberships.py
T
2026-10-23 12:19:40 -04:00

54 lines
2.4 KiB
Python
Executable File

import logging
import time
from datetime import timedelta
from django.core.management.base import BaseCommand, CommandError
from django.contrib.sessions.models import Session
from django.utils import timezone
from members.billing import sync_account
from members.models import Account, RateWindow, Registration, WebhookEvent
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = "Reconcile Stripe/SHKeeper payments and LDAP groups, including offline expiry."
def add_arguments(self, parser):
parser.add_argument("--loop", action="store_true")
parser.add_argument("--interval", type=int, default=60)
def handle(self, *args, **options):
if options["interval"] < 10:
raise CommandError("Use an interval of at least 10 seconds.")
while True:
failed = 0
# Expire EVERY account before any potentially slow Stripe history
# reads. This pass requires only the local DB and LDAP.
for account_id in Account.objects.values_list("pk", flat=True).iterator():
try:
sync_account(account_id, refresh_stripe=False)
except Exception as exc:
failed += 1
logger.error("Local expiry sync failed account=%s kind=%s", account_id, type(exc).__name__)
# A complete sweep is intentional: webhook loss must not prevent
# renewal recovery or revocation. Each account has its own lock.
for account_id in Account.objects.values_list("pk", flat=True).iterator():
try:
if not sync_account(account_id):
failed += 1
except Exception as exc:
failed += 1
logger.error("Membership sync failed account=%s kind=%s", account_id, type(exc).__name__)
now = timezone.now()
Session.objects.filter(expire_date__lte=now).delete()
Registration.objects.filter(expires_at__lte=now).delete()
RateWindow.objects.filter(expires_at__lte=now).delete()
WebhookEvent.objects.filter(processed_at__lt=now - timedelta(days=30)).delete()
if not options["loop"]:
if failed:
raise CommandError(f"{failed} account(s) failed to synchronize; inspect worker logs.")
return
time.sleep(options["interval"])