NotificationController.java

package no.nav.data.team.notify;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import no.nav.data.common.exceptions.ValidationException;
import no.nav.data.common.rest.RestResponsePage;
import no.nav.data.common.security.SecurityUtils;
import no.nav.data.common.storage.StorageService;
import no.nav.data.common.storage.domain.GenericStorage;
import no.nav.data.team.notify.domain.Notification;
import no.nav.data.team.notify.domain.Notification.NotificationType;
import no.nav.data.team.notify.domain.NotificationRepository;
import no.nav.data.team.notify.dto.Changelog;
import no.nav.data.team.notify.dto.NotificationDto;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

import static no.nav.data.common.utils.StreamUtils.convert;

@Slf4j
@RestController
@Tag(name = "Notification")
@RequestMapping("/notification")
public class NotificationController {

    private final StorageService storage;
    private final NotificationService service;
    private final NotificationRepository repository;
    private final SecurityUtils securityUtils;

    public NotificationController(StorageService storage, NotificationService service, NotificationRepository repository, SecurityUtils securityUtils) {
        this.storage = storage;
        this.service = service;
        this.repository = repository;
        this.securityUtils = securityUtils;
    }

    @Operation(summary = "Get Notifications for current user")
    @ApiResponses(value = {@ApiResponse(description = "Notifications fetched")})
    @GetMapping
    public ResponseEntity<RestResponsePage<NotificationDto>> getForCurrentUser() {
        var ident = securityUtils.lookupCurrentIdent();
        if (ident.isEmpty()) {
            return ResponseEntity.ok(new RestResponsePage<>());
        }
        return ResponseEntity.ok(new RestResponsePage<>(getAll(ident.get())));
    }

    @Operation(summary = "Get Notification")
    @ApiResponses(value = {@ApiResponse(description = "Notification fetched")})
    @GetMapping("/{id}")
    public ResponseEntity<NotificationDto> get(@PathVariable("id") UUID id) {
        return ResponseEntity.ok(storage.get(id, Notification.class).convertToDto());
    }

    @Operation(summary = "Save new Notification - immutable")
    @ApiResponses(value = {@ApiResponse(description = "Notification saved")})
    @PostMapping
    public ResponseEntity<NotificationDto> save(@RequestBody NotificationDto dto) {
        return ResponseEntity.ok(service.save(dto).convertToDto());
    }

    @Operation(summary = "Delete Notification")
    @ApiResponses(value = {@ApiResponse(description = "Notification deleted")})
    @DeleteMapping("/{id}")
    public ResponseEntity<NotificationDto> delete(@PathVariable("id") UUID id) {
        return ResponseEntity.ok(service.delete(id).convertToDto());
    }

    @Operation(summary = "diff test")
    @ApiResponses(value = {@ApiResponse(description = "diff")})
    @GetMapping(value = "/diff", produces = "text/html")
    public ResponseEntity<String> diff(
            @RequestParam(value = "type") NotificationType type,
            @RequestParam(value = "targetId", required = false) UUID targetId,
            @RequestParam(value = "start") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start,
            @RequestParam(value = "end", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime end
    ) {
        if (targetId == null && type != NotificationType.ALL_EVENTS) {
            throw new ValidationException("need targetId for " + type);
        }
        if (end == null) {
            end = LocalDateTime.now();
        }
        try {
            String changelog = service.changelogMail(type, targetId, start, end);
            return ResponseEntity.ok(changelog);
        } catch (Exception e) {
            log.error("notification diff failed", e);
            return new ResponseEntity<>("error", HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @Operation(summary = "Changelog")
    @ApiResponses(value = {@ApiResponse(description = "Changelog for team updates")})
    @GetMapping(value = "/changelog")
    public ResponseEntity<Changelog> changelog(
            @RequestParam(value = "type") NotificationType type,
            @RequestParam(value = "targetId", required = false) UUID targetId,
            @RequestParam(value = "start") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime start,
            @RequestParam(value = "end", required = false) @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime end
    ) {
        if (targetId == null && type != NotificationType.ALL_EVENTS) {
            throw new ValidationException("need targetId for " + type);
        }
        if (end == null) {
            end = LocalDateTime.now();
        }
        if (!Duration.between(start, end).minusDays(32).isNegative()) {
            throw new ValidationException("Duration is more than 31 days");
        }
        try {
            var changelog = service.changelogJson(type, targetId, start, end);
            return ResponseEntity.ok(changelog);
        } catch (Exception e) {
            log.error("notification diff failed", e);
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @Operation(summary = "Changelog")
    @ApiResponses(value = {@ApiResponse(description = "Changelog for team updates")})
    @GetMapping(value = "/changelog/day")
    public ResponseEntity<RestResponsePage<Changelog>> changelogDay(
            @RequestParam(value = "type") NotificationType type,
            @RequestParam(value = "targetId", required = false) UUID targetId,
            @RequestParam(value = "start") @DateTimeFormat(iso = ISO.DATE) LocalDate start,
            @RequestParam(value = "end", required = false) @DateTimeFormat(iso = ISO.DATE) LocalDate end
    ) {
        if (targetId == null && type != NotificationType.ALL_EVENTS) {
            throw new ValidationException("need targetId for " + type);
        }
        if (end == null) {
            end = LocalDate.now();
        }
        if (ChronoUnit.DAYS.between(start, end) > 31) {
            throw new ValidationException("Duration is more than 31 days");
        }
        try {
            var changelog = start.datesUntil(end)
                    .map(d -> service.changelogJson(type, targetId, LocalDateTime.of(d, LocalTime.MIN), LocalDateTime.of(d, LocalTime.MAX)))
                    .collect(Collectors.toList());
            return ResponseEntity.ok(new RestResponsePage<>(changelog));
        } catch (Exception e) {
            log.error("notification diff failed", e);
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    private List<NotificationDto> getAll(String ident) {
        return convert(GenericStorage.to(repository.findByIdent(ident), Notification.class), Notification::convertToDto);
    }

    static class NotificationPage extends RestResponsePage<NotificationDto> {

    }
}