package org.kalipo.service;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.kalipo.aop.KalipoExceptionHandler;
import org.kalipo.aop.RateLimit;
import org.kalipo.config.Constants;
import org.kalipo.config.ErrorCode;
import org.kalipo.domain.Comment;
import org.kalipo.domain.Report;
import org.kalipo.repository.CommentRepository;
import org.kalipo.repository.ReportRepository;
import org.kalipo.security.Privileges;
import org.kalipo.security.SecurityUtils;
import org.kalipo.service.util.Asserts;
import org.kalipo.service.util.NumUtils;
import org.kalipo.web.rest.KalipoException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import java.util.concurrent.Future;
/**
* on approve: reputation + 5 for every reporter, reputation - 100 for author
* on reject: reputation - 50 for author
* on report: iff report count > 5 comment will become hidden until reviewed
*/
@Service
@KalipoExceptionHandler
public class ReportService {
public static final int CRITICAL_REPORT_COUNT = 3;
private final Logger log = LoggerFactory.getLogger(ReportService.class);
@Inject
private ReportRepository reportRepository;
@Inject
private CommentRepository commentRepository;
@Inject
private ReputationModifierService reputationModifierService;
@Inject
private CommentService commentService;
@Inject
private NotificationService notificationService;
// @RolesAllowed(AuthoritiesConstants.ANONYMOUS)
@RateLimit
public Report create(Report report) throws KalipoException {
validate(report);
final String author = SecurityUtils.getCurrentLogin();
report.setAuthorId(author);
Comment comment = commentRepository.findOne(report.getCommentId());
boolean isDuplicate = reportRepository.findByCommentIdAndAuthorId(comment.getId(), author) != null;
return _create(report, comment, author, isDuplicate);
}
private void validate(Report report) throws KalipoException {
Asserts.isNotNull(report, "report");
Asserts.isNull(report.getId(), "id");
Asserts.isNotNull(report.getCommentId(), "commentId");
}
@RateLimit
public Report createAnonymous(Report report) throws KalipoException {
validate(report);
String email = report.getEmail();
Asserts.isNotNull(report.getEmail(), "email");
Asserts.isNotNull(report.getIp(), "ip");
report.setEmail(email);
Comment comment = commentRepository.findOne(report.getCommentId());
boolean isDuplicate = StringUtils.isNotBlank(email) && reportRepository.findByCommentIdAndEmail(comment.getId(), email) != null;
return _create(report, comment, email, isDuplicate);
}
private Report _create(Report report, Comment comment, String author, boolean isDuplicate) throws KalipoException {
if (isDuplicate) {
throw new KalipoException(ErrorCode.CONSTRAINT_VIOLATED, "You already filed a report for this comment");
}
if (Report.Reason.Other == report.getReason()) {
Asserts.isNotNull(report.getCustomReason(), "customReason");
log.info(String.format("User '%s' reports comment %s saying %s", author, comment.getId(), report.getCustomReason()));
} else {
log.info(String.format("User '%s' reports comment %s saying %s", author, comment.getId(), report.getReason()));
}
// todo if anon report, approve via email?
report.setStatus(Report.Status.PENDING);
report.setThreadId(comment.getThreadId());
report.setCommentId(comment.getId());
report = reportRepository.save(report);
// todo reject reports on already manually approved comments
Integer reportedCount = NumUtils.nullToZero(comment.getReportedCount()) + 1;
comment.setReportedCount(reportedCount);
if (reportedCount == 1) {
notificationService.announcePendingReport(comment.getThreadId(), report);
}
if (reportedCount == CRITICAL_REPORT_COUNT) {
log.info(String.format("Hiding comment %s after %s reports", comment.getId(), CRITICAL_REPORT_COUNT));
comment.setHidden(true);
notificationService.notifySuperModsOfFraudulentComment(comment, author);
}
commentRepository.save(comment);
return report;
}
@RolesAllowed(Privileges.CLOSE_REPORT)
@RateLimit
public void approve(String id) throws KalipoException {
approveOrRejectReport(getPendingReport(id).setStatus(Report.Status.APPROVED));
}
@RolesAllowed(Privileges.CLOSE_REPORT)
@RateLimit
public void reject(String id) throws KalipoException {
approveOrRejectReport(getPendingReport(id).setStatus(Report.Status.REJECTED));
}
// todo add RolesAllowed
@Async
public Future<Page<Report>> filtered(Report.Status status, int page) {
PageRequest pageable = new PageRequest(page, Constants.PAGE_SIZE, Sort.Direction.DESC, Constants.PARAM_CREATED_DATE);
return new AsyncResult<>(reportRepository.findByStatus(status, pageable));
}
@Async
public Future<Report> get(String id) throws KalipoException {
return new AsyncResult<Report>(reportRepository.findOne(id));
}
/**
* Delete a pending report
*
* @param id the report id
* @throws org.kalipo.web.rest.KalipoException
*/
// todo add RolesAllowed
public void delete(String id) throws KalipoException {
// todo delete replaces reject
getPendingReport(id); // will fail if not pending or existing
reportRepository.delete(id);
}
// --
// todo delete replaces reject
private void approveOrRejectReport(Report report) throws KalipoException {
final String currentLogin = SecurityUtils.getCurrentLogin();
report.setReviewerId(currentLogin);
Comment comment = commentRepository.findOne(report.getCommentId());
Asserts.isNotNull(comment, "comment");
if (report.getStatus() == Report.Status.APPROVED) {
log.info(String.format("%s approves report %s and triggers delete-comment", currentLogin, report.getId()));
commentService.delete(comment);
notificationService.announceReportApproved(report);
} else {
if (BooleanUtils.isTrue(comment.getHidden())) {
log.info(String.format("%s rejects report %s - comment is visible again", currentLogin, report.getId()));
comment.setHidden(false);
commentRepository.save(comment);
} else {
log.info(String.format("%s rejects report %s", currentLogin, report.getId()));
}
notificationService.announceReportRejected(report);
}
reputationModifierService.onReportApprovalOrRejection(reportRepository.save(report));
}
private Report getPendingReport(String id) throws KalipoException {
Report report = reportRepository.findOne(id);
Asserts.isNotNull(report, "id");
if (report.getStatus() != Report.Status.PENDING) {
throw new KalipoException(ErrorCode.CONSTRAINT_VIOLATED, "Report must be pending");
}
return report;
}
}