2026-01-26 11:09:40 -05:00

266 lines
9.6 KiB
Python

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()