209 lines
8.7 KiB
Python
209 lines
8.7 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
Notification model for in-app notifications.
|
|
Supports actionable notifications (e.g., invitation accept/decline).
|
|
"""
|
|
|
|
import uuid
|
|
from typing import Optional, List
|
|
from enum import Enum
|
|
from pydantic import BaseModel, Field, ConfigDict
|
|
from modules.shared.attributeUtils import registerModelLabels
|
|
from modules.shared.timeUtils import getUtcTimestamp
|
|
|
|
|
|
class NotificationType(str, Enum):
|
|
"""Types of notifications"""
|
|
INVITATION = "invitation" # Einladung zu Mandat/Feature
|
|
SYSTEM = "system" # System-Nachrichten
|
|
WORKFLOW = "workflow" # Workflow-Status Updates
|
|
MENTION = "mention" # Erwähnung in Chat/Kommentar
|
|
|
|
|
|
class NotificationStatus(str, Enum):
|
|
"""Status of a notification"""
|
|
UNREAD = "unread" # Noch nicht gelesen
|
|
READ = "read" # Gelesen
|
|
ACTIONED = "actioned" # Aktion wurde durchgeführt
|
|
DISMISSED = "dismissed" # Verworfen/Geschlossen
|
|
|
|
|
|
class NotificationAction(BaseModel):
|
|
"""Possible action for a notification"""
|
|
actionId: str = Field(
|
|
description="Unique identifier for the action (e.g., 'accept', 'decline')"
|
|
)
|
|
label: str = Field(
|
|
description="Display label for the action button"
|
|
)
|
|
style: str = Field(
|
|
default="default",
|
|
description="Button style: 'primary', 'danger', 'default'"
|
|
)
|
|
|
|
|
|
class UserNotification(BaseModel):
|
|
"""
|
|
In-app notification for a user.
|
|
Supports actionable notifications with accept/decline buttons.
|
|
"""
|
|
id: str = Field(
|
|
default_factory=lambda: str(uuid.uuid4()),
|
|
description="Unique ID of the notification",
|
|
json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False}
|
|
)
|
|
userId: str = Field(
|
|
description="Target user ID for this notification",
|
|
json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": True}
|
|
)
|
|
|
|
# Notification type and status
|
|
type: NotificationType = Field(
|
|
default=NotificationType.SYSTEM,
|
|
description="Type of notification",
|
|
json_schema_extra={
|
|
"frontend_type": "select",
|
|
"frontend_readonly": True,
|
|
"frontend_required": True,
|
|
"frontend_options": [
|
|
{"value": "invitation", "label": {"en": "Invitation", "de": "Einladung"}},
|
|
{"value": "system", "label": {"en": "System", "de": "System"}},
|
|
{"value": "workflow", "label": {"en": "Workflow", "de": "Workflow"}},
|
|
{"value": "mention", "label": {"en": "Mention", "de": "Erwähnung"}}
|
|
]
|
|
}
|
|
)
|
|
status: NotificationStatus = Field(
|
|
default=NotificationStatus.UNREAD,
|
|
description="Current status of the notification",
|
|
json_schema_extra={
|
|
"frontend_type": "select",
|
|
"frontend_readonly": True,
|
|
"frontend_required": False,
|
|
"frontend_options": [
|
|
{"value": "unread", "label": {"en": "Unread", "de": "Ungelesen"}},
|
|
{"value": "read", "label": {"en": "Read", "de": "Gelesen"}},
|
|
{"value": "actioned", "label": {"en": "Actioned", "de": "Bearbeitet"}},
|
|
{"value": "dismissed", "label": {"en": "Dismissed", "de": "Verworfen"}}
|
|
]
|
|
}
|
|
)
|
|
|
|
# Content
|
|
title: str = Field(
|
|
description="Notification title",
|
|
json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": True}
|
|
)
|
|
message: str = Field(
|
|
description="Notification message/body",
|
|
json_schema_extra={"frontend_type": "textarea", "frontend_readonly": True, "frontend_required": True}
|
|
)
|
|
icon: Optional[str] = Field(
|
|
default=None,
|
|
description="Optional icon identifier (e.g., 'mail', 'warning', 'info')",
|
|
json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False}
|
|
)
|
|
|
|
# Reference to triggering object (for actionable notifications)
|
|
referenceType: Optional[str] = Field(
|
|
default=None,
|
|
description="Type of referenced object (e.g., 'Invitation', 'Workflow')",
|
|
json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False}
|
|
)
|
|
referenceId: Optional[str] = Field(
|
|
default=None,
|
|
description="ID of referenced object",
|
|
json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False}
|
|
)
|
|
|
|
# Actions (for actionable notifications like invitations)
|
|
actions: Optional[List[NotificationAction]] = Field(
|
|
default=None,
|
|
description="List of possible actions for this notification",
|
|
json_schema_extra={"frontend_type": "json", "frontend_readonly": True, "frontend_required": False}
|
|
)
|
|
|
|
# Action result (when user takes action)
|
|
actionTaken: Optional[str] = Field(
|
|
default=None,
|
|
description="Which action was taken (actionId)",
|
|
json_schema_extra={"frontend_type": "text", "frontend_readonly": True, "frontend_required": False}
|
|
)
|
|
actionResult: Optional[str] = Field(
|
|
default=None,
|
|
description="Result message from the action",
|
|
json_schema_extra={"frontend_type": "textarea", "frontend_readonly": True, "frontend_required": False}
|
|
)
|
|
|
|
# Timestamps
|
|
createdAt: float = Field(
|
|
default_factory=getUtcTimestamp,
|
|
description="When the notification was created (UTC timestamp)",
|
|
json_schema_extra={"frontend_type": "timestamp", "frontend_readonly": True, "frontend_required": False}
|
|
)
|
|
readAt: Optional[float] = Field(
|
|
default=None,
|
|
description="When the notification was read (UTC timestamp)",
|
|
json_schema_extra={"frontend_type": "timestamp", "frontend_readonly": True, "frontend_required": False}
|
|
)
|
|
actionedAt: Optional[float] = Field(
|
|
default=None,
|
|
description="When action was taken (UTC timestamp)",
|
|
json_schema_extra={"frontend_type": "timestamp", "frontend_readonly": True, "frontend_required": False}
|
|
)
|
|
expiresAt: Optional[float] = Field(
|
|
default=None,
|
|
description="When the notification expires (optional, UTC timestamp)",
|
|
json_schema_extra={"frontend_type": "timestamp", "frontend_readonly": True, "frontend_required": False}
|
|
)
|
|
|
|
model_config = ConfigDict(use_enum_values=True)
|
|
|
|
|
|
registerModelLabels(
|
|
"UserNotification",
|
|
{"en": "Notification", "de": "Benachrichtigung", "fr": "Notification"},
|
|
{
|
|
"id": {"en": "ID", "de": "ID", "fr": "ID"},
|
|
"userId": {"en": "User", "de": "Benutzer", "fr": "Utilisateur"},
|
|
"type": {"en": "Type", "de": "Typ", "fr": "Type"},
|
|
"status": {"en": "Status", "de": "Status", "fr": "Statut"},
|
|
"title": {"en": "Title", "de": "Titel", "fr": "Titre"},
|
|
"message": {"en": "Message", "de": "Nachricht", "fr": "Message"},
|
|
"icon": {"en": "Icon", "de": "Symbol", "fr": "Icône"},
|
|
"referenceType": {"en": "Reference Type", "de": "Referenz-Typ", "fr": "Type de référence"},
|
|
"referenceId": {"en": "Reference ID", "de": "Referenz-ID", "fr": "ID de référence"},
|
|
"actions": {"en": "Actions", "de": "Aktionen", "fr": "Actions"},
|
|
"actionTaken": {"en": "Action Taken", "de": "Durchgeführte Aktion", "fr": "Action effectuée"},
|
|
"actionResult": {"en": "Action Result", "de": "Aktions-Ergebnis", "fr": "Résultat de l'action"},
|
|
"createdAt": {"en": "Created At", "de": "Erstellt am", "fr": "Créé le"},
|
|
"readAt": {"en": "Read At", "de": "Gelesen am", "fr": "Lu le"},
|
|
"actionedAt": {"en": "Actioned At", "de": "Bearbeitet am", "fr": "Traité le"},
|
|
"expiresAt": {"en": "Expires At", "de": "Gültig bis", "fr": "Expire le"},
|
|
},
|
|
)
|
|
|
|
|
|
registerModelLabels(
|
|
"NotificationType",
|
|
{"en": "Notification Type", "de": "Benachrichtigungs-Typ", "fr": "Type de notification"},
|
|
{
|
|
"invitation": {"en": "Invitation", "de": "Einladung", "fr": "Invitation"},
|
|
"system": {"en": "System", "de": "System", "fr": "Système"},
|
|
"workflow": {"en": "Workflow", "de": "Workflow", "fr": "Workflow"},
|
|
"mention": {"en": "Mention", "de": "Erwähnung", "fr": "Mention"},
|
|
},
|
|
)
|
|
|
|
|
|
registerModelLabels(
|
|
"NotificationStatus",
|
|
{"en": "Notification Status", "de": "Benachrichtigungs-Status", "fr": "Statut de notification"},
|
|
{
|
|
"unread": {"en": "Unread", "de": "Ungelesen", "fr": "Non lu"},
|
|
"read": {"en": "Read", "de": "Gelesen", "fr": "Lu"},
|
|
"actioned": {"en": "Actioned", "de": "Bearbeitet", "fr": "Traité"},
|
|
"dismissed": {"en": "Dismissed", "de": "Verworfen", "fr": "Rejeté"},
|
|
},
|
|
)
|