ClientsController.java

package se.jobtechdev.personaldatagateway.api.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.context.request.NativeWebRequest;
import se.jobtechdev.personaldatagateway.api.generated.api.ClientsApi;
import se.jobtechdev.personaldatagateway.api.generated.model.*;
import se.jobtechdev.personaldatagateway.api.service.ClientService;
import se.jobtechdev.personaldatagateway.api.service.OrganizationService;
import se.jobtechdev.personaldatagateway.api.service.RedirectService;
import se.jobtechdev.personaldatagateway.api.util.*;

import java.util.List;
import java.util.Objects;
import java.util.UUID;

import static se.jobtechdev.personaldatagateway.api.util.ControllerUtil.*;

@Controller
public class ClientsController implements ClientsApi {

  private final ClientService clientService;

  private final RedirectService redirectService;

  private final OrganizationService organizationService;

  private final NativeWebRequest request;

  private final String baseUrl;

  @Autowired
  public ClientsController(
      @Value("${pdg-api.base-url}") String baseUrl,
      NativeWebRequest request,
      ClientService clientService,
      RedirectService redirectService,
      OrganizationService organizationService) {
    this.baseUrl = baseUrl;
    this.request = request;
    this.clientService = clientService;
    this.redirectService = redirectService;
    this.organizationService = organizationService;
  }

  @Override
  public ResponseEntity<Client> postClient(Client client) {
    final var organizationId = client.getOrganizationId();
    final var organization =
        throwApiExceptionOnAbsentValue(
            organizationService.getOrganizationById(organizationId),
            HttpStatus.BAD_REQUEST,
            String.format(
                "Failed to handle POST client request - Could not find organization with id:"
                    + " %s",
                organizationId));

    final var clientKey = KeyProvider.randomBytes(32);

    final var createdClient =
        clientService.createClient(
            organization, client.getName(), client.getRole().getValue(), clientKey);

    final var savedClient =
        ResponseFactory.createClient(createdClient);

    final var headers =
        LinkHeaderUtil.createHeadersWithLocation(savedClient.getClientId().toString(), new Class<?>[]{Client.class}, client);

    return ResponseEntity.status(HttpStatus.CREATED)
        .contentType(MediaType.APPLICATION_JSON)
        .headers(headers)
        .body(savedClient);
  }

  @Override
  public ResponseEntity<List<Client>> getClients(Integer offset, Integer limit) {
    final var pageable = OffsetBasedPageRequest.of(offset, limit);
    final var clients = clientService.getClients(pageable);

    final var response =
        clients.stream()
            .map(ResponseFactory::createClient)
            .toList();

    final var headers =
        LinkHeaderUtil.createHeadersWithPagination(
            clients, new Class<?>[]{Integer.class, Integer.class}, (int) pageable.getOffset(), pageable.getPageSize());

    return ResponseEntity.status(HttpStatus.OK)
        .contentType(MediaType.APPLICATION_JSON)
        .headers(headers)
        .body(response);
  }

  ;

  @Override
  public ResponseEntity<Client> getClient(UUID clientId) {
    final var loggedInClientAndRole = loggedInClientAndRole();

    earlyExit(
        loggedInClientAndRole.clientId(),
        id -> (loggedInClientAndRole.role().equals("consumer") && !id.equals(clientId.toString())),
        HttpStatus.UNAUTHORIZED,
        String.format(
            "Failed to handle GET client request - Requested id: %s does not match logged in client id: %s",
            clientId.toString(),
            loggedInClientAndRole.clientId()));

    final var client =
        throwApiExceptionOnAbsentValue(
            clientService.getClientById(clientId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle GET client request - Could not find client with id: %s",
                clientId));

    final var response = ResponseFactory.createClient(client);

    final var headers = LinkHeaderUtil.createHeaders(new Class<?>[]{UUID.class}, clientId);

    return ResponseEntity.status(HttpStatus.OK)
        .contentType(MediaType.APPLICATION_JSON)
        .headers(headers)
        .body(response);
  }

  @Override
  public ResponseEntity<Client> patchClient(
      UUID clientId, ClientPatch clientPatch) {
    final var client =
        throwApiExceptionOnAbsentValue(
            clientService.getClientById(clientId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle PATCH client request - Could not find client with id: %s",
                clientId));

    final var assigned = assignNonNull(clientPatch, client, List.of("revoked"));

    final var savedClient = clientService.saveClient(assigned);

    final var response = ResponseFactory.createClient(savedClient);

    final var headers = LinkHeaderUtil.createHeaders(new Class<?>[]{UUID.class, ClientPatch.class}, clientId, clientPatch);

    return ResponseEntity.status(HttpStatus.OK)
        .contentType(MediaType.APPLICATION_JSON)
        .headers(headers)
        .body(response);
  }

