import strawberry from typing import List, Optional from strawberry.types import Info from strawberry.relay import GlobalID from channels.db import database_sync_to_async from django.contrib.contenttypes.models import ContentType from django.utils import timezone from core.graphql.types.event import NotificationRuleType, NotificationType from core.models.events import NotificationRule, Notification from core.models.enums import ( EventTypeChoices, NotificationChannelChoices, RoleChoices ) @strawberry.input class NotificationRuleInput: """Input for creating a notification rule""" name: str description: Optional[str] = "" event_types: List[EventTypeChoices] channels: List[NotificationChannelChoices] target_roles: Optional[List[RoleChoices]] = None target_team_profile_ids: Optional[List[strawberry.ID]] = None target_customer_profile_ids: Optional[List[strawberry.ID]] = None is_active: Optional[bool] = True template_subject: Optional[str] = "" template_body: Optional[str] = "" conditions: Optional[strawberry.scalars.JSON] = None @strawberry.input class NotificationRuleUpdateInput: """Input for updating a notification rule""" id: GlobalID name: Optional[str] = None description: Optional[str] = None event_types: Optional[List[EventTypeChoices]] = None channels: Optional[List[NotificationChannelChoices]] = None target_roles: Optional[List[RoleChoices]] = None target_team_profile_ids: Optional[List[strawberry.ID]] = None target_customer_profile_ids: Optional[List[strawberry.ID]] = None is_active: Optional[bool] = None template_subject: Optional[str] = None template_body: Optional[str] = None conditions: Optional[strawberry.scalars.JSON] = None @strawberry.type class Mutation: @strawberry.mutation(description="Create a notification rule (Admin only)") async def create_notification_rule( self, info: Info, input: NotificationRuleInput ) -> NotificationRuleType: profile = getattr(info.context.request, 'profile', None) if not profile: raise PermissionError("Authentication required") # Only admins can create notification rules from core.models.profile import TeamProfile if not isinstance(profile, TeamProfile) or profile.role != RoleChoices.ADMIN: raise PermissionError("Admin access required") # Prepare data data = { 'name': input.name, 'description': input.description or '', 'event_types': input.event_types, 'channels': input.channels, 'target_roles': input.target_roles or [], 'is_active': input.is_active if input.is_active is not None else True, 'template_subject': input.template_subject or '', 'template_body': input.template_body or '', 'conditions': input.conditions or {}, } # Create rule rule = await database_sync_to_async(NotificationRule.objects.create)(**data) # Set M2M relationships if input.target_team_profile_ids: await database_sync_to_async( lambda: rule.target_team_profiles.set(input.target_team_profile_ids) )() if input.target_customer_profile_ids: await database_sync_to_async( lambda: rule.target_customer_profiles.set(input.target_customer_profile_ids) )() return rule @strawberry.mutation(description="Update a notification rule (Admin only)") async def update_notification_rule( self, info: Info, input: NotificationRuleUpdateInput ) -> NotificationRuleType: profile = getattr(info.context.request, 'profile', None) if not profile: raise PermissionError("Authentication required") # Only admins can update notification rules from core.models.profile import TeamProfile if not isinstance(profile, TeamProfile) or profile.role != RoleChoices.ADMIN: raise PermissionError("Admin access required") # Get rule rule = await database_sync_to_async(NotificationRule.objects.get)(pk=input.id.node_id) # Update fields update_fields = [] if input.name is not None: rule.name = input.name update_fields.append('name') if input.description is not None: rule.description = input.description update_fields.append('description') if input.event_types is not None: rule.event_types = input.event_types update_fields.append('event_types') if input.channels is not None: rule.channels = input.channels update_fields.append('channels') if input.target_roles is not None: rule.target_roles = input.target_roles update_fields.append('target_roles') if input.is_active is not None: rule.is_active = input.is_active update_fields.append('is_active') if input.template_subject is not None: rule.template_subject = input.template_subject update_fields.append('template_subject') if input.template_body is not None: rule.template_body = input.template_body update_fields.append('template_body') if input.conditions is not None: rule.conditions = input.conditions update_fields.append('conditions') if update_fields: update_fields.append('updated_at') await database_sync_to_async(rule.save)(update_fields=update_fields) # Update M2M relationships if input.target_team_profile_ids is not None: await database_sync_to_async( lambda: rule.target_team_profiles.set(input.target_team_profile_ids) )() if input.target_customer_profile_ids is not None: await database_sync_to_async( lambda: rule.target_customer_profiles.set(input.target_customer_profile_ids) )() return rule @strawberry.mutation(description="Delete a notification rule (Admin only)") async def delete_notification_rule( self, info: Info, id: strawberry.ID ) -> strawberry.ID: profile = getattr(info.context.request, 'profile', None) if not profile: raise PermissionError("Authentication required") # Only admins can delete notification rules from core.models.profile import TeamProfile if not isinstance(profile, TeamProfile) or profile.role != RoleChoices.ADMIN: raise PermissionError("Admin access required") rule = await database_sync_to_async(NotificationRule.objects.get)(pk=id) await database_sync_to_async(rule.delete)() return id @strawberry.mutation(description="Mark notification as read") async def mark_notification_as_read( self, info: Info, id: strawberry.ID ) -> NotificationType: profile = getattr(info.context.request, 'profile', None) if not profile: raise PermissionError("Authentication required") # Get notification notification = await database_sync_to_async( lambda: Notification.objects.select_related('event', 'rule', 'recipient_content_type').get(pk=id) )() # Verify user has access to this notification content_type = await database_sync_to_async(ContentType.objects.get_for_model)(profile) if (notification.recipient_content_type != content_type or str(notification.recipient_object_id) != str(profile.id)): raise PermissionError("Not authorized to mark this notification as read") # Mark as read await database_sync_to_async(lambda: notification.mark_as_read())() return notification @strawberry.mutation(description="Mark all notifications as read for current user") async def mark_all_notifications_as_read(self, info: Info) -> int: profile = getattr(info.context.request, 'profile', None) if not profile: return 0 # Get content type for the profile content_type = await database_sync_to_async(ContentType.objects.get_for_model)(profile) # Update all unread notifications from core.models.enums import NotificationStatusChoices count = await database_sync_to_async( lambda: Notification.objects.filter( recipient_content_type=content_type, recipient_object_id=profile.id, read_at__isnull=True ).update( read_at=timezone.now(), status=NotificationStatusChoices.READ ) )() return count @strawberry.mutation(description="Delete a notification") async def delete_notification( self, info: Info, id: strawberry.ID ) -> strawberry.ID: profile = getattr(info.context.request, 'profile', None) if not profile: raise PermissionError("Authentication required") # Get notification and verify access @database_sync_to_async def get_and_verify(): notification = Notification.objects.get(pk=id) content_type = ContentType.objects.get_for_model(type(profile)) if (notification.recipient_content_type != content_type or str(notification.recipient_object_id) != str(profile.id)): raise PermissionError("Not authorized to delete this notification") notification.delete() return id return await get_and_verify()