org.wallride.service.ArticleService.java Source code

Java tutorial

Introduction

Here is the source code for org.wallride.service.ArticleService.java

Source

/*
 * Copyright 2014 Tagbangers, Inc.
 *
 * 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.wallride.service;

import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.MessageCodesResolver;
import org.wallride.autoconfigure.WallRideCacheConfiguration;
import org.wallride.autoconfigure.WallRideProperties;
import org.wallride.domain.*;
import org.wallride.exception.DuplicateCodeException;
import org.wallride.exception.EmptyCodeException;
import org.wallride.exception.NotNullException;
import org.wallride.exception.ServiceException;
import org.wallride.model.*;
import org.wallride.repository.*;
import org.wallride.support.AuthorizedUser;
import org.wallride.support.CodeFormatter;
import org.wallride.web.controller.admin.article.CustomFieldValueEditForm;

import javax.annotation.Resource;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.text.ParseException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
@Transactional(rollbackFor = Exception.class)
public class ArticleService {

    @Resource
    private BlogService blogService;

    @Resource
    private PostRepository postRepository;

    @Resource
    private ArticleRepository articleRepository;

    @Resource
    private TagRepository tagRepository;

    @Resource
    private MediaRepository mediaRepository;

    @Inject
    private MessageCodesResolver messageCodesResolver;

    @Inject
    private PlatformTransactionManager transactionManager;

    @Inject
    private WallRideProperties wallRideProperties;

    @PersistenceContext
    private EntityManager entityManager;

    private static Logger logger = LoggerFactory.getLogger(ArticleService.class);

    @CacheEvict(value = WallRideCacheConfiguration.ARTICLE_CACHE, allEntries = true)
    public Article createArticle(ArticleCreateRequest request, Post.Status status, AuthorizedUser authorizedUser) {
        LocalDateTime now = LocalDateTime.now();

        String code = request.getCode();
        if (code == null) {
            try {
                code = new CodeFormatter().parse(request.getTitle(), LocaleContextHolder.getLocale());
            } catch (ParseException e) {
                throw new ServiceException(e);
            }
        }
        if (!StringUtils.hasText(code)) {
            if (!status.equals(Post.Status.DRAFT)) {
                throw new EmptyCodeException();
            }
        }

        if (!status.equals(Post.Status.DRAFT)) {
            Post duplicate = postRepository.findOneByCodeAndLanguage(code, request.getLanguage());
            if (duplicate != null) {
                throw new DuplicateCodeException(code);
            }
        }

        Article article = new Article();

        if (!status.equals(Post.Status.DRAFT)) {
            article.setCode(code);
            article.setDraftedCode(null);
        } else {
            article.setCode(null);
            article.setDraftedCode(code);
        }

        Media cover = null;
        if (request.getCoverId() != null) {
            cover = entityManager.getReference(Media.class, request.getCoverId());
        }
        article.setCover(cover);
        article.setTitle(request.getTitle());
        article.setBody(request.getBody());

        article.setAuthor(entityManager.getReference(User.class, authorizedUser.getId()));

        LocalDateTime date = request.getDate();
        if (Post.Status.PUBLISHED.equals(status)) {
            if (date == null) {
                date = now;
            } else if (date.isAfter(now)) {
                status = Post.Status.SCHEDULED;
            }
        }
        article.setDate(date);
        article.setStatus(status);
        article.setLanguage(request.getLanguage());

        article.getCategories().clear();
        for (long categoryId : request.getCategoryIds()) {
            article.getCategories().add(entityManager.getReference(Category.class, categoryId));
        }

        article.getTags().clear();
        Set<String> tagNames = StringUtils.commaDelimitedListToSet(request.getTags());
        if (!CollectionUtils.isEmpty(tagNames)) {
            for (String tagName : tagNames) {
                Tag tag = tagRepository.findOneForUpdateByNameAndLanguage(tagName, request.getLanguage());
                if (tag == null) {
                    tag = new Tag();
                    tag.setName(tagName);
                    tag.setLanguage(request.getLanguage());
                    article.setCreatedAt(now);
                    article.setCreatedBy(authorizedUser.toString());
                    article.setUpdatedAt(now);
                    article.setUpdatedBy(authorizedUser.toString());
                    tag = tagRepository.saveAndFlush(tag);
                }
                article.getTags().add(tag);
            }
        }

        article.getRelatedPosts().clear();
        Set<Post> relatedPosts = new HashSet<>();
        for (long relatedId : request.getRelatedPostIds()) {
            relatedPosts.add(entityManager.getReference(Post.class, relatedId));
        }
        article.setRelatedToPosts(relatedPosts);

        Seo seo = new Seo();
        seo.setTitle(request.getSeoTitle());
        seo.setDescription(request.getSeoDescription());
        seo.setKeywords(request.getSeoKeywords());
        article.setSeo(seo);

        List<Media> medias = new ArrayList<>();
        if (StringUtils.hasText(request.getBody())) {
            //         Blog blog = blogService.getBlogById(Blog.DEFAULT_ID);
            String mediaUrlPrefix = wallRideProperties.getMediaUrlPrefix();
            Pattern mediaUrlPattern = Pattern.compile(String.format("%s([0-9a-zA-Z\\-]+)", mediaUrlPrefix));
            Matcher mediaUrlMatcher = mediaUrlPattern.matcher(request.getBody());
            while (mediaUrlMatcher.find()) {
                Media media = mediaRepository.findOneById(mediaUrlMatcher.group(1));
                medias.add(media);
            }
        }
        article.setMedias(medias);

        article.setCreatedAt(now);
        article.setCreatedBy(authorizedUser.toString());
        article.setUpdatedAt(now);
        article.setUpdatedBy(authorizedUser.toString());

        article.getCustomFieldValues().clear();
        if (!CollectionUtils.isEmpty(request.getCustomFieldValues())) {
            for (CustomFieldValueEditForm valueForm : request.getCustomFieldValues()) {
                CustomFieldValue value = new CustomFieldValue();
                value.setCustomField(entityManager.getReference(CustomField.class, valueForm.getCustomFieldId()));
                value.setPost(article);
                if (valueForm.getFieldType().equals(CustomField.FieldType.CHECKBOX)) {
                    if (!ArrayUtils.isEmpty(valueForm.getTextValues())) {
                        value.setTextValue(String.join(",", valueForm.getTextValues()));
                    } else {
                        value.setTextValue(null);
                    }
                } else {
                    value.setTextValue(valueForm.getTextValue());
                }
                value.setStringValue(valueForm.getStringValue());
                value.setNumberValue(valueForm.getNumberValue());
                value.setDateValue(valueForm.getDateValue());
                value.setDatetimeValue(valueForm.getDatetimeValue());
                if (!value.isEmpty()) {
                    article.getCustomFieldValues().add(value);
                }
            }
        }
        return articleRepository.save(article);
    }

    @CacheEvict(value = WallRideCacheConfiguration.ARTICLE_CACHE, allEntries = true)
    public Article saveArticleAsDraft(ArticleUpdateRequest request, AuthorizedUser authorizedUser) {
        postRepository.lock(request.getId());
        Article article = articleRepository.findOneByIdAndLanguage(request.getId(), request.getLanguage());
        if (!article.getStatus().equals(Post.Status.DRAFT)) {
            Article draft = articleRepository.findOne(ArticleSpecifications.draft(article));
            if (draft == null) {
                ArticleCreateRequest createRequest = new ArticleCreateRequest.Builder().code(request.getCode())
                        .coverId(request.getCoverId()).title(request.getTitle()).body(request.getBody())
                        .authorId(request.getAuthorId()).date(request.getDate())
                        .categoryIds(request.getCategoryIds()).tags(request.getTags())
                        .seoTitle(request.getSeoTitle()).seoDescription(request.getSeoDescription())
                        .seoKeywords(request.getSeoKeywords())
                        .customFieldValues(new ArrayList<>(request.getCustomFieldValues()))
                        .language(request.getLanguage()).build();
                draft = createArticle(createRequest, Post.Status.DRAFT, authorizedUser);
                draft.setDrafted(article);
                return articleRepository.save(draft);
            } else {
                ArticleUpdateRequest updateRequest = new ArticleUpdateRequest.Builder().id(draft.getId())
                        .code(request.getCode()).coverId(request.getCoverId()).title(request.getTitle())
                        .body(request.getBody()).authorId(request.getAuthorId()).date(request.getDate())
                        .categoryIds(request.getCategoryIds()).tags(request.getTags())
                        .seoTitle(request.getSeoTitle()).seoDescription(request.getSeoDescription())
                        .seoKeywords(request.getSeoKeywords()).customFieldValues(request.getCustomFieldValues())
                        .language(request.getLanguage()).build();
                return saveArticle(updateRequest, authorizedUser);
            }
        } else {
            return saveArticle(request, authorizedUser);
        }
    }

    @CacheEvict(value = WallRideCacheConfiguration.ARTICLE_CACHE, allEntries = true)
    public Article saveArticleAsPublished(ArticleUpdateRequest request, AuthorizedUser authorizedUser) {
        postRepository.lock(request.getId());
        Article article = articleRepository.findOneByIdAndLanguage(request.getId(), request.getLanguage());
        publishArticle(article);
        return saveArticle(request, authorizedUser);
    }

    private Article publishArticle(Article article) {
        Article deleteTarget = getDraftById(article.getId());
        if (deleteTarget != null) {
            articleRepository.delete(deleteTarget);
        }
        article.setDrafted(null);
        article.setStatus(Post.Status.PUBLISHED);
        Article published = articleRepository.save(article);
        return published;
    }

    @CacheEvict(value = WallRideCacheConfiguration.ARTICLE_CACHE, allEntries = true)
    public Article saveArticleAsUnpublished(ArticleUpdateRequest request, AuthorizedUser authorizedUser) {
        postRepository.lock(request.getId());
        Article article = articleRepository.findOneByIdAndLanguage(request.getId(), request.getLanguage());
        unpublishArticle(article);
        return saveArticle(request, authorizedUser);
    }

    private Article unpublishArticle(Article article) {
        Article deleteTarget = getDraftById(article.getId());
        if (deleteTarget != null) {
            articleRepository.delete(deleteTarget);
        }
        article.setDrafted(null);
        article.setStatus(Post.Status.DRAFT);
        Article unpublished = articleRepository.save(article);
        return unpublished;
    }

    @CacheEvict(value = WallRideCacheConfiguration.ARTICLE_CACHE, allEntries = true)
    public Article saveArticle(ArticleUpdateRequest request, AuthorizedUser authorizedUser) {
        postRepository.lock(request.getId());
        Article article = articleRepository.findOneByIdAndLanguage(request.getId(), request.getLanguage());
        LocalDateTime now = LocalDateTime.now();

        String code = request.getCode();
        if (code == null) {
            try {
                code = new CodeFormatter().parse(request.getTitle(), LocaleContextHolder.getLocale());
            } catch (ParseException e) {
                throw new ServiceException(e);
            }
        }
        if (!StringUtils.hasText(code)) {
            if (!article.getStatus().equals(Post.Status.DRAFT)) {
                throw new EmptyCodeException();
            }
        }
        if (!article.getStatus().equals(Post.Status.DRAFT)) {
            Post duplicate = postRepository.findOneByCodeAndLanguage(code, request.getLanguage());
            if (duplicate != null && !duplicate.equals(article)) {
                throw new DuplicateCodeException(code);
            }
        }

        if (!article.getStatus().equals(Post.Status.DRAFT)) {
            article.setCode(code);
            article.setDraftedCode(null);
        } else {
            article.setCode(null);
            article.setDraftedCode(code);
        }

        Media cover = null;
        if (request.getCoverId() != null) {
            cover = entityManager.getReference(Media.class, request.getCoverId());
        }
        article.setCover(cover);
        article.setTitle(request.getTitle());
        article.setBody(request.getBody());

        //      User author = null;
        //      if (request.getAuthorId() != null) {
        //         author = entityManager.getReference(User.class, request.getAuthorId());
        //      }
        //      article.setAuthor(author);

        LocalDateTime date = request.getDate();
        if (!Post.Status.DRAFT.equals(article.getStatus())) {
            if (date == null) {
                date = now;
            } else if (date.isAfter(now)) {
                article.setStatus(Post.Status.SCHEDULED);
            } else {
                article.setStatus(Post.Status.PUBLISHED);
            }
        }
        article.setDate(date);
        article.setLanguage(request.getLanguage());

        article.getCategories().clear();
        for (long categoryId : request.getCategoryIds()) {
            article.getCategories().add(entityManager.getReference(Category.class, categoryId));
        }

        article.getTags().clear();
        Set<String> tagNames = StringUtils.commaDelimitedListToSet(request.getTags());
        if (!CollectionUtils.isEmpty(tagNames)) {
            for (String tagName : tagNames) {
                Tag tag = tagRepository.findOneForUpdateByNameAndLanguage(tagName, request.getLanguage());
                if (tag == null) {
                    tag = new Tag();
                    tag.setName(tagName);
                    tag.setLanguage(request.getLanguage());
                    article.setCreatedAt(now);
                    article.setCreatedBy(authorizedUser.toString());
                    article.setUpdatedAt(now);
                    article.setUpdatedBy(authorizedUser.toString());
                    tag = tagRepository.saveAndFlush(tag);
                }
                article.getTags().add(tag);
            }
        }

        article.getRelatedPosts().clear();
        Set<Post> relatedPosts = new HashSet<>();
        for (long relatedId : request.getRelatedPostIds()) {
            relatedPosts.add(entityManager.getReference(Post.class, relatedId));
        }
        article.setRelatedToPosts(relatedPosts);

        Seo seo = new Seo();
        seo.setTitle(request.getSeoTitle());
        seo.setDescription(request.getSeoDescription());
        seo.setKeywords(request.getSeoKeywords());
        article.setSeo(seo);

        List<Media> medias = new ArrayList<>();
        if (StringUtils.hasText(request.getBody())) {
            //         Blog blog = blogService.getBlogById(Blog.DEFAULT_ID);
            String mediaUrlPrefix = wallRideProperties.getMediaUrlPrefix();
            Pattern mediaUrlPattern = Pattern.compile(String.format("%s([0-9a-zA-Z\\-]+)", mediaUrlPrefix));
            Matcher mediaUrlMatcher = mediaUrlPattern.matcher(request.getBody());
            while (mediaUrlMatcher.find()) {
                Media media = mediaRepository.findOneById(mediaUrlMatcher.group(1));
                medias.add(media);
            }
        }
        article.setMedias(medias);

        article.setUpdatedAt(now);
        article.setUpdatedBy(authorizedUser.toString());

        Map<CustomField, CustomFieldValue> valueMap = new LinkedHashMap<>();
        for (CustomFieldValue value : article.getCustomFieldValues()) {
            valueMap.put(value.getCustomField(), value);
        }

        article.getCustomFieldValues().clear();
        if (!CollectionUtils.isEmpty(request.getCustomFieldValues())) {
            for (CustomFieldValueEditForm valueForm : request.getCustomFieldValues()) {
                CustomField customField = entityManager.getReference(CustomField.class,
                        valueForm.getCustomFieldId());
                CustomFieldValue value = valueMap.get(customField);
                if (value == null) {
                    value = new CustomFieldValue();
                }
                value.setCustomField(customField);
                value.setPost(article);
                if (valueForm.getFieldType().equals(CustomField.FieldType.CHECKBOX)) {
                    if (!ArrayUtils.isEmpty(valueForm.getTextValues())) {
                        value.setTextValue(String.join(",", valueForm.getTextValues()));
                    } else {
                        value.setTextValue(null);
                    }
                } else {
                    value.setTextValue(valueForm.getTextValue());
                }
                value.setStringValue(valueForm.getStringValue());
                value.setNumberValue(valueForm.getNumberValue());
                value.setDateValue(valueForm.getDateValue());
                value.setDatetimeValue(valueForm.getDatetimeValue());
                if (!value.isEmpty()) {
                    article.getCustomFieldValues().add(value);
                }
            }
        }

        return articleRepository.save(article);
    }

    @CacheEvict(value = WallRideCacheConfiguration.ARTICLE_CACHE, allEntries = true)
    public Article deleteArticle(ArticleDeleteRequest request, BindingResult result) throws BindException {
        postRepository.lock(request.getId());
        Article article = articleRepository.findOneByIdAndLanguage(request.getId(), request.getLanguage());
        articleRepository.delete(article);
        return article;
    }

    @CacheEvict(value = WallRideCacheConfiguration.ARTICLE_CACHE, allEntries = true)
    public List<Article> bulkPublishArticle(ArticleBulkPublishRequest request, AuthorizedUser authorizedUser) {
        List<Article> articles = new ArrayList<>();
        for (long id : request.getIds()) {
            postRepository.lock(id);
            Article article = articleRepository.findOneByIdAndLanguage(id, request.getLanguage());
            if (article.getStatus() != Post.Status.DRAFT && request.getDate() == null) {
                continue;
            }

            if (!StringUtils.hasText(article.getCode())) {
                throw new NotNullException();
            }
            if (!StringUtils.hasText(article.getTitle())) {
                throw new NotNullException();
            }
            if (!StringUtils.hasText(article.getBody())) {
                throw new NotNullException();
            }

            LocalDateTime now = LocalDateTime.now();
            LocalDateTime date = article.getDate();
            if (request.getDate() != null) {
                date = request.getDate();
            }
            if (date == null) {
                date = now;
            }
            article.setDate(date);
            article.setUpdatedAt(now);
            article.setUpdatedBy(authorizedUser.toString());

            article = publishArticle(article);

            if (article.getDate().isAfter(now)) {
                article.setStatus(Post.Status.SCHEDULED);
            } else {
                article.setStatus(Post.Status.PUBLISHED);
            }
            article = articleRepository.saveAndFlush(article);

            articles.add(article);
        }
        return articles;
    }

    @CacheEvict(value = WallRideCacheConfiguration.ARTICLE_CACHE, allEntries = true)
    public List<Article> bulkUnpublishArticle(ArticleBulkUnpublishRequest request, AuthorizedUser authorizedUser) {
        List<Article> articles = new ArrayList<>();
        for (long id : request.getIds()) {
            postRepository.lock(id);
            Article article = articleRepository.findOneByIdAndLanguage(id, request.getLanguage());
            if (article.getStatus() == Post.Status.DRAFT) {
                continue;
            }

            LocalDateTime now = LocalDateTime.now();
            article.setUpdatedAt(now);
            article.setUpdatedBy(authorizedUser.toString());

            article = unpublishArticle(article);
            articles.add(article);
        }
        return articles;
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @CacheEvict(value = WallRideCacheConfiguration.ARTICLE_CACHE, allEntries = true)
    public List<Article> bulkDeleteArticle(ArticleBulkDeleteRequest bulkDeleteRequest, BindingResult result) {
        List<Article> articles = new ArrayList<>();
        for (long id : bulkDeleteRequest.getIds()) {
            final ArticleDeleteRequest deleteRequest = new ArticleDeleteRequest.Builder().id(id)
                    .language(bulkDeleteRequest.getLanguage()).build();

            final BeanPropertyBindingResult r = new BeanPropertyBindingResult(deleteRequest, "request");
            r.setMessageCodesResolver(messageCodesResolver);

            TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
            transactionTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
            Article article = null;
            try {
                article = transactionTemplate.execute(new TransactionCallback<Article>() {
                    public Article doInTransaction(TransactionStatus status) {
                        try {
                            return deleteArticle(deleteRequest, r);
                        } catch (BindException e) {
                            throw new RuntimeException(e);
                        }
                    }
                });
                articles.add(article);
            } catch (Exception e) {
                logger.debug("Errors: {}", r);
                result.addAllErrors(r);
            }
        }
        return articles;
    }

    public List<Long> getArticleIds(ArticleSearchRequest request) {
        return articleRepository.searchForId(request);
    }

    public Page<Article> getArticles(ArticleSearchRequest request) {
        Pageable pageable = new PageRequest(0, 10);
        return getArticles(request, pageable);
    }

    @Cacheable(value = WallRideCacheConfiguration.ARTICLE_CACHE)
    public Page<Article> getArticles(ArticleSearchRequest request, Pageable pageable) {
        return articleRepository.search(request, pageable);
    }

    public List<Article> getArticles(Collection<Long> ids) {
        Set<Article> results = new LinkedHashSet<Article>(articleRepository.findAllByIdIn(ids));
        List<Article> articles = new ArrayList<>();
        for (long id : ids) {
            for (Article article : results) {
                if (id == article.getId()) {
                    articles.add(article);
                    break;
                }
            }
        }
        return articles;
    }

    @Cacheable(value = WallRideCacheConfiguration.ARTICLE_CACHE)
    public SortedSet<Article> getArticlesByCategoryCode(String language, String code, Post.Status status) {
        return getArticlesByCategoryCode(language, code, status, 10);
    }

    @Cacheable(value = WallRideCacheConfiguration.ARTICLE_CACHE)
    public SortedSet<Article> getArticlesByCategoryCode(String language, String code, Post.Status status,
            int size) {
        ArticleSearchRequest request = new ArticleSearchRequest().withLanguage(language).withCategoryCodes(code)
                .withStatus(status);

        Pageable pageable = new PageRequest(0, size);
        Page<Article> page = articleRepository.search(request, pageable);
        return new TreeSet<>(page.getContent());
    }

    @Cacheable(value = WallRideCacheConfiguration.ARTICLE_CACHE)
    public SortedSet<Article> getLatestArticles(String language, Post.Status status, int size) {
        ArticleSearchRequest request = new ArticleSearchRequest().withLanguage(language).withStatus(status);

        Pageable pageable = new PageRequest(0, size);
        Page<Article> page = articleRepository.search(request, pageable);
        return new TreeSet<>(page.getContent());
    }

    public Article getArticleById(long id) {
        return articleRepository.findOneById(id);
    }

    public Article getArticleById(long id, String language) {
        return articleRepository.findOneByIdAndLanguage(id, language);
    }

    @Cacheable(value = WallRideCacheConfiguration.ARTICLE_CACHE)
    public Article getArticleByCode(String code, String language) {
        return articleRepository.findOneByCodeAndLanguage(code, language);
    }

    public Article getDraftById(long id) {
        return articleRepository
                .findOne(ArticleSpecifications.draft(entityManager.getReference(Article.class, id)));
    }

    public long countArticles(String language) {
        return articleRepository.count(language);
    }

    public long countArticlesByStatus(Post.Status status, String language) {
        return articleRepository.countByStatus(status, language);
    }

    public Map<Long, Long> countArticlesByAuthorIdGrouped(Post.Status status, String language) {
        List<Map<String, Object>> results = articleRepository.countByAuthorIdGrouped(status, language);
        Map<Long, Long> counts = new HashMap<>();
        for (Map<String, Object> row : results) {
            counts.put((Long) row.get("userId"), (Long) row.get("count"));
        }
        return counts;
    }

    public Map<Long, Long> countArticlesByCategoryIdGrouped(Post.Status status, String language) {
        List<Map<String, Object>> results = articleRepository.countByCategoryIdGrouped(status, language);
        Map<Long, Long> counts = new HashMap<>();
        for (Map<String, Object> row : results) {
            counts.put((Long) row.get("categoryId"), (Long) row.get("count"));
        }
        return counts;
    }

    public Map<Long, Long> countArticlesByTagIdGrouped(Post.Status status, String language) {
        List<Map<String, Object>> results = articleRepository.countByTagIdGrouped(status, language);
        Map<Long, Long> counts = new HashMap<>();
        for (Map<String, Object> row : results) {
            counts.put((Long) row.get("tagId"), (Long) row.get("count"));
        }
        return counts;
    }
}