CitationController.java

/*
 * Copyright 2024 Global Crop Diversity Trust
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.genesys.server.api.v2;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;

import org.apache.commons.lang3.ArrayUtils;
import org.genesys.server.api.ApiBaseController;
import org.genesys.server.api.FilteredPage;
import org.genesys.server.api.Pagination;
import org.genesys.server.api.v2.facade.CitationApiService;
import org.genesys.server.api.v2.model.bib.CitationDTO;
import org.genesys.server.exception.InvalidApiUsageException;
import org.genesys.server.exception.SearchException;
import org.genesys.server.model.bib.Citation;
import org.genesys.server.model.bib.QCitation;
import org.genesys.server.model.filters.CitationFilter;
import org.genesys.server.service.PartnerService;
import org.genesys.server.service.worker.bib.CitationRisConverter;
import org.genesys.server.service.worker.bib.CitationStyle;
import org.genesys.server.service.worker.dupe.DuplicateFinder;
import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
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 com.querydsl.core.types.OrderSpecifier;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

import javax.validation.Valid;

@RestController("citationApi2")
@PreAuthorize("isAuthenticated()")
@RequestMapping(CitationController.CONTROLLER_URL)
@Tag(name = "Citation")
public class CitationController extends FilteredCRUDController<CitationDTO, Citation, CitationApiService, CitationFilter> {

	/** The Constant CONTROLLER_URL. */
	public static final String CONTROLLER_URL = ApiBaseController.APIv2_BASE + "/citation";

	@Autowired
	CitationStyle citationStyle;

	@Autowired
	private CitationRisConverter risConverter;

	@Autowired
	private PartnerService partnerService;

	@Override
	protected OrderSpecifier<?>[] defaultSort() {
		return new OrderSpecifier[] { QCitation.citation.lastModifiedDate.desc() };
	}

	@Override
	@Operation(hidden = true)
	public CitationDTO get(long id) {
		throw new InvalidApiUsageException("Not applicable");
	}

	@Operation(description = "Get record by UUID", summary = "Get")
	@GetMapping("/{uuid:\\w{8}\\-\\w{4}\\-.{22}}")
	public CitationDTO get(@PathVariable(value = "uuid") UUID uuid) {
		return serviceFacade.get(uuid);
	}

	@Override
	@Operation(hidden = true)
	public CitationDTO remove(@PathVariable("id") final long id) {
		throw new InvalidApiUsageException("Not applicable");
	}

	@Operation(description = "Delete record by UUID", summary = "Delete citation")
	@DeleteMapping("/{uuid:\\w{8}\\-\\w{4}\\-.{22}}")
	public CitationDTO remove(@PathVariable(value = "uuid") UUID uuid) {
		return serviceFacade.remove(serviceFacade.get(uuid));
	}

	@Operation(description = "List records I can access", summary = "List only my records")
	@PostMapping(value = "/list-mine", produces = MediaType.APPLICATION_JSON_VALUE)
	public FilteredPage<CitationDTO, CitationFilter> listMyCitations(@RequestParam(name = "f", required = false) String filterCode, @ParameterObject final Pagination page,
		@RequestBody(required = false) CitationFilter filter) throws IOException, SearchException {
		var filterInfo = processFilter(filterCode, filter, filterType);
		Pageable pageable = ArrayUtils.isEmpty(page.getS()) ? page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, defaultSort()) : page.toPageRequest(MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE);
		return new FilteredPage<>(filterInfo.filterCode, filterInfo.filter, serviceFacade.listMyCitations(filterInfo.filter, pageable));
	}

	@Operation(description = "Find similar citations", summary = "Get similar")
	@PostMapping(value = "/similar", produces = MediaType.APPLICATION_JSON_VALUE)
	public List<DuplicateFinder.Hit<CitationDTO>> findSimilar(@RequestBody CitationDTO citation) {
		return serviceFacade.findSimilar(citation, null);
	}

	/**
	 * Generate citation texts in different styles
	 *
	 * @param citation thing to cite
	 * @return Map of citations by style
	 */
	@Operation(description = "Generate citations in all available styles", summary = "Generate citation")
	@PostMapping(value = "/cite", produces = MediaType.APPLICATION_JSON_VALUE)
	public Map<String, String> cite(@RequestBody CitationDTO citation) {
		return Map.of("apa", citationStyle.toAPA(citation));
	}

	@Operation(description = "Read citation data from RIS format", summary = "Parse RIS")
	@PostMapping(value = "/parse", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.TEXT_PLAIN_VALUE)
	public List<CitationDTO> parseCitations(@RequestBody String input) throws IOException {
		return risConverter.readCitations(new StringReader(input)).collect(Collectors.toList());
	}

	@Operation(description = "Export citation data inf RIS format", summary = "Export as RIS")
	@PostMapping(value = "/export", produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
	public String exportCitations(@RequestBody CitationFilter filter) throws IOException, SearchException {
		var citations = serviceFacade.listMyCitations(filter, Pageable.unpaged()).getContent();
		var writer = new StringWriter();
		for (CitationDTO citation : citations) {
			risConverter.writeCitation(citation, writer);
		}
		return writer.toString();
	}

	@PostMapping(value = "/approve", produces = MediaType.APPLICATION_JSON_VALUE)
	public CitationDTO approveCitation(@RequestBody @Valid EntityUuidAndVersion ref) {
		return serviceFacade.get(serviceFacade.approveCitation(ref));
	}

	@PostMapping(value = "/reject", produces = MediaType.APPLICATION_JSON_VALUE)
	public CitationDTO rejectCitation(@RequestBody @Valid EntityUuidAndVersion ref) {
		return serviceFacade.get(serviceFacade.rejectCitation(ref));
	}

	@PostMapping(value = "/for-review", produces = MediaType.APPLICATION_JSON_VALUE)
	public CitationDTO reviewCitation(@RequestBody @Valid EntityUuidAndVersion ref) {
		return serviceFacade.get(serviceFacade.reviewCitation(ref));
	}

	@Operation(description = "Get all collections by owner", summary = "Get collections by owner")
	@GetMapping("/owner/{uuid:\\w{8}\\-\\w{4}\\-.{22}}/collections")
	public List<String> getCollections(@PathVariable(value = "uuid") UUID owner) {
		return serviceFacade.getCollections(partnerService.get(owner));
	}

	@Operation(description = "Get all published collections by filter", summary = "Get published collections by filter")
	@PostMapping("/collections")
	public List<String> getCollections(@RequestBody(required = false) CitationFilter filter) {
		return serviceFacade.getCollections(filter);
	}
}