266 lines
9.6 KiB
Python
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()
|