@RequestMapping(
      value = "/admin/search-reindex",
      method = RequestMethod.POST,
      params = "action=current")
  public ModelAndView reindexCurrentMonth(ServletRequest request) throws Exception {
    Template tmpl = Template.getTemplate(request);

    Connection db = LorDataSource.getConnection();

    try {
      if (!tmpl.isSessionAuthorized()) {
        throw new AccessViolationException("Not authorized");
      }

      tmpl.getCurrentUser().checkDelete();

      Calendar current = Calendar.getInstance();

      for (int i = 0; i < 3; i++) {
        searchQueueSender.updateMonth(current.get(Calendar.YEAR), current.get(Calendar.MONTH) + 1);
        current.add(Calendar.MONTH, -1);
      }

      return new ModelAndView("action-done", "message", "Scheduled reindex last 3 month");
    } finally {
      JdbcUtils.closeConnection(db);
    }
  }
  @RequestMapping(value = "/edit-vote.jsp", method = RequestMethod.GET)
  public ModelAndView showForm(HttpServletRequest request, @RequestParam("msgid") int msgid)
      throws Exception {
    Template tmpl = Template.getTemplate(request);

    if (!tmpl.isModeratorSession()) {
      throw new AccessViolationException("Not authorized");
    }

    Map<String, Object> params = new HashMap<String, Object>();
    params.put("msgid", msgid);

    Connection db = null;

    try {
      db = LorDataSource.getConnection();

      Poll poll = Poll.getPollByTopic(db, msgid);
      params.put("poll", poll);

      List<PollVariant> variants = poll.getPollVariants(db, Poll.ORDER_ID);
      params.put("variants", variants);

      return new ModelAndView("edit-vote", params);
    } finally {
      if (db != null) {
        db.close();
      }
    }
  }
  @RequestMapping(value = "/show-topics.jsp", method = RequestMethod.GET)
  public ModelAndView showUserTopics(
      @RequestParam("nick") String nick,
      @RequestParam(value = "offset", required = false) Integer offset,
      @RequestParam(value = "output", required = false) String output,
      HttpServletResponse response)
      throws Exception {
    Connection db = null;

    Map<String, Object> params = new HashMap<String, Object>();

    try {
      response.setDateHeader("Expires", System.currentTimeMillis() + 60 * 1000);
      response.setDateHeader("Last-Modified", System.currentTimeMillis());

      db = LorDataSource.getConnection();

      User user = User.getUser(db, nick);

      params.put("ptitle", "Сообщения " + user.getNick());
      params.put("navtitle", "Сообщения " + user.getNick());

      params.put("user", user);

      NewsViewer newsViewer = new NewsViewer();

      offset = fixOffset(offset);

      newsViewer.setLimit("LIMIT 20" + (offset > 0 ? (" OFFSET " + offset) : ""));

      newsViewer.setCommitMode(NewsViewer.CommitMode.ALL);

      if (user.getId() == 2) {
        throw new UserErrorException("Лента для пользователя anonymous не доступна");
      }

      newsViewer.setUserid(user.getId());

      params.put("messages", newsViewer.getMessagesCached(db));

      params.put("offsetNavigation", true);
      params.put("offset", offset);

      params.put("rssLink", "show-topics.jsp?nick=" + nick + "&output=rss");

      if (output != null && output.equals("rss")) {
        return new ModelAndView("section-rss", params);
      } else {
        return new ModelAndView("view-news", params);
      }
    } finally {
      if (db != null) {
        db.close();
      }
    }
  }
  @RequestMapping(
      value = "/admin/search-reindex",
      method = RequestMethod.POST,
      params = "action=all")
  public ModelAndView reindexAll(ServletRequest request) throws Exception {
    Template tmpl = Template.getTemplate(request);

    Connection db = LorDataSource.getConnection();

    try {
      if (!tmpl.isSessionAuthorized()) {
        throw new AccessViolationException("Not authorized");
      }

      tmpl.getCurrentUser().checkDelete();

      Statement st = db.createStatement();

      ResultSet rs =
          st.executeQuery("SELECT min(postdate) FROM topics WHERE postdate!='epoch'::timestamp");

      if (!rs.next()) {
        throw new RuntimeException("no topics?!");
      }

      Timestamp startDate = rs.getTimestamp(1);

      rs.close();
      st.close();

      Calendar start = Calendar.getInstance();
      start.setTime(startDate);

      start.set(Calendar.DAY_OF_MONTH, 1);
      start.set(Calendar.HOUR, 0);
      start.set(Calendar.MINUTE, 0);

      for (Calendar i = Calendar.getInstance(); i.after(start); i.add(Calendar.MONTH, -1)) {
        searchQueueSender.updateMonth(i.get(Calendar.YEAR), i.get(Calendar.MONTH) + 1);
      }

      searchQueueSender.updateMonth(1970, 1);

      return new ModelAndView("action-done", "message", "Scheduled reindex");
    } finally {
      JdbcUtils.closeConnection(db);
    }
  }
  @RequestMapping(value = "/view-news.jsp", method = RequestMethod.GET)
  public ModelAndView showNews(
      @RequestParam(value = "month", required = false) Integer month,
      @RequestParam(value = "year", required = false) Integer year,
      @RequestParam(value = "section", required = false) Integer sectionid,
      @RequestParam(value = "group", required = false) Integer groupid,
      @RequestParam(value = "tag", required = false) String tag,
      @RequestParam(value = "offset", required = false) Integer offset,
      HttpServletResponse response)
      throws Exception {
    Connection db = null;

    Map<String, Object> params = new HashMap<String, Object>();

    try {
      if (month == null) {
        response.setDateHeader("Expires", System.currentTimeMillis() + 60 * 1000);
        response.setDateHeader("Last-Modified", System.currentTimeMillis());
      } else {
        long expires = System.currentTimeMillis() + 30 * 24 * 60 * 60 * 1000L;

        if (year == null) {
          throw new ServletParameterMissingException("year");
        }

        params.put("year", year);
        params.put("month", month);

        Calendar calendar = Calendar.getInstance();
        calendar.set(year, month - 1, 1);
        calendar.add(Calendar.MONTH, 1);

        long lastmod = calendar.getTimeInMillis();

        if (lastmod < System.currentTimeMillis()) {
          response.setDateHeader("Expires", expires);
          response.setDateHeader("Last-Modified", lastmod);
        } else {
          response.setDateHeader("Expires", System.currentTimeMillis() + 60 * 1000);
          response.setDateHeader("Last-Modified", System.currentTimeMillis());
        }
      }

      db = LorDataSource.getConnection();

      Section section = null;

      if (sectionid != null) {
        section = new Section(db, sectionid);

        params.put("section", section);
      }

      Group group = null;

      if (groupid != null) {
        group = new Group(db, groupid);

        if (group.getSectionId() != sectionid) {
          throw new ScriptErrorException(
              "группа #" + groupid + " не принадлежит разделу #" + sectionid);
        }

        params.put("group", group);
      }

      if (tag != null) {
        Tags.checkTag(tag);
        params.put("tag", tag);
      }

      if (section == null && tag == null) {
        throw new ServletParameterException("section or tag required");
      }

      String navtitle;
      if (section != null) {
        navtitle = section.getName();
      } else {
        navtitle = tag;
      }

      if (group != null) {
        navtitle =
            "<a href=\"view-news.jsp?section="
                + section.getId()
                + "\">"
                + section.getName()
                + "</a> - "
                + group.getTitle();
      }

      String ptitle;

      if (month == null) {
        if (section != null) {
          ptitle = section.getName();
          if (group != null) {
            ptitle += " - " + group.getTitle();
          }

          if (tag != null) {
            ptitle += " - " + tag;
          }
        } else {
          ptitle = tag;
        }
      } else {
        ptitle = "Архив: " + section.getName();

        if (group != null) {
          ptitle += " - " + group.getTitle();
        }

        if (tag != null) {
          ptitle += " - " + tag;
        }

        ptitle += ", " + year + ", " + DateUtil.getMonth(month);
        navtitle += " - Архив " + year + ", " + DateUtil.getMonth(month);
      }

      params.put("ptitle", ptitle);
      params.put("navtitle", navtitle);

      NewsViewer newsViewer = new NewsViewer();

      if (section != null) {
        newsViewer.addSection(sectionid);
      }

      if (group != null) {
        newsViewer.setGroup(group.getId());
      }

      if (tag != null) {
        newsViewer.setTag(tag);
      }

      offset = fixOffset(offset);

      if (month != null) {
        newsViewer.setDatelimit(
            "postdate>='"
                + year
                + '-'
                + month
                + "-01'::timestamp AND (postdate<'"
                + year
                + '-'
                + month
                + "-01'::timestamp+'1 month'::interval)");
      } else if (tag == null) {
        if (section.isPremoderated()) {
          newsViewer.setDatelimit("(commitdate>(CURRENT_TIMESTAMP-'6 month'::interval))");
        } else {
          newsViewer.setDatelimit("(postdate>(CURRENT_TIMESTAMP-'6 month'::interval))");
        }

        newsViewer.setLimit("LIMIT 20" + (offset > 0 ? (" OFFSET " + offset) : ""));
      } else {
        newsViewer.setLimit("LIMIT 20" + (offset > 0 ? (" OFFSET " + offset) : ""));
      }

      params.put("messages", newsViewer.getMessagesCached(db));

      params.put("offsetNavigation", month == null);
      params.put("offset", offset);

      if (section != null) {
        String rssLink = "section-rss.jsp?section=" + section.getId();
        if (group != null) {
          rssLink += "&group=" + group.getId();
        }

        params.put("rssLink", rssLink);
      }

      return new ModelAndView("view-news", params);
    } finally {
      if (db != null) {
        db.close();
      }
    }
  }
  @RequestMapping(value = "/edit-vote.jsp", method = RequestMethod.POST)
  public ModelAndView editVote(
      HttpServletRequest request,
      @RequestParam("msgid") int msgid,
      @RequestParam("id") int id,
      @RequestParam("title") String title)
      throws Exception {
    Template tmpl = Template.getTemplate(request);

    if (!tmpl.isModeratorSession()) {
      throw new AccessViolationException("Not authorized");
    }

    Connection db = null;

    try {
      db = LorDataSource.getConnection();
      db.setAutoCommit(false);

      User user = User.getUser(db, tmpl.getNick());
      user.checkCommit();

      Poll poll = new Poll(db, id);

      PreparedStatement pstTitle = db.prepareStatement("UPDATE votenames SET title=? WHERE id=?");
      pstTitle.setInt(2, id);
      pstTitle.setString(1, HTMLFormatter.htmlSpecialChars(title));

      pstTitle.executeUpdate();

      PreparedStatement pstTopic = db.prepareStatement("UPDATE topics SET title=? WHERE id=?");
      pstTopic.setInt(2, msgid);
      pstTopic.setString(1, HTMLFormatter.htmlSpecialChars(title));

      pstTopic.executeUpdate();

      List<PollVariant> variants = poll.getPollVariants(db, Poll.ORDER_ID);
      for (PollVariant var : variants) {
        String label = new ServletParameterParser(request).getString("var" + var.getId());

        if (label == null || label.trim().length() == 0) {
          var.remove(db);
        } else {
          var.updateLabel(db, label);
        }
      }

      for (int i = 1; i <= 3; i++) {
        String label = new ServletParameterParser(request).getString("new" + i);

        if (label != null && label.trim().length() > 0) {
          poll.addNewVariant(db, label);
        }
      }

      logger.info("Отредактирован опрос" + id + " пользователем " + user.getNick());

      db.commit();

      Random random = new Random();

      return new ModelAndView(
          new RedirectView("view-message.jsp?msgid=" + msgid + "&nocache=" + random.nextInt()));
    } finally {
      if (db != null) {
        db.close();
      }
    }
  }