  @Override
  public ResponseEntity<ClientRedirect> postClientRedirect(
      UUID clientId, ClientRedirect clientRedirect) {
    final var client =
        throwApiExceptionOnAbsentValue(
            clientService.getClientById(clientId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle POST client redirect request - Could not find client with id:"
                    + " %s",
                clientId));

    final var redirect =
        redirectService.createRedirect(clientRedirect.getRedirectUrl(), client);

    final var response = ResponseFactory.createClientRedirect(redirect);

    final var headers =
        LinkHeaderUtil.createHeadersWithLocation(
            redirect.getId().toString(), new Class<?>[]{UUID.class, ClientRedirect.class}, clientId, clientRedirect);

    return ResponseEntity.status(HttpStatus.CREATED)
        .contentType(MediaType.APPLICATION_JSON)
        .headers(headers)
        .body(response);
  }

  @Override
  public ResponseEntity<List<ClientRedirect>> getClientRedirects(
      UUID clientId, Integer offset, Integer limit) {
    final var client =
        throwApiExceptionOnAbsentValue(
            clientService.getClientById(clientId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle GET client redirects request - Could not find client with id:"
                    + " %s",
                clientId));

    final var pageable = OffsetBasedPageRequest.of(offset, limit);
    final var redirects = redirectService.getRedirects(client, pageable);

    final var response =
        redirects.stream()
            .map(
                ResponseFactory::createClientRedirect)
            .toList();

    final var headers =
        LinkHeaderUtil.createHeadersWithPagination(
            redirects, new Class<?>[]{UUID.class, Integer.class, Integer.class}, clientId, (int) pageable.getOffset(), pageable.getPageSize());

    return ResponseEntity.status(HttpStatus.OK)
        .contentType(MediaType.APPLICATION_JSON)
        .headers(headers)
        .body(response);
  }

  @Override
  public ResponseEntity<ClientRedirect> getClientRedirect(
      UUID clientId, UUID redirectId) {
    final var client =
        throwApiExceptionOnAbsentValue(
            clientService.getClientById(clientId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle GET client redirect request - Could not find client with id:"
                    + " %s",
                clientId));

    final var redirect =
        throwApiExceptionOnAbsentValue(
            redirectService.getRedirectById(redirectId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle GET client redirect request - Could not find redirect with id: %s",
                redirectId));

    final var redirectClientId = redirect.getClientEntity().getId();

    earlyExit(
        redirectClientId,
        UuidProvider.generateInequalityCheckingLambda(client.getId()),
        HttpStatus.NOT_FOUND,
        String.format(
            "Failed to handle GET client redirect request - The redirect's [clientId:%s] does not"
                + " match provided [clientId:%s]",
            redirectClientId, clientId));

    final var response =
        ResponseFactory.createClientRedirect(redirect);

    final var headers = LinkHeaderUtil.createHeaders(new Class<?>[]{UUID.class, UUID.class}, clientId, redirectId);

    return ResponseEntity.status(HttpStatus.OK)
        .contentType(MediaType.APPLICATION_JSON)
        .headers(headers)
        .body(response);
  }

  @Override
  public ResponseEntity<ClientRedirect> patchClientRedirect(
      UUID clientId, UUID redirectId, ClientRedirectPatch clientRedirectPatch) {
    final var client =
        throwApiExceptionOnAbsentValue(
            clientService.getClientById(clientId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle PATCH client redirect request - Could not find client with id: %s",
                clientId));

    final var redirect =
        throwApiExceptionOnAbsentValue(
            redirectService.getRedirectById(redirectId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle PATCH client redirect request - Could not find redirect with id: %s",
                redirectId));

    final var redirectClientId = redirect.getClientEntity().getId();

    earlyExit(
        redirectClientId,
        UuidProvider.generateInequalityCheckingLambda(client.getId()),
        HttpStatus.NOT_FOUND,
        String.format(
            "Failed to handle PATCH client redirect request - The redirect's [clientId:%s] does not"
                + " match provided [clientId:%s]",
            redirectClientId, clientId));

    final var assigned = assignNonNull(clientRedirectPatch, redirect, List.of("deleted"));

    final var updatedRedirect = redirectService.update(assigned);

    final var response =
        ResponseFactory.createClientRedirect(updatedRedirect);

    final var headers =
        LinkHeaderUtil.createHeaders(new Class<?>[]{UUID.class, UUID.class, ClientRedirectPatch.class}, clientId, redirectId, clientRedirectPatch);

    return ResponseEntity.status(HttpStatus.OK)
        .contentType(MediaType.APPLICATION_JSON)
        .headers(headers)
        .body(response);
  }

