package io.kaif.web; import java.io.IOException; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.view.RedirectView; import io.kaif.flake.FlakeId; import io.kaif.model.article.Article; import io.kaif.model.article.ArticleList; import io.kaif.model.debate.Debate; import io.kaif.model.debate.DebateList; import io.kaif.model.zone.Zone; import io.kaif.model.zone.ZoneInfo; import io.kaif.service.ArticleService; import io.kaif.service.HonorRollService; import io.kaif.service.ZoneService; @Controller @RequestMapping("/z") public class ZoneController { static class ZonePageModelView extends ModelAndView { public ZonePageModelView(ZoneInfo zoneInfo, ZoneService zoneService) { super("/zone/zone-page"); addObject("zoneInfo", zoneInfo); addObject("recommendZones", zoneService.listRecommendZones()); addObject("administrators", zoneService.listAdministratorsWithCache(zoneInfo.getZone())); } } @Autowired private ZoneService zoneService; @Autowired private ArticleService articleService; @Autowired private HonorRollService honorRollService; @RequestMapping("/{zone}") public Object hotArticles(@PathVariable("zone") String rawZone, @RequestParam(value = "start", required = false) FlakeId startArticleId, HttpServletRequest request) throws IOException { return resolveZone(request, rawZone, zoneInfo -> { return new ZonePageModelView(zoneInfo, zoneService).addObject("articleList", new ArticleList(articleService.listHotZoneArticles(zoneInfo.getZone(), startArticleId))); }); } @RequestMapping("/{zone}/hot.rss") public Object rssFeed(@PathVariable("zone") String rawZone, HttpServletRequest request) { return resolveZone(request, rawZone, zoneInfo -> { request.getRequestURL(); ModelAndView modelAndView = new ModelAndView().addObject("zoneInfo", zoneInfo) .addObject("articles", articleService.listRssHotZoneArticlesWithCache(zoneInfo.getZone())); modelAndView.setView(new HotArticleRssContentView()); return modelAndView; }); } private Object resolveZone(HttpServletRequest request, String decodedRawZone, Function<ZoneInfo, ModelAndView> onZoneInfo) { // note that decodedRawZone already do http url decode, and PathVariable already trim() // space of value return Zone.tryFallback(decodedRawZone).map(zone -> { if (!zone.value().equals(decodedRawZone)) { String orgUrl = request.getRequestURL().toString(); // replace pattern is combine of fallback pattern and valid pattern // TODO refactor replace rule to Zone String location = orgUrl.replaceFirst("/z/[a-zA-Z0-9_\\-]+", "/z/" + zone); //check if fallback success, this prevent infinite redirect loop if (!location.equals(orgUrl)) { RedirectView redirectView = new RedirectView(location); redirectView.setPropagateQueryParams(true); redirectView.setExpandUriTemplateVariables(false); redirectView.setExposeModelAttributes(false); redirectView.setExposeContextBeansAsAttributes(false); redirectView.setExposePathVariables(false); redirectView.setContextRelative(true); redirectView.setStatusCode(HttpStatus.PERMANENT_REDIRECT); return redirectView; } } return onZoneInfo.apply(zoneService.loadZone(zone)); }).orElseThrow(() -> new EmptyResultDataAccessException("no such zone: " + decodedRawZone, 1)); } @RequestMapping("/{zone}/new") public Object newArticles(@PathVariable("zone") String rawZone, @RequestParam(value = "start", required = false) FlakeId startArticleId, HttpServletRequest request) { return resolveZone(request, rawZone, zoneInfo -> { return new ZonePageModelView(zoneInfo, zoneService).addObject("articleList", new ArticleList(articleService.listLatestZoneArticles(zoneInfo.getZone(), startArticleId))); }); } @RequestMapping("/{zone}/new-debate") public Object newDebates(@PathVariable("zone") String rawZone, @RequestParam(value = "start", required = false) FlakeId startDebateId, HttpServletRequest request) { return resolveZone(request, rawZone, zoneInfo -> { List<Debate> debates = articleService.listLatestZoneDebates(zoneInfo.getZone(), startDebateId); List<Article> articles = articleService.listArticlesByDebatesWithCache(debates.stream() .map(Debate::getDebateId) .collect(Collectors.toList())); return new ZonePageModelView(zoneInfo, zoneService).addObject("debateList", new DebateList(debates, articles)); }); } @RequestMapping({ "/{zone}/article/create-link", "/{zone}/article/create-speak" }) public Object createArticle(@PathVariable("zone") String rawZone, HttpServletRequest request) { return resolveZone(request, rawZone, zoneInfo -> new CreateArticleModelAndView(zoneService, zoneInfo)); } @RequestMapping("/{zone}/honor") public Object zoneHonors(@PathVariable("zone") String rawZone, HttpServletRequest request) { return resolveZone(request, rawZone, zoneInfo -> new ZonePageModelView(zoneInfo, zoneService).addObject("honorRolls", honorRollService.listHonorRollsByZone(zoneInfo.getZone()))); } @RequestMapping("/{zone}/debates/{articleId}") public Object articleDebates(@PathVariable("zone") String rawZone, @PathVariable("articleId") FlakeId articleFlakeId, HttpServletRequest request) throws IOException { return resolveZone(request, rawZone, zoneInfo -> { return new ModelAndView("article/debates")// .addObject("zoneInfo", zoneInfo) .addObject("recommendZones", zoneService.listRecommendZones()) .addObject("article", articleService.loadArticle(articleFlakeId)) .addObject("zoneAdmins", zoneService.listAdministratorsWithCache(zoneInfo.getZone()) .stream() .collect(Collectors.joining(","))) .addObject("debateTree", articleService.listBestDebates(articleFlakeId, null)); }); } @RequestMapping("/{zone}/debates/{articleId}/{parentDebateId}") public Object childDebates(@PathVariable("zone") String rawZone, @PathVariable("articleId") FlakeId articleFlakeId, @PathVariable("parentDebateId") FlakeId debateFlakeId, HttpServletRequest request) throws IOException { return resolveZone(request, rawZone, zoneInfo -> { return new ModelAndView("article/debates")// .addObject("zoneInfo", zoneInfo) .addObject("article", articleService.loadArticle(articleFlakeId)) .addObject("recommendZones", zoneService.listRecommendZones()) .addObject("parentDebate", articleService.loadDebateWithoutCache(debateFlakeId)) .addObject("debateTree", articleService.listBestDebates(articleFlakeId, debateFlakeId)); }); } }