/* * Copyright (C) 2008-2020, Juick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.juick.www.controllers; import com.juick.model.Message; import com.juick.model.Tag; import com.juick.model.User; import com.juick.util.formatters.PlainTextFormatter; import com.juick.util.HttpForbiddenException; import com.juick.util.HttpNotFoundException; import com.juick.util.WebUtils; import com.juick.www.WebApp; import com.juick.www.api.activity.model.Context; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import com.juick.service.*; import com.juick.util.MessageUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.text.StringEscapeUtils; 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.security.core.AuthenticationException; import org.springframework.security.web.WebAttributes; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.view.RedirectView; import javax.inject.Inject; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.ResourceBundle; import java.util.stream.Collectors; /** * * @author Ugnich Anton */ @Controller public class Site { @Inject private UserService userService; @Inject private TagService tagService; @Inject private MessagesService messagesService; @Inject private ChatService chatService; @Inject private WebApp webApp; @Inject private User serviceUser; @Value("${web_domain:localhost}") private String webDomain; @Value("${telegram_botname:Juick_bot}") private String tgBot; private void fillUserModel(ModelMap model, User user, User visitor) { user.setAvatar(webApp.getAvatarWebPath(user)); model.addAttribute("user", user); model.addAttribute("isSubscribed", userService.isSubscribed(visitor.getUid(), user.getUid())); model.addAttribute("isInBL", userService.isInBL(visitor.getUid(), user.getUid())); model.addAttribute("isInBLAny", userService.isInBLAny(user.getUid(), visitor.getUid())); model.addAttribute("statsIRead", userService.getUserFriends(user.getUid()).size()); model.addAttribute("statsMyReaders", userService.getUserReaders(user.getUid()).size()); model.addAttribute("statsMyBL", userService.getUserBLUsers(user.getUid()).size()); model.addAttribute("statsMessages", userService.getStatsMessages(user.getUid())); model.addAttribute("statsReplies", userService.getStatsReplies(user.getUid())); model.addAttribute("iread", userService.getUserReadLeastPopular(user.getUid(), 8)); model.addAttribute("tagStats", tagService.getUserTagStats(user.getUid()).stream() .sorted((e1, e2) -> Integer.compare(e2.getUsageCount(), e1.getUsageCount())).limit(20) .map(t -> t.getTag().getName()).toList()); } @GetMapping("/login") public String getloginForm(@ModelAttribute User visitor, @RequestParam(name = "retpath", required = false, defaultValue = "/") String retPath, HttpSession session, ModelMap model) { if (!visitor.isAnonymous()) { return String.format("redirect:%s", retPath); } model.addAttribute("visitor", visitor); model.addAttribute("tags", tagService.getPopularTags()); model.addAttribute("domain", webDomain); model.addAttribute("tgBot", tgBot); AuthenticationException authEx = (AuthenticationException) session .getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); if (authEx != null) { model.addAttribute("authErrorMessage", authEx.getLocalizedMessage()); } String socialLoginError = (String) session.getAttribute(SocialLogin.AUTH_ERROR); if (socialLoginError != null) { model.addAttribute("authErrorMessage", socialLoginError); } return "views/login"; } @GetMapping("/") protected String doGet(@ModelAttribute User visitor, Locale locale, @RequestParam(required = false) String tag, @RequestParam(name = "show", required = false) String paramShow, @RequestParam(name = "search", required = false) String paramSearch, @RequestParam(name = "before", required = false, defaultValue = "0") Integer paramBefore, @RequestParam(name = "to", required = false, defaultValue = "0") Long paramTo, @RequestParam(name = "page", required = false, defaultValue = "0") Integer page, ModelMap model) { if (tag != null) { return "redirect:/tag/" + URLEncoder.encode(tag, StandardCharsets.UTF_8); } visitor.setAvatar(webApp.getAvatarWebPath(visitor)); if (paramSearch != null && paramSearch.length() > 64) { paramSearch = null; } model.addAttribute("discover", false); String title; List<Integer> mids; if (paramSearch != null) { String searchTitle = ResourceBundle.getBundle("messages", locale).getString("title.search"); title = searchTitle + StringEscapeUtils.escapeHtml4(paramSearch); mids = messagesService.getSearch(visitor, paramSearch, page); } else if (paramShow == null) { title = ResourceBundle.getBundle("messages", locale).getString("link.discuss"); mids = messagesService.getDiscussions(visitor.getUid(), paramTo); } else if (paramShow.equals("top")) { title = ResourceBundle.getBundle("messages", locale).getString("link.popular"); mids = messagesService.getUserBlogWithRecommendations(serviceUser, visitor, 0, paramBefore); model.addAttribute("discover", true); } else if (paramShow.equals("my") && !visitor.isAnonymous()) { title = ResourceBundle.getBundle("messages", locale).getString("link.my"); mids = messagesService.getMyFeed(visitor.getUid(), paramBefore, true); } else if (paramShow.equals("private") && !visitor.isAnonymous()) { title = ResourceBundle.getBundle("messages", locale).getString("link.privateMessages"); mids = messagesService.getPrivate(visitor.getUid(), paramBefore); } else if (paramShow.equals("discuss")) { return "redirect:/"; } else if (paramShow.equals("recommended") && !visitor.isAnonymous()) { title = ResourceBundle.getBundle("messages", locale).getString("link.recommended"); mids = messagesService.getRecommended(visitor.getUid(), paramBefore); } else if (paramShow.equals("photos")) { title = ResourceBundle.getBundle("messages", locale).getString("link.withPhotos"); mids = messagesService.getPhotos(visitor.getUid(), paramBefore); model.addAttribute("discover", true); } else if (paramShow.equals("all")) { title = ResourceBundle.getBundle("messages", locale).getString("link.allMessages"); mids = messagesService.getAll(visitor.getUid(), paramBefore); model.addAttribute("discover", true); } else { throw new HttpNotFoundException(); } String head = "<meta name=\"Description\" content=\"" + title + "\" />\n"; if (paramBefore > 0 || paramShow != null || paramTo > 0 || page > 0 || paramSearch != null) { head = "<meta name=\"robots\" content=\"noindex\"/>"; } model.addAttribute("title", title); model.addAttribute("headers", head); model.addAttribute("visitor", visitor); model.addAttribute("noindex", !(paramShow == null && paramBefore == 0)); List<Message> msgs = messagesService.getMessages(visitor, mids); msgs.forEach(m -> m.getUser().setAvatar(webApp.getAvatarWebPath(m.getUser()))); if (!visitor.isAnonymous()) { fillUserModel(model, visitor, visitor); List<Integer> unread = messagesService.getUnread(visitor); visitor.setUnreadCount(unread.size()); List<Integer> blUIDs = userService.checkBL(visitor.getUid(), msgs.stream().map(m -> m.getUser().getUid()).toList()); msgs.forEach(m -> m.ReadOnly |= blUIDs.contains(m.getUser().getUid())); } model.addAttribute("msgs", msgs); model.addAttribute("tags", tagService.getPopularTags()); model.addAttribute("headers", head); if (mids.size() >= 20) { String nextpage = paramSearch != null ? String.format("?page=%d", page + 1) : (paramShow == null) ? "?to=" + msgs.get(msgs.size() - 1).getUpdated().toEpochMilli() : "?before=" + mids.get(mids.size() - 1); if (paramShow != null) { nextpage += "&show=" + paramShow; } if (paramSearch != null) { nextpage += "&search=" + URLEncoder.encode(paramSearch, StandardCharsets.UTF_8); } model.addAttribute("nextpage", nextpage); } return "views/index"; } @GetMapping(path = "/{uname}/", headers = "Connection!=Upgrade") protected String doGetBlog(@ModelAttribute User visitor, @RequestParam(required = false, name = "show") String paramShow, @RequestParam(required = false, name = "tag") String paramTagStr, @RequestParam(required = false, name = "search") String paramSearch, @RequestParam(required = false, name = "page", defaultValue = "0") Integer page, @PathVariable String uname, @RequestParam(required = false, defaultValue = "0") Integer before, @CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie, ModelMap model) { User user = userService.getUserByName(uname); if (user.isBanned() || user.isAnonymous()) { throw new HttpNotFoundException(); } visitor.setAvatar(webApp.getAvatarWebPath(visitor)); List<Integer> mids; Tag paramTag = null; if (paramTagStr != null) { if (paramTagStr.length() < 64) { paramTag = tagService.getTag(paramTagStr, false); } if (paramTag == null) { throw new HttpNotFoundException(); } else if (!paramTag.getName().equals(paramTagStr)) { String url = user.getName() + "/?tag=" + URLEncoder.encode(paramTag.getName(), StandardCharsets.UTF_8); return "redirect:/" + url; } } if (paramSearch != null && paramSearch.length() > 64) { paramSearch = null; } int privacy = 0; if (!visitor.isAnonymous()) { if (user.getUid() == visitor.getUid() || visitor.getUid() == 1) { privacy = -3; } else if (userService.isInWL(user.getUid(), visitor.getUid())) { privacy = -2; } } String title; if (paramShow == null) { if (paramTag != null) { title = "Блог " + user.getName() + ": *" + StringEscapeUtils.escapeHtml4(paramTag.getName()); mids = messagesService.getUserTag(user.getUid(), paramTag.TID, privacy, before); } else if (paramSearch != null) { title = "Блог " + user.getName() + ": " + StringEscapeUtils.escapeHtml4(paramSearch); mids = messagesService.getUserSearch(visitor, user.getUid(), paramSearch, privacy, page); } else { title = "Блог " + user.getName(); mids = messagesService.getUserBlog(user.getUid(), privacy, before); } } else if (paramShow.equals("recomm")) { title = "Рекомендации " + user.getName(); mids = messagesService.getUserRecommendations(user.getUid(), before); } else if (paramShow.equals("photos")) { title = "Фотографии " + user.getName(); mids = messagesService.getUserPhotos(user.getUid(), privacy, before); } else { throw new HttpNotFoundException(); } String head = "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"@" + user.getName() + "\" href=\"//rss.juick.com/" + user.getName() + "/blog\"/>"; head += "<meta name=\"Description\" content=\"" + title + "\" />\n"; if (paramTag != null && tagService.getTagNoIndex(paramTag.TID)) { head += "<meta name=\"robots\" content=\"noindex,nofollow\"/>"; } else if (before > 0 || paramShow != null) { head += "<meta name=\"robots\" content=\"noindex\"/>"; } model.addAttribute("pageUrl", "http://juick.com/" + user.getName()); model.addAttribute("title", title); model.addAttribute("headers", head); model.addAttribute("visitor", visitor); model.addAttribute("noindex", paramShow == null && before == 0); fillUserModel(model, user, visitor); model.addAttribute("paramTag", paramTag); List<Message> msgs = messagesService.getMessages(visitor, mids); msgs.forEach(m -> m.getUser().setAvatar(webApp.getAvatarWebPath(m.getUser()))); if (!visitor.isAnonymous()) { List<Integer> unread = messagesService.getUnread(visitor); visitor.setUnreadCount(unread.size()); List<Integer> blUIDs = userService.checkBL(visitor.getUid(), msgs.stream().map(m -> m.getUser().getUid()).toList()); msgs.forEach(m -> m.ReadOnly |= blUIDs.contains(m.getUser().getUid())); } model.addAttribute("msgs", msgs); model.addAttribute("headers", head); if (mids.size() >= 20) { String nextpage = paramSearch != null ? String.format("?page=%d", page + 1) : "?before=" + mids.get(mids.size() - 1); if (paramShow != null) { nextpage += "&show=" + paramShow; } if (paramSearch != null) { nextpage += "&search=" + URLEncoder.encode(paramSearch, StandardCharsets.UTF_8); } if (paramTag != null) { nextpage += "&tag=" + URLEncoder.encode(paramTag.getName(), StandardCharsets.UTF_8); } model.addAttribute("nextpage", nextpage); } return "views/blog"; } @GetMapping("/{uname}/tags") protected String doGetTags(@ModelAttribute User visitor, @PathVariable String uname, ModelMap model) { User user = userService.getUserByName(uname); if (visitor.isBanned()) { throw new HttpNotFoundException(); } visitor.setAvatar(webApp.getAvatarWebPath(visitor)); model.addAttribute("title", "Теги " + user.getName()); model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex,nofollow\"/>"); model.addAttribute("visitor", visitor); fillUserModel(model, user, visitor); model.addAttribute("tags", tagService.getUserTagStats(user.getUid()).stream() .sorted((e1, e2) -> Integer.compare(e2.getUsageCount(), e1.getUsageCount())) .map(t -> t.getTag().getName()).toList()); return "views/blog_tags"; } @GetMapping("/{uname}/friends") protected String doGetFriends(@ModelAttribute User visitor, @PathVariable String uname, ModelMap model) { User user = userService.getUserByName(uname); if (visitor.isBanned()) { throw new HttpNotFoundException(); } visitor.setAvatar(webApp.getAvatarWebPath(visitor)); model.addAttribute("title", "Подписки " + user.getName()); model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex\"/>"); model.addAttribute("visitor", visitor); fillUserModel(model, user, visitor); model.addAttribute("users", userService.getUserFriends(user.getUid())); return "views/users"; } @GetMapping("/{uname}/readers") protected String doGetReaders(@ModelAttribute User visitor, @PathVariable String uname, ModelMap model) { User user = userService.getUserByName(uname); visitor.setAvatar(webApp.getAvatarWebPath(visitor)); model.addAttribute("title", "Читатели " + user.getName()); model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex\"/>"); model.addAttribute("visitor", visitor); fillUserModel(model, user, visitor); model.addAttribute("users", userService.getUserReaders(user.getUid())); return "views/users"; } @GetMapping("/{uname}/bl") protected String doGetBL(@ModelAttribute User visitor, @PathVariable String uname, ModelMap model) { User user = userService.getUserByName(uname); if (visitor.getUid() != user.getUid()) { throw new HttpForbiddenException(); } visitor.setAvatar(webApp.getAvatarWebPath(visitor)); model.addAttribute("title", "Черный список " + user.getName()); model.addAttribute("headers", "<meta name=\"robots\" content=\"noindex\"/>"); model.addAttribute("visitor", visitor); fillUserModel(model, user, visitor); model.addAttribute("users", userService.getUserBLUsers(user.getUid())); return "views/users"; } @GetMapping("/tag/{tagName}") protected String tagAction(@ModelAttribute User visitor, HttpServletRequest request, @PathVariable String tagName, @RequestParam(required = false, defaultValue = "0") int before, ModelMap model) { visitor.setAvatar(webApp.getAvatarWebPath(visitor)); String paramTagStr = StringEscapeUtils.unescapeHtml4(tagName); Tag paramTag = tagService.getTag(paramTagStr, false); if (paramTag == null) { throw new HttpNotFoundException(); } else if (paramTag.SynonymID > 0 && paramTag.TID != paramTag.SynonymID) { Tag synTag = tagService.getTag(paramTag.SynonymID); String url = "/tag/" + URLEncoder.encode(StringEscapeUtils.escapeHtml4(synTag.getName()), StandardCharsets.UTF_8); if (request.getQueryString() != null) { url += "?" + request.getQueryString(); } return "redirect:" + url; } else if (!paramTag.getName().equals(paramTagStr)) { String url = "/tag/" + URLEncoder.encode(StringEscapeUtils.escapeHtml4(paramTag.getName()), StandardCharsets.UTF_8); if (request.getQueryString() != null) { url += "?" + request.getQueryString(); } return "redirect:" + url; } String title = "*" + StringEscapeUtils.escapeHtml4(paramTag.getName()); model.addAttribute("title", title); List<Integer> mids = messagesService.getTag(paramTag.TID, visitor.getUid(), before, (visitor.isAnonymous()) ? 40 : 20); List<Message> msgs = messagesService.getMessages(visitor, mids); msgs.forEach(m -> m.getUser().setAvatar(webApp.getAvatarWebPath(m.getUser()))); if (!visitor.isAnonymous()) { List<Integer> unread = messagesService.getUnread(visitor); visitor.setUnreadCount(unread.size()); List<Integer> blUIDs = userService.checkBL(visitor.getUid(), msgs.stream().map(m -> m.getUser().getUid()).toList()); msgs.forEach(m -> m.ReadOnly |= blUIDs.contains(m.getUser().getUid())); fillUserModel(model, visitor, visitor); } String head = StringUtils.EMPTY; if (tagService.getTagNoIndex(paramTag.TID)) { head = "<meta name=\"robots\" content=\"noindex,nofollow\"/>"; } else if (before > 0 || mids.size() < 5) { head = "<meta name=\"robots\" content=\"noindex\"/>"; } model.addAttribute("headers", head); model.addAttribute("visitor", visitor); model.addAttribute("tag", paramTag); model.addAttribute("title", title); model.addAttribute("msgs", msgs); model.addAttribute("tags", tagService.getPopularTags()); model.addAttribute("noindex", before > 0); model.addAttribute("isSubscribed", tagService.isSubscribed(visitor, paramTag)); model.addAttribute("isInBL", tagService.isInBL(visitor, paramTag)); if (mids.size() >= 20) { String nextpage = "/tag/" + URLEncoder.encode(paramTag.getName(), StandardCharsets.UTF_8) + "?before=" + mids.get(mids.size() - 1); model.addAttribute("nextpage", nextpage); } return "views/index"; } @GetMapping("/pm/inbox") protected String doGetInbox(@ModelAttribute User visitor, ModelMap model) { visitor.setAvatar(webApp.getAvatarWebPath(visitor)); String title = "PM: Inbox"; List<Message> msgs = chatService.getInbox(visitor.getUid()); msgs.forEach(m -> m.getUser().setAvatar(webApp.getAvatarWebPath(m.getUser()))); fillUserModel(model, visitor, visitor); model.addAttribute("title", title); model.addAttribute("visitor", visitor); model.addAttribute("msgs", msgs); model.addAttribute("tags", tagService.getPopularTags()); return "views/pm_inbox"; } @GetMapping("/pm/sent") protected String doGetSent(@ModelAttribute User visitor, @RequestParam(required = false) String uname, ModelMap model) { visitor.setAvatar(webApp.getAvatarWebPath(visitor)); String title = "PM: Sent"; List<Message> msgs = chatService.getOutbox(visitor.getUid()); msgs.forEach(m -> m.getUser().setAvatar(webApp.getAvatarWebPath(m.getUser()))); if (WebUtils.isNotUserName(uname)) { uname = StringUtils.EMPTY; } fillUserModel(model, visitor, visitor); model.addAttribute("title", title); model.addAttribute("visitor", visitor); model.addAttribute("msgs", msgs); model.addAttribute("tags", tagService.getPopularTags()); model.addAttribute("uname", uname); return "views/pm_sent"; } @GetMapping(value = "/{uname}/{mid}", produces = { MediaType.APPLICATION_JSON_VALUE, Context.LD_JSON_MEDIA_TYPE, Context.ACTIVITY_MEDIA_TYPE, Context.ACTIVITYSTREAMS_PROFILE_MEDIA_TYPE }) public RedirectView threadRedirect(@PathVariable String uname, @PathVariable int mid) { String linkedDataLocation = "/n/" + mid + "-0"; return new RedirectView(linkedDataLocation); } @GetMapping(value = "/{uname}/{mid}", produces = { MediaType.TEXT_HTML_VALUE, MediaType.ALL_VALUE }) protected String threadAction(@ModelAttribute User visitor, ModelMap model, @PathVariable String uname, @PathVariable int mid, @CookieValue(name = "sape_cookie", required = false, defaultValue = StringUtils.EMPTY) String sapeCookie) { if (!messagesService.canViewThread(mid, visitor.getUid())) { throw new HttpForbiddenException(); } visitor.setAvatar(webApp.getAvatarWebPath(visitor)); Optional<Message> message = messagesService.getMessage(mid); if (message.isEmpty()) { throw new HttpNotFoundException(); } Message msg = message.get(); User user = userService.getUserByName(uname); if (user.isAnonymous() || !msg.getUser().equals(user)) { return String.format("redirect:/%s/%d", msg.getUser().getName(), mid); } msg.VisitorCanComment = !visitor.isAnonymous(); msg.getUser().setAvatar(webApp.getAvatarWebPath(msg.getUser())); List<Message> replies = messagesService.getReplies(visitor, msg.getMid()); // this should be after getReplies to mark thread as read fillUserModel(model, user, visitor); if (!visitor.isAnonymous()) { List<Integer> unread = messagesService.getUnread(visitor); visitor.setUnreadCount(unread.size()); boolean isMsgAuthor = visitor.getUid() == msg.getUser().getUid(); boolean isInBL = userService.isInBL(visitor.getUid(), msg.getUser().getUid()); msg.VisitorCanComment = isMsgAuthor || !(msg.ReadOnly || isInBL); } model.addAttribute("msg", msg); String title = msg.getUser().getName() + ": " + MessageUtils.getTagsString(msg); model.addAttribute("title", title); model.addAttribute("visitor", visitor); String headers = "<link rel=\"alternate\" type=\"application/rss+xml\" title=\"@" + msg.getUser().getName() + "\" href=\"//rss.juick.com/" + msg.getUser().getName() + "/blog\"/>"; String pageUrl = "https://juick.com/" + msg.getUser().getName() + "/" + msg.getMid(); if (msg.Hidden) { headers += "<meta name=\"robots\" content=\"noindex\"/>"; } String cardType = StringUtils.isNotEmpty(msg.getAttachmentType()) ? "summary_large_image" : "summary"; if (StringUtils.isNotEmpty(msg.getAttachmentType())) { // additional check in case of broken images if (msg.getAttachment() != null) { String msgImage = msg.getAttachment().getMedium().getUrl(); headers += "<meta property=\"og:image\" content=\"" + msgImage + "\" />"; } } else { String msgImage = webApp.getAvatarWebPath(msg.getUser()); headers += "<meta property=\"og:image\" content=\"" + msgImage + "\" />"; } model.addAttribute("ogtype", "article"); String cardDescription = StringEscapeUtils.escapeHtml4(PlainTextFormatter.formatTwitterCard(msg)); headers += "<meta name=\"twitter:card\" content=\"" + cardType + "\" />\n" + "<meta name=\"twitter:site\" content=\"@juick\" />\n" + "<meta property=\"og:url\" content=\"" + pageUrl + "\" />\n" + "<meta property=\"og:title\" content=\"" + msg.getUser().getName() + " at Juick\" />\n" + "<meta property=\"og:description\" content=\"" + cardDescription + "\" />\n" + "<meta name=\"Description\" content=\"" + cardDescription + "\" />\n"; String twitterName = userService.getTwitterName(msg.getUser().getUid()); if (StringUtils.isNotEmpty(twitterName)) { headers += "<meta name=\"twitter:creator\" content=\"@" + twitterName + "\" />\n"; } if (msg.getTags().size() > 0) { headers += "<meta name=\"Keywords\" content=\"" + msg.getTags().stream().map(Tag::getName).collect(Collectors.joining(", ")) + "\" />\n"; } model.addAttribute("headers", headers); model.addAttribute("visitorSubscribed", messagesService.isSubscribed(visitor.getUid(), msg.getMid())); model.addAttribute("visitorInBL", userService.isInBL(msg.getUser().getUid(), visitor.getUid())); model.addAttribute("recomm", messagesService.getMessagesRecommendations(Collections.singletonList(msg.getMid())) .stream().map(Pair::getRight).toList()); List<Integer> blUIDs = new ArrayList<>(); for (Message reply : replies) { if (reply.getUser().getUid() != msg.getUser().getUid() && !blUIDs.contains(reply.getUser().getUid())) { blUIDs.add(reply.getUser().getUid()); } reply.VisitorCanComment = !visitor.isAnonymous(); reply.getUser().setAvatar(webApp.getAvatarWebPath(reply.getUser())); if (!visitor.isAnonymous()) { boolean isMsgAuthor = visitor.getUid() == msg.getUser().getUid(); boolean isReplyAuthor = visitor.getUid() == reply.getUser().getUid(); reply.VisitorCanComment = isMsgAuthor || (!msg.ReadOnly && msg.VisitorCanComment && (isReplyAuthor || !userService.isInBL(visitor.getUid(), reply.getUser().getUid()))); } } model.addAttribute("replies", replies); return "views/thread"; } @GetMapping("/post") protected String postAction(@ModelAttribute User visitor, @RequestParam(required = false) String body, ModelMap model) { fillUserModel(model, visitor, visitor); visitor.setAvatar(webApp.getAvatarWebPath(visitor)); model.addAttribute("title", "Написать"); model.addAttribute("headers", ""); model.addAttribute("visitor", visitor); if (body == null) { body = StringUtils.EMPTY; } else { if (body.length() > 4096) { body = body.substring(0, 4096); } body = StringEscapeUtils.escapeHtml4(body); } model.addAttribute("body", body); model.addAttribute("visitor", visitor); model.addAttribute("user", visitor); model.addAttribute("tags", tagService.getUserTagStats(visitor.getUid()).stream() .sorted((e1, e2) -> Integer.compare(e2.getUsageCount(), e1.getUsageCount())) .map(t -> t.getTag().getName()).toList()); return "views/post"; } // when message id is not fit to int @ExceptionHandler(NumberFormatException.class) public ResponseEntity<String> notFoundAction() { return new ResponseEntity<>(HttpStatus.NOT_FOUND); } }