ArticleController.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.impl;

import io.swagger.v3.oas.annotations.tags.Tag;
import org.genesys.blocks.auditlog.service.ClassPKService;
import org.genesys.server.api.ApiBaseController;
import org.genesys.server.api.v2.FilteredCRUDController;
import org.genesys.server.api.v2.TranslatedCRUDController;
import org.genesys.server.api.v2.facade.ArticleApiService;
import org.genesys.server.api.v2.facade.ArticleLangApiService;
import org.genesys.server.api.v2.mapper.MapstructMapper;
import org.genesys.server.api.v2.model.impl.ArticleDTO;
import org.genesys.server.api.v2.model.impl.ArticleLangDTO;
import org.genesys.server.api.v2.model.impl.ClassPKInfo;
import org.genesys.server.api.v2.model.impl.RepositoryFileDTO;
import org.genesys.server.api.v2.model.impl.TranslatedArticleDTO;
import org.genesys.server.exception.NotFoundElement;
import org.genesys.server.model.impl.Article;
import org.genesys.server.model.impl.ArticleLang;
import org.genesys.server.service.filter.ArticleFilter;
import org.genesys.server.service.filter.ArticleLangFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
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.RequestMethod;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

@RestController("articleApi2")
@RequestMapping(ArticleController.CONTROLLER_URL)
@Tag(name = "Article")
public class ArticleController extends TranslatedCRUDController<ArticleDTO, TranslatedArticleDTO,
	ArticleLangDTO, Article, ArticleLang, ArticleApiService, ArticleFilter> {

	/** The Constant CONTROLLER_URL. */
	public static final String CONTROLLER_URL = ApiBaseController.APIv2_BASE + "/article";
	
	@Autowired
	private ClassPKService classPKService;
	
	@Autowired
	private MapstructMapper mapstructMapper;

	@RestController("articleLangApi2")
	@RequestMapping(ArticleController.ArticleLangController.API_URL)
	@PreAuthorize("isAuthenticated()")
	@Tag(name = "Article")
	public static class ArticleLangController extends FilteredCRUDController<ArticleLangDTO, ArticleLang, ArticleLangApiService, ArticleLangFilter> {
		/** The Constant API_URL. */
		public static final String API_URL = ArticleController.CONTROLLER_URL + "/lang";
	}
	
//	@Autowired(required = false)
//	private TransifexService transifexService;
//
//	@Autowired
//	private ObjectMapper mapper;
//
//	@Resource
//	private Set<String> supportedLocales;
//
//	private static final String defaultLanguage = "en";

	/**
	 * Gets the article
	 *
	 * @param slug the slug
	 * @param className the className of article
	 * @param targetId the targetId
	 * @param language the language
	 * @return the article
	 */
	@RequestMapping(value = "/{slug:.+}/{clazz}/{targetId:\\d+}/{language}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
	public TranslatedArticleDTO getArticle(@PathVariable(value = "clazz") final String className, @PathVariable("targetId") final long targetId,
		@PathVariable(value = "slug") final String slug, @PathVariable(value = "language") final String language) throws ClassNotFoundException {

		final TranslatedArticleDTO article = translatedApiService.getArticle(Class.forName(className), targetId, slug, new Locale(language));
		if (article == null) {
			throw new NotFoundElement("Article not found.");
		}
		return article;
	}

	/**
	 * Gets the article
	 *
	 * @param slug the slug
	 * @param language the language
	 * @return the article
	 */
	@RequestMapping(value = "/{slug:.+}/{language}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
	public TranslatedArticleDTO getArticleBySlugAndLang(@PathVariable(value = "slug") final String slug, @PathVariable(value = "language") final String language) {
		final TranslatedArticleDTO article = translatedApiService.getArticleBySlugAndLang(slug, language);
		if (article == null) {
			throw new NotFoundElement("Article not found.");
		}
		return article;
	}

	/**
	 * Gets the global article
	 *
	 * @param slug the slug
	 * @param language the language
	 * @return the global article
	 */
	@RequestMapping(value = "/global-article/{slug:.+}/{language}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
	public TranslatedArticleDTO getGlobalArticle(@PathVariable(value = "slug") final String slug, @PathVariable(value = "language") final String language) {

		final TranslatedArticleDTO article = translatedApiService.getGlobalArticle(slug, new Locale(language));
		if (article == null) {
			throw new NotFoundElement("Article not found.");
		}
		return article;
	}

	/**
	 * Delete articles.
	 *
	 * @param entities the list of IDs and versions of articles
	 * @return list of operation responses
	 */
	@PostMapping(value = "/delete-articles", produces = { MediaType.APPLICATION_JSON_VALUE })
	public List<OpResponse<Boolean>> deleteArticles(@RequestBody final List<IDandVersion> entities) {
		return entities.stream().map(entity -> {
			try {
				translatedApiService.deleteArticle(entity.id, entity.version);
				return new OpResponse<>(true);
			} catch (Throwable e) {
				LOG.info("Error deleting article by id={}: {}", entity.id, e.getMessage());
				return new OpResponse<Boolean>(e, false);
			}
		}).collect(Collectors.toList());
	}

	/**
	 * Uploads a file to Article folder
	 *
	 * @param id the id of Article
	 * @param file the file to upload
	 * @return persisted file
	 * @throws Exception
	 */
	@PostMapping(value = "/{id:\\d+}/upload", produces = { MediaType.APPLICATION_JSON_VALUE })
	public RepositoryFileDTO uploadArticleFile(@PathVariable("id") final long id, @RequestPart(name = "file") final MultipartFile file)
		throws Exception {

		return translatedApiService.uploadArticleFile(translatedApiService.get(id), file);
	}

	@GetMapping(value = "/classPK/{className}", produces = { MediaType.APPLICATION_JSON_VALUE })
	public ClassPKInfo getClassPK(@PathVariable("className") final String className) throws Exception {
		return mapstructMapper.mapInfo(classPKService.getClassPk(Class.forName(className)));
	}

//	@PostMapping(value = "/transifex")
//	@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CONTENTMANAGER')")
//	public TranslatedArticleDTO postToTransifex(@RequestParam("slug") String slug,
//		@RequestParam(value = "targetId", required = false) Long targetId, @RequestParam("classPkShortName") String classPkShortName) throws IOException {
//		if (transifexService == null) {
//			throw new NotFoundElement("translationService not enabled");
//		}
//
//		TranslatedArticleDTO article;
//		String resourceName;
//		if (targetId != null) {
//			article = translatedApiService.getArticleBySlugLangTargetIdClassPk(slug, LocaleContextHolder.getLocale().toLanguageTag(), targetId, classPkShortName);
//			resourceName = "article-" + slug + "-" + classPkShortName + "-" + targetId;
//		} else {
//			article = translatedApiService.getGlobalArticle(slug, LocaleContextHolder.getLocale(), true);
//			resourceName = "article-" + slug;
//		}
//		
//		String title;
//		String summary;
//		String body;
//		var translation = article.getTranslation();
//		title = translation != null ? translation.getTitle() : article.getEntity().getOriginalTitle();
//		summary = translation != null ? translation.getSummary() : article.getEntity().getSummary();
//		body = translation != null ? translation.getBody() : article.getEntity().getBody();
//
//		String messageBody = String.format("<div class=\"summary\">%s</div><div class=\"body\">%s</div>", summary, body);
//
//		if (transifexService.resourceExists(resourceName)) {
//			transifexService.updateXhtmlResource(resourceName, title, messageBody);
//		} else {
//			transifexService.createXhtmlResource(resourceName, title, messageBody);
//		}
//
//		return article;
//	}

//	@DeleteMapping(value = "/transifex")
//	@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CONTENTMANAGER')")
//	public Boolean deleteFromTransifex(@RequestParam("slug") String slug,
//		@RequestParam(value = "targetId", required = false) Long targetId, @RequestParam("classPkShortName") String classPkShortName) throws TransifexException {
//		if (transifexService == null) {
//			throw new NotFoundElement("translationService not enabled");
//		}
//
//		String resourceName;
//		if (targetId != null) {
//			resourceName = "article-" + slug + "-" + classPkShortName + "-" + targetId;
//		} else {
//			resourceName = "article-" + slug;
//		}
//
//		return transifexService.deleteResource(resourceName);
//	}

//	/**
//	 * Fetch all from Transifex and store
//	 *
//	 * @param slug - article slug
//	 * @return map with results of fetching
//	 * @throws Exception
//	 */
//	@PostMapping(value = "/transifex/fetchAll")
//	@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CONTENTMANAGER')")
//	public Map<String, String> fetchAllFromTransifex(@RequestParam("slug") String slug, @RequestParam(value = "targetId", required = false) Long targetId,
//		@RequestParam(value = "classPkShortName", required = false) String classPkShortName, @RequestParam(name = "template", defaultValue = "false") Boolean template) throws Exception {
//		if (transifexService == null) {
//			throw new NotFoundElement("translationService not enabled");
//		}
//
//		String resourceName;
//		if (targetId != null) {
//			resourceName = "article-" + slug + "-" + classPkShortName + "-" + targetId;
//		} else {
//			resourceName = "article-" + slug;
//		}
//
//		Map<String, String> responses = new HashMap<>(20);
//
//		for (String lang : supportedLocales) {
//			if (defaultLanguage.equalsIgnoreCase(lang)) {
//				continue;
//			}
//
//			Locale locale = Locale.forLanguageTag(lang);
//			LOG.info("Fetching article {} translation for {}", resourceName, locale);
//
//			String translatedResource;
//			try {
//				translatedResource = transifexService.getTranslatedResource(resourceName, locale, TransifexService.TranslationMode.ONLYTRANSLATED);
//			} catch (TransifexException e) {
//				LOG.warn(e.getMessage(), e);
//				if (e.getCause() != null) {
//					responses.put(lang, e.getLocalizedMessage() + ": " + e.getCause().getLocalizedMessage());
//				} else {
//					responses.put(lang, e.getLocalizedMessage());
//				}
//				// throw new Exception(e.getMessage(), e);
//				continue;
//			}
//
//			String title;
//			String body;
//			String summary = null;
//
//			try {
//				JsonNode jsonObject = mapper.readTree(translatedResource);
//				String content = jsonObject.get("content").asText();
//
//				Document doc = Jsoup.parse(content);
//				title = doc.title();
//				if (content.contains("class=\"summary")) {
//					// 1st <div class="summary">...
//					summary = doc.body().child(0).html();
//					// 2nd <div class="body">...
//					body = doc.body().child(1).html();
//				} else {
//					// Old fashioned body-only approach
//					body = doc.body().html();
//				}
//
//				if (targetId != null && StringUtils.isNotBlank(classPkShortName)) {
//					ClassPK classPk = translatedApiService.getClassPk(classPkShortName);
//					translatedApiService.updateArticle(Class.forName(classPk.getClassname()), targetId, slug, title, summary, body, new Locale(lang));
//					responses.put(lang, "article.translations-updated");
//				} else if (targetId == null && StringUtils.isBlank(classPkShortName)) {
//					translatedApiService.updateGlobalArticle(slug, locale, title, summary, body);
//					responses.put(lang, "article.translations-updated");
//				} else {
//					responses.put(lang, "Error updating local content");
//				}
//			} catch (IOException e) {
//				LOG.warn(e.getMessage(), e);
//				responses.put(lang, e.getMessage());
//			}
//		}
//
//		return responses;
//	}
}