60 lines
2.4 KiB
Python
60 lines
2.4 KiB
Python
from repositories.tenant_repository import tenant_repository
|
|
from repositories.seat_repository import seat_repository
|
|
|
|
# Both trial and active tenants may use the dashboard and the Apps Script
|
|
# export plugin — only 'inactive' (cancelled, or trial expired with no
|
|
# card on file) blocks access.
|
|
VALID_STATUSES = ['active', 'trial']
|
|
|
|
|
|
def validate_email(email):
|
|
"""
|
|
Validates a PM's access to Bettersight via two checks: domain
|
|
registration and seat count.
|
|
|
|
Note: CLAUDE.md's earlier §9 draft described a third, sheet_id-binding
|
|
layer, but §15 explicitly removes it for the standalone Apps Script
|
|
model ("validation is email-only... remove the sheet_id binding layer
|
|
— it is redundant and would break the standalone model"). That later,
|
|
more specific instruction is what's implemented here.
|
|
|
|
Data flow:
|
|
email →
|
|
domain extraction → tenant lookup by domain (Layer 1) →
|
|
status check (must be trial or active) →
|
|
existing active seat → refresh last_accessed, allow →
|
|
no existing seat → seat count vs tenant.max_seats →
|
|
under limit: register new seat, allow / at limit: reject →
|
|
{ valid: bool, tier: str|None, reason: str|None }
|
|
"""
|
|
if not email or '@' not in email:
|
|
return {'valid': False, 'reason': 'Invalid email address'}
|
|
|
|
domain = email.split('@', 1)[1].lower()
|
|
|
|
# Layer 1: domain must belong to a known tenant in good standing
|
|
tenant = tenant_repository.get_by_domain(domain)
|
|
if not tenant:
|
|
return {'valid': False, 'reason': 'Domain not registered'}
|
|
if tenant.get('status') not in VALID_STATUSES:
|
|
return {'valid': False, 'reason': 'Subscription inactive'}
|
|
|
|
# Layer 2: existing seat is a fast-path allow with a freshness bump;
|
|
# a new seat must fit within the tenant's purchased seat count
|
|
existing_seat = seat_repository.get_active(tenant['id'], email)
|
|
if existing_seat:
|
|
seat_repository.update_last_accessed(existing_seat['id'])
|
|
return {'valid': True, 'tier': tenant['tier']}
|
|
|
|
active_count = seat_repository.count_active(tenant['id'])
|
|
if active_count >= tenant.get('max_seats', 0):
|
|
return {'valid': False, 'reason': 'Seat limit reached'}
|
|
|
|
seat_repository.create({
|
|
'tenant_id': tenant['id'],
|
|
'email': email,
|
|
'active': True,
|
|
'added_by': 'self',
|
|
})
|
|
return {'valid': True, 'tier': tenant['tier']}
|