Commit 850ce0d6 authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '337-cms-controller' into 'master'

Resolve "CMS Controller"

Closes #337

See merge request genesys-pgr/genesys-server!237
parents 5a6d14b2 60bb96fd
/*
* Copyright 2018 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.genesys2.server.api.v1;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import io.swagger.annotations.Api;
import org.genesys.catalog.api.FilteredPage;
import org.genesys.catalog.service.ShortFilterService;
import org.genesys2.server.api.ApiBaseController;
import org.genesys2.server.exception.NotFoundElement;
import org.genesys2.server.model.impl.ActivityPost;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.Menu;
import org.genesys2.server.model.impl.MenuItem;
import org.genesys2.server.service.CRMException;
import org.genesys2.server.service.ContentService;
import org.genesys2.server.service.filter.ArticleFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* The Class CMSController.
*
* @author Maxym Borodenko
*/
@RestController("cmsApi1")
@PreAuthorize("isAuthenticated()")
@RequestMapping(CMSController.API_BASE)
@Api(tags = { "cms" })
public class CMSController {
/** The Constant API_BASE. */
public static final String API_BASE = ApiBaseController.APIv1_BASE + "/cms";
/** The Constant LOG. */
private static final Logger LOG = LoggerFactory.getLogger(CMSController.class);
@Autowired
private ContentService contentService;
/** The short filter service. */
@Autowired
private ShortFilterService shortFilterService;
/**
* List of last 6 ActivityPost.
*/
@RequestMapping(value = "/last-news", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public List<ActivityPost> getLastNews() {
return contentService.lastNews();
}
/**
* All activity posts.
*
* @param page the page
* @return the page
*/
@RequestMapping(value = "/all-news", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public Page<ActivityPost> getAllNews(@RequestParam(name = "p", required = false, defaultValue = "1") final int page) {
return contentService.allNews(page);
}
/**
* Gets the article
*
* @param slug the slug
* @param className the className of article
* @param targetId the targetId
* @param language the language
* @param useDefault the useDefault
* @return the article
*/
@RequestMapping(value = "/{slug:.+}/{clazz}/{targetId:\\d+}/{language}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public Article getArticle(@PathVariable(value = "clazz") final String className, @PathVariable("targetId") final long targetId,
@PathVariable(value = "slug") final String slug, @PathVariable(value = "language") final String language,
@RequestParam(name = "useDefault", required = false, defaultValue = "false") final boolean useDefault) throws ClassNotFoundException {
final Article article = contentService.getArticle(Class.forName(className), targetId, slug, new Locale(language), useDefault);
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 = "/article/{slug:.+}/{language}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public Article getArticleBySlugAndLang(@PathVariable(value = "slug") final String slug, @PathVariable(value = "language") final String language) {
final Article article = contentService.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
* @param useDefault the useDefault
* @return the global article
*/
@RequestMapping(value = "/global-article/{slug:.+}/{language}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public Article getGlobalArticle(@PathVariable(value = "slug") final String slug, @PathVariable(value = "language") final String language,
@RequestParam(name = "useDefault", required = false, defaultValue = "true") final boolean useDefault) {
final Article article = contentService.getGlobalArticle(slug, new Locale(language), useDefault);
if (article == null) {
throw new NotFoundElement("Article not found.");
}
return article;
}
/**
* List articles by filterCode or filter
*
* @param page the page
* @param pageSize the page size
* @param direction the direction
* @param sort the sort
* @param filterCode short filter code
* @param filter the article filter
* @return the page
* @throws IOException
*/
@RequestMapping(value = "/list", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
public FilteredPage<Article> listArticles(@RequestParam(name = "p", required = false, defaultValue = "0") final int page,
@RequestParam(name = "l", required = false, defaultValue = "50") final int pageSize,
@RequestParam(name = "d", required = false, defaultValue = "ASC") final Sort.Direction direction,
@RequestParam(name = "s", required = false, defaultValue = "id") final String[] sort,
@RequestParam(name = "f", required = false) String filterCode,
@RequestBody(required = false) ArticleFilter filter) throws IOException {
if (filterCode != null) {
filter = shortFilterService.filterByCode(filterCode, ArticleFilter.class);
} else {
filterCode = shortFilterService.getCode(filter);
}
return new FilteredPage<>(filterCode, filter, contentService.listArticles(filter, new PageRequest(page, Integer.min(pageSize, 100), direction, sort)));
}
/**
* Create the activity post.
*
* @param post the activity post
* @return the activity post
*/
@RequestMapping(value = "/create-post", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
public ActivityPost createActivityPost(@RequestBody final ActivityPost post) {
return contentService.createActivityPost(post.getTitle(), post.getTitle());
}
/**
* Update the article.
*
* @param article the article
* @param className the className of article
* @return the updated article
*/
@RequestMapping(value = "/update-article", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
public Article updateArticle(@RequestParam(name = "clazz", required = false) String className, @RequestBody final Article article) throws NotFoundElement, ClassNotFoundException, CRMException {
if (className == null) {
return contentService.updateArticle(article.getId(), article.getSlug(), article.getTitle(), article.getBody(), article.getSummary());
} else {
return contentService.updateArticle(Class.forName(className), article.getTargetId(), article.getSlug(), article.getTitle(), article.getBody(), article.getSummary(), new Locale(article.getLang()));
}
}
/**
* Create the article.
*
* @param article the article
* @return the updated article
*/
@RequestMapping(value = "/create-article", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
public Article createGlobalArticle(@RequestBody final Article article) throws CRMException {
return contentService.createGlobalArticle(article.getSlug(), new Locale(article.getLang()), article.getTitle(), article.getBody(), article.getSummary(), article.isTemplate());
}
/**
* Update the global article.
*
* @param article the article
* @return the updated global article
*/
@RequestMapping(value = "/update-global-article", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
public Article updateGlobalArticle(@RequestBody final Article article) throws CRMException {
return contentService.updateGlobalArticle(article.getSlug(), new Locale(article.getLang()), article.getTitle(), article.getBody(), article.getSummary());
}
/**
* Gets the activity post.
*
* @param id the id of activity post
* @return the activity post
*/
@RequestMapping(value = "/activity-post/{id:\\d+}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public ActivityPost getActivityPost(@PathVariable("id") final long id) {
final ActivityPost activityPost = contentService.getActivityPost(id);
if (activityPost == null) {
throw new NotFoundElement("Activity post not found.");
}
return activityPost;
}
/**
* Update the activity post.
*
* @param post the activity post
* @return the updated activity post
*/
@RequestMapping(value = "/update-activity-post", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
public ActivityPost updateActivityPost(@RequestBody final ActivityPost post) throws NotFoundElement {
return contentService.updateActivityPost(post.getId(), post.getTitle(), post.getBody());
}
/**
* Delete the activity post.
*
* @param id the id of activity post
* @return true if deleted
*/
@RequestMapping(value = "delete-post/{id:\\d+}", method = { RequestMethod.DELETE, RequestMethod.POST }, produces = { MediaType.APPLICATION_JSON_VALUE })
public Boolean deleteActivityPost(@PathVariable("id") final long id) throws NotFoundElement {
contentService.deleteActivityPost(id);
return true;
}
/**
* Gets the default locale.
*
* @return the default locale
*/
@RequestMapping(value = "/locale", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public Locale getDefaultLocale() {
return contentService.getDefaultLocale();
}
/**
* Process template.
*
* @param body the body
* @param root the map
* @return the processed template in string
*/
@RequestMapping(value = "/process-template", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
public String processTemplate(@RequestParam(name = "body", required = true) final String body, @RequestBody final Map<String, Object> root) {
return contentService.processTemplate(body, root);
}
/**
* Gets the CMS menu.
*
* @param menuKey the key of menu
*
* @return the CMS menu
*/
@RequestMapping(value = "/menu/{menuKey}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public Menu getMenu(@PathVariable(value = "menuKey") final String menuKey) {
final Menu menu = contentService.getMenu(menuKey);
if (menu == null) {
throw new NotFoundElement("Menu not found by key=" + menuKey);
}
return menu;
}
/**
* Update the CMS menu.
*
* @param menuKey the key of menu
* @param items the menu items
* @return the updated CMS menu
*/
@RequestMapping(value = "/update-menu/{menuKey}", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
public Menu updateMenu(@PathVariable(value = "menuKey") final String menuKey, @RequestBody final List<MenuItem> items) {
return contentService.updateMenu(menuKey, items);
}
/**
* Ensure menu item.
*
* @param menuKey the key of menu
* @param url the url
* @param text the text
* @return the menu item
*/
@RequestMapping(value = "/ensure-menu-item", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
public MenuItem ensureMenuItem(@RequestParam(name = "menuKey", required = true) final String menuKey,
@RequestParam(name = "url", required = true) final String url,
@RequestParam(name = "text", required = true) final String text) {
return contentService.ensureMenuItem(menuKey, url, text);
}
}
\ No newline at end of file
......@@ -107,11 +107,11 @@ public class InstituteController {
@PostMapping(value = "/list", produces = { MediaType.APPLICATION_JSON_VALUE })
@JsonView({ JsonViews.Public.class })
public FilteredPage<FaoInstitute> list(@RequestParam(name = "p", required = false, defaultValue = "0") final int page,
@RequestParam(name = "l", required = false, defaultValue = "50") final int pageSize,
@RequestParam(name = "d", required = false, defaultValue = "ASC") final Sort.Direction direction,
@RequestParam(name = "s", required = false, defaultValue = "id") final String[] sort,
@RequestParam(name = "f", required = false) String filterCode,
@RequestBody(required = false) InstituteFilter filter) throws IOException {
@RequestParam(name = "l", required = false, defaultValue = "50") final int pageSize,
@RequestParam(name = "d", required = false, defaultValue = "ASC") final Sort.Direction direction,
@RequestParam(name = "s", required = false, defaultValue = "id") final String[] sort,
@RequestParam(name = "f", required = false) String filterCode,
@RequestBody(required = false) InstituteFilter filter) throws IOException {
if (filterCode != null) {
filter = shortFilterService.filterByCode(filterCode, InstituteFilter.class);
......
......@@ -26,6 +26,7 @@ import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.genesys.blocks.model.BasicModel;
/**
......@@ -42,6 +43,7 @@ public class Menu extends BasicModel {
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "menu")
@OrderBy("orderIndex")
@JsonIgnoreProperties(value = { "menu" })
private List<MenuItem> items = new ArrayList<MenuItem>();
public String getKey() {
......
......@@ -22,11 +22,12 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface ArticleRepository extends JpaRepository<Article, Long> {
public interface ArticleRepository extends JpaRepository<Article, Long>, QueryDslPredicateExecutor<Article> {
Article findByClassPkAndTargetIdAndSlugAndLang(ClassPK classPk, Long id, String slug, String lang);
......
/*
* Copyright 2017 Global Crop Diversity Trust
* Copyright 2018 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.
......@@ -23,10 +23,12 @@ import java.util.Map;
import org.genesys.blocks.model.ClassPK;
import org.genesys.blocks.model.EntityId;
import org.genesys2.server.exception.NotFoundElement;
import org.genesys2.server.model.impl.ActivityPost;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.Menu;
import org.genesys2.server.model.impl.MenuItem;
import org.genesys2.server.service.filter.ArticleFilter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
......@@ -80,11 +82,14 @@ public interface ContentService {
* Load article with {@link ClassPK} of clazz with specified id and the "slug"
* for the locale
*
* @param slug
* @param locale
* @param slug the slug
* @param locale the locale
* @param targetId the target id
* @param useDefault the useDefault
* @param clazz the clazz
* @return
*/
Article getArticle(Class<?> clazz, Long id, String slug, Locale locale, boolean useDefault);
Article getArticle(Class<?> clazz, Long targetId, String slug, Locale locale, boolean useDefault);
Article getArticle(EntityId entity, String slug, Locale locale);
......@@ -114,6 +119,8 @@ public interface ContentService {
Page<Article> listArticles(Pageable pageable);
Page<Article> listArticles(ArticleFilter filter, Pageable pageable);
Page<Article> listArticlesByLang(String lang, Pageable pageable);
// void save(Iterable<Article> articles);
......@@ -131,7 +138,7 @@ public interface ContentService {
Article updateArticle(Class<?> clazz, Long id, String slug, String title, String body, String summary, Locale locale) throws CRMException;
Article updateArticle(long id, String slug, String title, String body, String summary);
Article updateArticle(long id, String slug, String title, String body, String summary) throws NotFoundElement;
Article createGlobalArticle(String slug, Locale locale, String title, String body, String summary, boolean isTemplate) throws CRMException;
......@@ -139,7 +146,7 @@ public interface ContentService {
ActivityPost getActivityPost(long id);
ActivityPost updateActivityPost(long id, String title, String body);
ActivityPost updateActivityPost(long id, String title, String body) throws NotFoundElement;
void deleteActivityPost(long id);
......
/*
* Copyright 2018 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.genesys2.server.service.filter;
import static org.genesys2.server.model.impl.QArticle.article;
import java.util.Set;
import com.hazelcast.util.CollectionUtil;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Predicate;
import org.genesys.blocks.model.filters.BasicModelFilter;
import org.genesys.blocks.model.filters.StringFilter;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.QArticle;
/**
* Filters for {@link Article}
*/
public class ArticleFilter extends BasicModelFilter<ArticleFilter, Article> {
/** The language. */
public Set<String> lang;
/** The slug. */
public Set<String> slug;
/** The title. */
public StringFilter title;
/** The template. */
public Boolean template;
@Override
public Predicate buildQuery() {
return buildQuery(article);
}
/**
* Builds the query.
*
* @return the predicate
*/
public Predicate buildQuery(QArticle article) {
final BooleanBuilder and = new BooleanBuilder();
super.buildQuery(article, article._super._super, and);
if (CollectionUtil.isNotEmpty(lang)) {
and.and(article.lang.in(lang));
}
if (CollectionUtil.isNotEmpty(slug)) {
and.and(article.slug.in(slug));
}
if (title != null) {
and.and(title.buildQuery(article.title));
}
if (template != null) {
and.and(article.template.eq(template));
}
return and;
}
}
/*
* Copyright 2017 Global Crop Diversity Trust
* Copyright 2018 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.
......@@ -29,6 +29,7 @@ import org.apache.velocity.app.VelocityEngine;
import org.genesys.blocks.auditlog.service.ClassPKService;
import org.genesys.blocks.model.ClassPK;
import org.genesys.blocks.model.EntityId;
import org.genesys2.server.exception.NotFoundElement;
import org.genesys2.server.model.impl.ActivityPost;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.Menu;
......@@ -40,6 +41,7 @@ import org.genesys2.server.persistence.MenuRepository;
import org.genesys2.server.service.CRMException;
import org.genesys2.server.service.ContentService;
import org.genesys2.server.service.HtmlSanitizer;
import org.genesys2.server.service.filter.ArticleFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -106,6 +108,11 @@ public class ContentServiceImpl implements ContentService {
return articleRepository.findAll(pageable);
}
@Override
public Page<Article> listArticles(final ArticleFilter filter, final Pageable pageable) {
return articleRepository.findAll(filter.buildQuery(), pageable);
}
@Override
public Page<Article> listArticlesByLang(String lang, Pageable pageable) {
return articleRepository.getAllByLang(lang, pageable);
......@@ -144,11 +151,11 @@ public class ContentServiceImpl implements ContentService {
}
@Override
@Cacheable(value = "contentcache", key = "'article-' + #clazz.name + '-' + #id + '-' + #slug + '-' + #locale.language + '-' + #useDefault")
public Article getArticle(Class<?> clazz, Long id, String slug, Locale locale, boolean useDefault) {
Article article = articleRepository.findByClassPkAndTargetIdAndSlugAndLang(getClassPk(clazz), id, slug, locale.getLanguage());
@Cacheable(value = "contentcache", key = "'article-' + #clazz.name + '-' + #targetId + '-' + #slug + '-' + #locale.language + '-' + #useDefault")
public Article getArticle(Class<?> clazz, Long targetId, String slug, Locale locale, boolean useDefault) {
Article article = articleRepository.findByClassPkAndTargetIdAndSlugAndLang(getClassPk(clazz), targetId, slug, locale.getLanguage());
if (article == null && useDefault && !locale.getLanguage().equals(getDefaultLocale().getLanguage())) {
article = articleRepository.findByClassPkAndTargetIdAndSlugAndLang(getClassPk(clazz), id, slug, getDefaultLocale().getLanguage());
article = articleRepository.findByClassPkAndTargetIdAndSlugAndLang(getClassPk(clazz), targetId, slug, getDefaultLocale().getLanguage());
}
return article;
}
......@@ -157,9 +164,13 @@ public class ContentServiceImpl implements ContentService {
@Transactional(readOnly = false)
@PreAuthorize("hasRole('ADMINISTRATOR') or hasRole('CONTENTMANAGER')")
@CacheEvict(value = "contentcache", allEntries = true)
public Article updateArticle(long id, String slug, String title, String body, String summary) {
public Article updateArticle(long id, String slug, String title, String body, String summary) throws NotFoundElement {
final Article article = articleRepository.findOne(id);
if (article == null) {
throw n