DatasourcesController.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.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.context.request.NativeWebRequest;
import se.jobtechdev.personaldatagateway.api.generated.api.DatasourcesApi;
import se.jobtechdev.personaldatagateway.api.generated.model.*;
import se.jobtechdev.personaldatagateway.api.service.DataService;
import se.jobtechdev.personaldatagateway.api.service.DatasourceService;
import se.jobtechdev.personaldatagateway.api.service.FaqService;
import se.jobtechdev.personaldatagateway.api.service.OrganizationService;
import se.jobtechdev.personaldatagateway.api.util.LinkHeaderUtil;
import se.jobtechdev.personaldatagateway.api.util.OffsetBasedPageRequest;
import se.jobtechdev.personaldatagateway.api.util.ResponseFactory;

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

import static se.jobtechdev.personaldatagateway.api.util.ControllerUtil.assignNonNull;
import static se.jobtechdev.personaldatagateway.api.util.ControllerUtil.throwApiExceptionOnAbsentValue;

@Controller
public class DatasourcesController implements DatasourcesApi {
  public static final String DELETED = "deleted";

  private final DatasourceService datasourceService;

  private final FaqService faqService;

  private final OrganizationService organizationService;

  private final DataService dataService;

  private final NativeWebRequest request;

  private final String baseUrl;

  @Autowired
  public DatasourcesController(
      @Value("${pdg-api.base-url}") String baseUrl,
      NativeWebRequest request,
      DatasourceService datasourceService,
      FaqService faqService,
      OrganizationService organizationService,
      DataService dataService) {
    this.baseUrl = baseUrl;
    this.request = request;
    this.datasourceService = datasourceService;
    this.faqService = faqService;
    this.organizationService = organizationService;
    this.dataService = dataService;
  }

  private static String getDatasourceNotFoundMessage(UUID datasourceId) {
    return String.format("Could not find datasource by id: %s", datasourceId);
  }

