import uuid from repositories.pocketbase_client import pb_client from repositories.repo_config import USE_MOCK_REPOSITORIES COLLECTION = 'alerts' class AlertRepository: """Data access for the `alerts` collection.""" def create(self, data): return pb_client.create(COLLECTION, data) def get_by_id(self, alert_id): return pb_client.get_one(COLLECTION, alert_id) def list_unread(self, tenant_id): return pb_client.list( COLLECTION, filter_str=f'tenant_id = "{tenant_id}" && read = false', sort='-created', per_page=200 ) def list_undelivered_email(self, tenant_id): """ Alerts not yet included in a digest email — independent of the dashboard `read` flag, since a PM reading something on the dashboard first shouldn't make it disappear from that day's digest (CLAUDE.md §6 alerts schema: delivered_email tracks digest inclusion, read tracks dashboard dismissal — separate concerns). """ return pb_client.list( COLLECTION, filter_str=f'tenant_id = "{tenant_id}" && delivered_email = false', sort='-created', per_page=200 ) def list_recent(self, tenant_id, limit=50): return pb_client.list(COLLECTION, filter_str=f'tenant_id = "{tenant_id}"', sort='-created', per_page=limit) def mark_read(self, alert_id): return pb_client.update(COLLECTION, alert_id, {'read': True}) def mark_email_delivered(self, alert_id): return pb_client.update(COLLECTION, alert_id, {'delivered_email': True}) def list_read_older_than(self, cutoff_iso): """Used by the weekly data-cleanup job — CLAUDE.md §22 DATA_RETENTION (90 days, read alerts only).""" return pb_client.list(COLLECTION, filter_str=f'read = true && created < "{cutoff_iso}"', per_page=500) def delete(self, alert_id): return pb_client.delete(COLLECTION, alert_id) class MockAlertRepository: """In-memory stand-in for AlertRepository.""" def __init__(self): self._records = {} def create(self, data): record_id = data.get('id') or str(uuid.uuid4()) record = {'id': record_id, 'read': False, **data} self._records[record_id] = record return record def get_by_id(self, alert_id): return self._records.get(alert_id) def list_unread(self, tenant_id): return [r for r in self._records.values() if r.get('tenant_id') == tenant_id and not r.get('read')] def list_undelivered_email(self, tenant_id): return [ r for r in self._records.values() if r.get('tenant_id') == tenant_id and not r.get('delivered_email') ] def list_recent(self, tenant_id, limit=50): items = [r for r in self._records.values() if r.get('tenant_id') == tenant_id] items.sort(key=lambda r: r.get('created') or '', reverse=True) return items[:limit] def mark_read(self, alert_id): if alert_id not in self._records: return None self._records[alert_id]['read'] = True return self._records[alert_id] def mark_email_delivered(self, alert_id): if alert_id not in self._records: return None self._records[alert_id]['delivered_email'] = True return self._records[alert_id] def list_read_older_than(self, cutoff_iso): return [ r for r in self._records.values() if r.get('read') and (r.get('created') or '') < cutoff_iso ] def delete(self, alert_id): return self._records.pop(alert_id, None) is not None alert_repository = MockAlertRepository() if USE_MOCK_REPOSITORIES else AlertRepository()