diff --git a/app/notifications/rest.py b/app/notifications/rest.py index 4e9adc2c6..7e5682027 100644 --- a/app/notifications/rest.py +++ b/app/notifications/rest.py @@ -15,6 +15,7 @@ from app.notifications.validators import ( service_has_permission, validate_template, ) +from app.public_schemas.public import PublicNotificationSchema from app.schemas import ( email_notification_schema, notification_with_personalisation_schema, @@ -35,6 +36,7 @@ def get_notification_by_id(notification_id): notification = notifications_dao.get_notification_with_personalisation( str(authenticated_service.id), notification_id, key_type=None ) + if notification.job_id is not None: notification.personalisation = get_personalisation_from_s3( notification.service_id, @@ -48,16 +50,9 @@ def get_notification_by_id(notification_id): ) notification.to = recipient notification.normalised_to = recipient - return ( - jsonify( - data={ - "notification": notification_with_personalisation_schema.dump( - notification - ) - } - ), - 200, - ) + + serialized = PublicNotificationSchema().dump(notification) + return jsonify(data={"notification": serialized}), 200 @notifications.route("/notifications", methods=["GET"]) diff --git a/app/public_schemas/__init__.py b/app/public_schemas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/public_schemas/public.py b/app/public_schemas/public.py new file mode 100644 index 000000000..5b8d36a6d --- /dev/null +++ b/app/public_schemas/public.py @@ -0,0 +1,70 @@ +from datetime import timezone + +from marshmallow import Schema, fields, pre_dump + + +class PublicTemplateSchema(Schema): + id = fields.UUID(required=True) + name = fields.String(required=True) + template_type = fields.String(required=True) + version = fields.Integer(required=True) + + +class PublicJobSchema(Schema): + id = fields.UUID(required=True) + original_file_name = fields.String(required=True) + + +class PublicNotificationSchema(Schema): + id = fields.UUID(required=True) + to = fields.String(required=True) + job_row_number = fields.Integer(allow_none=True) + template_version = fields.Integer(required=True) + billable_units = fields.Integer(required=True) + notification_type = fields.String(required=True) + created_at = fields.String(required=True) + sent_at = fields.String(allow_none=True) + updated_at = fields.String(allow_none=True) + sent_by = fields.String(allow_none=True) + status = fields.String(required=True) + reference = fields.String(allow_none=True) + template = fields.Nested(PublicTemplateSchema, required=True) + service = fields.UUID(required=True) + job = fields.Nested(PublicJobSchema, allow_none=True) + api_key = fields.UUID(allow_none=True) + body = fields.String(required=True) + content_char_count = fields.Integer(required=True) + + @pre_dump + def transform(self, notification, **kwargs): + def to_rfc3339(dt): + if dt is None: + return None + if dt.tzinfo is None: + dt = dt.replace(tzinfo=timezone.utc) + return dt.isoformat().replace("+00:00", "Z") + + return { + **notification.__dict__, + "created_at": to_rfc3339(getattr(notification, "created_at", None)), + "sent_at": to_rfc3339(getattr(notification, "sent_at", None)), + "updated_at": to_rfc3339(getattr(notification, "updated_at", None)), + "sent_by": getattr(notification, "sent_by", None), + "reference": getattr(notification, "reference", None), + "service": str(notification.service.id) if notification.service else None, + "api_key": str(notification.api_key.id) if notification.api_key else None, + "body": getattr(notification, "body", None) + or (notification.template.content if notification.template else ""), + "content_char_count": len( + getattr(notification, "body", "") + or (notification.template.content if notification.template else "") + ), + "job": ( + { + "id": str(notification.job.id), + "original_file_name": notification.job.original_file_name, + } + if hasattr(notification, "job") and notification.job + else None + ), + } diff --git a/app/schemas.py b/app/schemas.py index 06d668a7f..9130514ac 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -145,7 +145,7 @@ class UserSchema(BaseSchema): try: validate_email_address(value) except InvalidEmailError as e: - raise ValidationError(f"{data_key}: {str(e)}") + raise ValidationError(str(e)) @validates("mobile_number") def validate_mobile_number(self, value, data_key): @@ -185,7 +185,7 @@ class UserUpdateAttributeSchema(BaseSchema): try: validate_email_address(value) except InvalidEmailError as e: - raise ValidationError(f"{data_key}: {str(e)}") + raise ValidationError(str(e)) @validates("mobile_number") def validate_mobile_number(self, value, data_key): @@ -534,7 +534,7 @@ class EmailNotificationSchema(NotificationSchema): try: validate_email_address(value) except InvalidEmailError as e: - raise ValidationError(f"{data_key}: {str(e)}") + raise ValidationError(str(e)) class SmsTemplateNotificationSchema(SmsNotificationSchema): @@ -544,6 +544,7 @@ class SmsTemplateNotificationSchema(SmsNotificationSchema): class NotificationWithTemplateSchema(BaseSchema): class Meta(BaseSchema.Meta): + unknown = EXCLUDE model = models.Notification exclude = ("_personalisation",) @@ -616,26 +617,30 @@ class NotificationWithPersonalisationSchema(NotificationWithTemplateSchema): @pre_dump def handle_personalisation_property(self, in_data, **kwargs): - self.personalisation = in_data.personalisation + in_data._merged_personalisation = in_data.personalisation return in_data - @post_dump - def handle_template_merge(self, in_data, **kwargs): - in_data["template"] = in_data.pop("template_history") - template = get_template_instance( - in_data["template"], in_data["personalisation"] - ) - in_data["body"] = template.content_with_placeholders_filled_in - if in_data["template"]["template_type"] != TemplateType.SMS: - in_data["subject"] = template.subject - in_data["content_char_count"] = None - else: - in_data["content_char_count"] = template.content_count - in_data.pop("personalisation", None) - in_data["template"].pop("content", None) - in_data["template"].pop("subject", None) - return in_data +@post_dump(pass_original=True) +def handle_template_merge(self, in_data, original_obj, **kwargs): + personalisation = getattr(original_obj, "_merged_personalisation", None) + + in_data["template"] = in_data.pop("template_history") + template = get_template_instance(in_data["template"], personalisation) + + in_data["body"] = template.content_with_placeholders_filled_in + + if in_data["template"]["template_type"] != TemplateType.SMS: + in_data["subject"] = template.subject + in_data["content_char_count"] = None + else: + in_data["content_char_count"] = template.content_count + + in_data["template"].pop("content", None) + in_data["template"].pop("subject", None) + in_data.pop("personalisation", None) + + return in_data class InvitedUserSchema(BaseSchema): @@ -651,7 +656,7 @@ class InvitedUserSchema(BaseSchema): try: validate_email_address(value) except InvalidEmailError as e: - raise ValidationError(f"{data_key}: {str(e)}") + raise ValidationError(str(e)) class EmailDataSchema(ma.Schema): @@ -673,7 +678,7 @@ class EmailDataSchema(ma.Schema): try: validate_email_address(value) except InvalidEmailError as e: - raise ValidationError(f"{data_key}: {str(e)}") + raise ValidationError(str(e)) class NotificationsFilterSchema(ma.Schema):