NotificationController.java

package com.ecommerce.notification.interfaces.rest;

import com.ecommerce.notification.application.model.NotificationModels.DeliveryRetryView;
import com.ecommerce.notification.application.model.NotificationModels.EmailPreferenceView;
import com.ecommerce.notification.application.model.NotificationModels.NotificationView;
import com.ecommerce.notification.application.service.NotificationApplicationService;
import com.ecommerce.platform.common.api.ApiResponse;
import com.ecommerce.platform.common.api.CursorPageResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Validated
@RestController
@RequestMapping("/api/v1/notifications")
public class NotificationController {

    private final NotificationApplicationService service;

    public NotificationController(NotificationApplicationService service) {
        this.service = service;
    }

    @GetMapping
    public ApiResponse<CursorPageResponse<NotificationView>> list(
            @AuthenticationPrincipal Jwt jwt,
            @RequestParam(required = false) String cursor,
            @RequestParam(required = false) Integer size) {
        return ApiResponse.success(service.list(userId(jwt), cursor, size));
    }

    @GetMapping("/unread-count")
    public ApiResponse<UnreadCountResponse> unreadCount(@AuthenticationPrincipal Jwt jwt) {
        return ApiResponse.success(new UnreadCountResponse(service.unreadCount(userId(jwt))));
    }

    @PostMapping("/{notificationId}/read")
    public ApiResponse<Void> markRead(
            @AuthenticationPrincipal Jwt jwt,
            @PathVariable long notificationId) {
        service.markRead(userId(jwt), notificationId);
        return ApiResponse.success(null);
    }

    @PutMapping("/email-preference")
    public ApiResponse<EmailPreferenceView> saveEmailPreference(
            @AuthenticationPrincipal Jwt jwt,
            @Valid @RequestBody EmailPreferenceRequest request) {
        return ApiResponse.success(service.saveEmailPreference(
                userId(jwt),
                request.email(),
                request.enabled()));
    }

    @PostMapping("/admin/email-deliveries/{deliveryId}/retry")
    public ApiResponse<DeliveryRetryView> retryEmailDelivery(
            @AuthenticationPrincipal Jwt jwt,
            @PathVariable long deliveryId,
            @Valid @RequestBody DeliveryRetryRequest request) {
        return ApiResponse.success(service.retryEmailDelivery(
                userId(jwt),
                deliveryId,
                request.commandId(),
                request.reason()));
    }

    private long userId(Jwt jwt) {
        try {
            long value = Long.parseLong(jwt.getSubject());
            if (value <= 0) {
                throw new IllegalArgumentException("JWT subject must be positive");
            }
            return value;
        } catch (RuntimeException exception) {
            throw new IllegalArgumentException("JWT subject is invalid", exception);
        }
    }

    public record UnreadCountResponse(long count) {
    }

    public record EmailPreferenceRequest(
            @Email @Size(max = 190) String email,
            boolean enabled) {

        public EmailPreferenceRequest {
            if (enabled && (email == null || email.isBlank())) {
                throw new IllegalArgumentException(
                        "Email is required when email notifications are enabled");
            }
        }
    }

    public record DeliveryRetryRequest(
            @NotBlank @Size(max = 64) String commandId,
            @NotBlank @Size(min = 8, max = 500) String reason) {
    }
}