/* * Copyright 2015-2017 EuregJUG. * * 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 eu.euregjug.site.web; import com.github.mkopylec.recaptcha.validation.RecaptchaValidator; import eu.euregjug.site.events.EventEntity; import eu.euregjug.site.events.EventRepository; import eu.euregjug.site.events.Registration; import eu.euregjug.site.events.RegistrationEntity; import eu.euregjug.site.events.RegistrationService; import eu.euregjug.site.events.RegistrationService.InvalidRegistrationException; import eu.euregjug.site.links.LinkEntity; import eu.euregjug.site.links.LinkRepository; import eu.euregjug.site.posts.Post; import eu.euregjug.site.posts.PostEntity; import eu.euregjug.site.posts.PostEntity.Status; import eu.euregjug.site.posts.PostRenderingService; import eu.euregjug.site.posts.PostRepository; import java.time.DateTimeException; import java.time.LocalDate; import java.time.ZoneId; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.NoSuchElementException; import java.util.Optional; import java.util.TreeMap; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; 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.mvc.support.RedirectAttributes; import static java.util.Comparator.reverseOrder; import static java.util.stream.Collectors.toList; import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.POST; import static java.util.stream.Collectors.groupingBy; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; /** * @author Michael J. Simons, 2015-12-27 */ @Controller @RequiredArgsConstructor @Slf4j class IndexController { private static final String ATTRIBUTE_ALERTS = "alerts"; private static final String ATTRIBUTE_REGISTERED = "registered"; private static final String ATTRIBUTE_POSTS = "posts"; private static final String ATTRIBUTE_POST = "post"; private static final String VIEW_POST = ATTRIBUTE_POST; private static final String ATTRIBUTE_EVENT = "event"; private final EventRepository eventRepository; private final RegistrationService registrationService; private final LinkRepository linkRepository; private final PostRepository postRepository; private final PostRenderingService postRenderingService; private final RecaptchaValidator recaptchaValidator; @RequestMapping({"", "/", "/feed"}) public String index( @RequestParam(required = false, defaultValue = "0") final Integer page, final Model model ) { model .addAttribute("upcomingEvents", this.eventRepository.findUpcomingEvents()) .addAttribute("links", this.linkRepository.findAllByOrderByTypeAscSortColAscTitleAsc().stream().collect(groupingBy(LinkEntity::getType))) .addAttribute(ATTRIBUTE_POSTS, this.postRepository.findAllByStatus(Status.published, new PageRequest(page, 5, Direction.DESC, "publishedOn", "createdAt")).map(postRenderingService::render)); return "index"; } @RequestMapping({ "/{year:\\d+}/{month:\\d+}/{day:\\d+}/{slug}", "/posts/{year:\\d+}-{month:\\d+}-{day:\\d+}-{slug}" }) public String post( @PathVariable final Integer year, @PathVariable final Integer month, @PathVariable final Integer day, @PathVariable final String slug, final Model model ) { String rv = "redirect:/"; try { final Date publishedOn = Date.from(LocalDate.of(year, month, day).atStartOfDay(ZoneId.systemDefault()).toInstant()); final Optional<PostEntity> post = this.postRepository .findByPublishedOnAndSlug(publishedOn, slug) .filter(PostEntity::isPublished); model .addAttribute("previousPost", post.flatMap(this.postRepository::getPrevious)) .addAttribute(ATTRIBUTE_POST, post.map(postRenderingService::render).get()) .addAttribute("nextPost", post.flatMap(this.postRepository::getNext)); rv = VIEW_POST; } catch (DateTimeException | NoSuchElementException e) { log.debug("Invalid request for post", e); } return rv; } @RequestMapping({"/archive", "/archives"}) public String archive(final Model model) { model.addAttribute(ATTRIBUTE_POSTS, this.postRepository .findAll(new Sort(Direction.DESC, "publishedOn")).stream() .filter(PostEntity::isPublished) .map(Post::new) .collect(groupingBy( post -> post.getPublishedOn().withDayOfMonth(1), () -> new TreeMap<LocalDate, List<Post>>(reverseOrder()), toList() ) ) ); return "archive"; } @RequestMapping("/search") public String search(@RequestParam final String q, final Model model) { final TreeMap<LocalDate, List<Post>> posts = this.postRepository .searchByKeyword(q).stream() .filter(PostEntity::isPublished) .map(Post::new) .collect(groupingBy( post -> post.getPublishedOn().withDayOfMonth(1), () -> new TreeMap<LocalDate, List<Post>>(reverseOrder()), toList() )); if (posts.isEmpty()) { model.addAttribute(ATTRIBUTE_ALERTS, Arrays.asList("search.noResults")); } model .addAttribute(ATTRIBUTE_POSTS, posts) .addAttribute("q", q); return "archive"; } @RequestMapping(value = "/events", produces = "text/calendar") public String events(final Model model) { model.addAttribute("events", this.eventRepository.findUpcomingEvents()); return "events"; } @RequestMapping(value = "/register/{eventId}", method = GET) public String register( @PathVariable final Integer eventId, final Model model, final RedirectAttributes redirectAttributes ) { final EventEntity event = this.eventRepository.findOne(eventId).orElse(null); String rv; if (event == null) { redirectAttributes.addFlashAttribute(ATTRIBUTE_ALERTS, Arrays.asList("invalidEvent")); rv = "redirect:/"; } else { model.addAttribute(ATTRIBUTE_EVENT, event); if (!model.containsAttribute("registration")) { model.addAttribute("registration", new Registration()); } if (!model.containsAttribute(ATTRIBUTE_REGISTERED)) { model.addAttribute(ATTRIBUTE_REGISTERED, false); } rv = "register"; } return rv; } @RequestMapping(value = "/register/{eventId}", method = POST) public String register( @PathVariable final Integer eventId, @Valid final Registration registration, final BindingResult registrationBindingResult, final Locale locale, final HttpServletRequest request, final Model model, final RedirectAttributes redirectAttributes ) { String rv; if (registrationBindingResult.hasErrors() || recaptchaValidator.validate(request).isFailure()) { model.addAttribute(ATTRIBUTE_ALERTS, Arrays.asList("invalidRegistration")); rv = register(eventId, model, redirectAttributes); } else { try { final RegistrationEntity registrationEntity = this.registrationService.register(eventId, registration); this.registrationService.sendConfirmationMail(registrationEntity, locale); redirectAttributes .addFlashAttribute(ATTRIBUTE_EVENT, registrationEntity.getEvent()) .addFlashAttribute(ATTRIBUTE_REGISTERED, true) .addFlashAttribute(ATTRIBUTE_ALERTS, Arrays.asList(ATTRIBUTE_REGISTERED)); rv = "redirect:/register/" + eventId; } catch (InvalidRegistrationException e) { log.debug("Invalid registration request", e); model.addAttribute(ATTRIBUTE_ALERTS, Arrays.asList(e.getLocalizedMessage())); rv = register(eventId, model, redirectAttributes); } } return rv; } }