  @Override
  public ResponseEntity<Datasource> postDatasource(Datasource datasource) {
    final var organizationId = datasource.getOrganizationId();

    final var organization =
        throwApiExceptionOnAbsentValue(
            organizationService.getOrganizationById(organizationId),
            HttpStatus.BAD_REQUEST,
            String.format(
                "Failed to handle POST datasource request - Could not find organization with id: %s",
                organizationId));

    final var createdDatasource =
        throwApiExceptionOnAbsentValue(
            datasourceService.createDatasource(
                organization,
                datasource.getName(),
                "",
                null,
                null),
            HttpStatus.BAD_REQUEST,
            "Failed to handle POST datasource request - Attempt to create datasource resulted"
                + " in an empty result");

    final var response = ResponseFactory.createDatasource(createdDatasource);

    final var headers =
        LinkHeaderUtil.createHeadersWithLocation(
            createdDatasource.getId().toString(), new Class<?>[]{Datasource.class}, datasource);

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

  @Override
  @CrossOrigin(origins = "*")
  public ResponseEntity<List<Datasource>> getDatasources(Integer offset, Integer limit) {
    final var pageable = OffsetBasedPageRequest.of(offset, limit);
    final var datasources = datasourceService.getDatasources(pageable);

    final var response =
        datasources.stream()
            .map(
                ResponseFactory::createDatasource)
            .toList();

    final var headers =
        LinkHeaderUtil.createHeadersWithPagination(
            datasources, 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
  @CrossOrigin(origins = "*")
  public ResponseEntity<Datasource> getDatasource(UUID datasourceId) {
    final var datasource =
        throwApiExceptionOnAbsentValue(
            datasourceService.getDatasourceById(datasourceId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle GET datasource - Could not find datasource with id: %s",
                datasourceId));

    final var response = ResponseFactory.createDatasource(datasource);

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

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

  @Override
  public ResponseEntity<Datasource> patchDatasource(UUID datasourceId, DatasourcePatch datasourcePatch) {
    final var datasource =
        throwApiExceptionOnAbsentValue(
            datasourceService.getDatasourceById(datasourceId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle PATCH datasource - Could not find datasource with id: %s",
                datasourceId));

    final var assigned = assignNonNull(datasourcePatch, datasource, List.of(DELETED));

    final var updated = datasourceService.update(assigned);

    final var response = ResponseFactory.createDatasource(updated);

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

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

  @Override
  public ResponseEntity<byte[]> getDatasourceUserData(UUID datasourceId, String userId) {
    final var datasource =
        throwApiExceptionOnAbsentValue(
            datasourceService.getDatasourceById(datasourceId),
            HttpStatus.NOT_FOUND,
            getDatasourceNotFoundMessage(datasourceId));

    final var dataWrapper =
        dataService.retrieveData(datasource, userId, request.getHeader(HttpHeaders.ACCEPT));

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

    return ResponseEntity.status(dataWrapper.status())
        .contentType(dataWrapper.contentType())
        .headers(headers)
        .body(dataWrapper.body());
  }

  @Override
  public ResponseEntity<DatasourceConfig> getDatasourceConfig(UUID datasourceId) {
    final var datasource =
        throwApiExceptionOnAbsentValue(
            datasourceService.getDatasourceById(datasourceId),
            HttpStatus.NOT_FOUND,
            getDatasourceNotFoundMessage(datasourceId));

    final var responseBody =
        ResponseFactory.createDatasourceConfig(datasource);

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

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

  @Override
  public ResponseEntity<DatasourceConfig> patchDatasourceConfig(
      UUID datasourceId, DatasourceConfigPatch datasourceConfigPatch) {
    final var datasource =
        throwApiExceptionOnAbsentValue(
            datasourceService.getDatasourceById(datasourceId),
            HttpStatus.NOT_FOUND,
            getDatasourceNotFoundMessage(datasourceId));

    final var assigned = assignNonNull(datasourceConfigPatch, datasource, List.of(DELETED));

    final var updated = datasourceService.update(assigned);

    final var responseBody =
        ResponseFactory.createDatasourceConfig(updated);

    responseBody.setHeaders(updated.getHeaders());

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

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

  @Override
  public ResponseEntity<DatasourceFaq> postDatasourceFaq(
      UUID datasourceId, DatasourceFaq datasourceFaq) {
    final var datasource =
        throwApiExceptionOnAbsentValue(
            datasourceService.getDatasourceById(datasourceId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle POST datasource faq request - Could not find datasource with id: %s",
                datasourceId));

    final var faqs = datasource.getFaqEntities();

    final var faq =
        faqService.createFaq(
            datasourceFaq.getLang(),
            datasourceFaq.getTitle(),
            datasourceFaq.getContent());
    faqs.add(faq);
    datasourceService.save(datasource);

    final var response =
        ResponseFactory.createDatasourceFaq(datasourceId, faq);

    final var headers =
        LinkHeaderUtil.createHeadersWithLocation(
            faq.getId().toString(), new Class<?>[]{UUID.class, DatasourceFaq.class}, datasourceId, datasourceFaq);

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

  @Override
  @CrossOrigin(origins = "*")
  public ResponseEntity<List<DatasourceFaq>> getDatasourceFaqs(
      UUID datasourceId, String acceptLanguage, Integer offset, Integer limit) {
    final var datasource =
        throwApiExceptionOnAbsentValue(
            datasourceService.getDatasourceById(datasourceId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle GET datasource faqs request - Could not find datasource with id: %s",
                datasourceId));

    final var pageable = OffsetBasedPageRequest.of(offset, limit);
    final var faqs = faqService.getDatasourceFaqs(datasource.getId(), acceptLanguage, pageable);

    final var response =
        faqs.stream()
            .map(
                faq ->
                    ResponseFactory.createDatasourceFaq(datasourceId, faq))
            .toList();

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

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

  @Override
  @CrossOrigin(origins = "*")
  public ResponseEntity<DatasourceFaq> getDatasourceFaq(UUID datasourceId, UUID faqId) {
    final var faq =
        throwApiExceptionOnAbsentValue(
            faqService.getFaqById(faqId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle GET datasource faq request - Could not find faq with id: %s",
                faqId));

    final var response =
        ResponseFactory.createDatasourceFaq(datasourceId, faq);

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

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

  @Override
  public ResponseEntity<DatasourceFaq> patchDatasourceFaq(
      UUID datasourceId, UUID faqId, DatasourceFaqPatch datasourceFaqPatch) {

    final var faq =
        throwApiExceptionOnAbsentValue(
            faqService.getFaqById(faqId),
            HttpStatus.NOT_FOUND,
            String.format(
                "Failed to handle PATCH datasource faq request - Could not find faq with id:"
                    + " %s",
                faqId));

    final var patchedFaq = assignNonNull(datasourceFaqPatch, faq, List.of(DELETED));

    final var updated = faqService.update(patchedFaq);

    final var response =
        ResponseFactory.createDatasourceFaq(datasourceId, updated);

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

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