  @Override
  public ResponseEntity<List<ClientKeyReset>> getClientKeyResets(
      UUID clientId, Integer offset, Integer limit) {
    final var client =
        throwApiExceptionOnAbsentValue(
            clientService.getClientById(clientId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle GET client key resets request - Could not find client with id: %s",
                clientId));

    final var pageable = OffsetBasedPageRequest.of(offset, limit);
    final var resets = clientService.getAllClientKeyResetsByClientId(client, pageable);

    final var response =
        resets.stream()
            .map(
                ResponseFactory::createClientKeyReset)
            .toList();

    final var headers =
        LinkHeaderUtil.createHeadersWithPagination(
            resets, new Class<?>[]{UUID.class, Integer.class, Integer.class}, clientId, (int) pageable.getOffset(), pageable.getPageSize());

    return ResponseEntity.status(HttpStatus.OK)
        .contentType(MediaType.APPLICATION_JSON)
        .headers(headers)
        .body(response);
  }

  @Override
  public ResponseEntity<ClientKeyOtc> postClientKeyReset(UUID clientId, ClientKeyResetOtc clientKeyResetOtc) {
    final var loggedInClientAndRole = loggedInClientAndRole();
    final var loggedInClientId = loggedInClientAndRole.clientId();
    final var loggedInRole = loggedInClientAndRole.role();
    earlyExit(
        loggedInClientId,
        id -> (!loggedInRole.equals("admin") && !id.equals(clientId.toString())),
        HttpStatus.UNAUTHORIZED,
        String.format(
            "Failed to handle POST client key reset request - Requested id: %s does not match logged in client id: %s",
            clientId.toString(),
            loggedInClientId));
    final var client =
        throwApiExceptionOnAbsentValue(
            clientService.getClientById(clientId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle POST client key reset request - Could not find client with id: %s",
                clientId));
    final var providedKeyResetString = (clientKeyResetOtc != null) ? clientKeyResetOtc.getKeyResetCode() : "";
    earlyExit(
        client.getKeyResetCode(),
        clientKeyResetCode -> (!loggedInRole.equals("admin") && !clientKeyResetCode.equals(providedKeyResetString)),
        HttpStatus.UNAUTHORIZED,
        String.format(
            "Failed to handle POST client key reset request - Provided key reset code: %s does not match the one generated for the client with id: %s",
            providedKeyResetString,
            clientId));

    final var clientKeyResetEntity = clientService.keyReset(client);

    final var response = ResponseFactory.createClientKeyOtc(clientKeyResetEntity);

    final var headers = LinkHeaderUtil.createHeaders(new Class<?>[]{UUID.class, ClientKeyResetOtc.class}, clientId, clientKeyResetOtc);

    return ResponseEntity.status(HttpStatus.OK)
        .contentType(MediaType.APPLICATION_JSON)
        .headers(headers)
        .body(response);
  }

  @Override
  public ResponseEntity<ClientKey> postClientKey(UUID clientId, ClientKeyOtc clientKeyOtc) {
    final var clientEntity =
        throwApiExceptionOnAbsentValue(
            clientService.getClientById(clientId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle POST key request - Could not find client with id: %s",
                clientId));

    final var optionalKeyReset = clientService.getKeyReset(clientEntity, clientKeyOtc.getCode());
    final var keyReset =
        throwApiExceptionOnAbsentValue(
            optionalKeyReset,
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle POST key request - Could not find key reset for code: %s", clientKeyOtc.getCode()));

    final var now = TimeProvider.now();
    earlyExit(
        keyReset.getCreated(),
        created -> created.isBefore(now.minusDays(1)),
        HttpStatus.GONE,
        String.format(
            "Failed to handle POST key request - code: %s is more then a day old", clientKeyOtc.getCode()));

    earlyExit(
        keyReset.getAccessed(),
        Objects::nonNull,
        HttpStatus.GONE,
        String.format(
            "Failed to handle POST key request - Key has already been accessed by using code: %s", clientKeyOtc.getCode()));

    final var clientKey = clientService.key(clientEntity, keyReset);

    final var headers = LinkHeaderUtil.createHeaders(new Class<?>[]{UUID.class, ClientKeyOtc.class}, clientId, clientKeyOtc);

    return ResponseEntity.status(HttpStatus.OK)
        .contentType(MediaType.APPLICATION_JSON)
        .headers(headers)
        .body(clientKey);
  }

}