SmtpService.java

package jasper.service;

import io.micrometer.core.annotation.Timed;
import jasper.component.ConfigCache;
import jasper.domain.Ref;
import jasper.domain.Ref_;
import jasper.repository.RefRepository;
import jasper.repository.filter.RefFilter;
import jasper.security.Auth;
import jasper.service.dto.SmtpWebhookDto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.UUID;

import static jasper.repository.spec.QualifiedTag.concat;
import static jasper.repository.spec.QualifiedTag.selector;
import static org.springframework.data.domain.Sort.Order.desc;
import static org.springframework.data.domain.Sort.by;

@Service
public class SmtpService {
	private static final Logger logger = LoggerFactory.getLogger(SmtpService.class);

	@Autowired
	Auth auth;

	DateTimeFormatter smtp1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z Z", Locale.US);
	DateTimeFormatter smtp2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z z", Locale.US);

	@Autowired
	RefService refService;

	@Autowired
	RefRepository refRepository;

	@Autowired
	ConfigCache configs;

	@PreAuthorize("@auth.hasRole('USER') and @auth.local(#origin)")
	@Timed(value = "jasper.service", extraTags = {"service", "smtp"}, histogram = true)
	public void create(SmtpWebhookDto email, String origin) {
		var ref = smtpToDomain(email);
		ref.setOrigin(origin);
		Page<Ref> source = refRepository.findAll(
				RefFilter.builder()
						.query("plugin/email:!internal")
						.endsTitle(ref.getTitle())
						.publishedBefore(ref.getPublished()).build().spec(),
				PageRequest.of(0, 1, by(desc(Ref_.PUBLISHED))));
		if (!source.isEmpty()) {
			ref.setSources(List.of(source.getContent().get(0).getUrl()));
			ref.addTag("internal");
		}
		refService.create(ref);
	}

	private Ref smtpToDomain(SmtpWebhookDto msg) {
		var result = new Ref();
		result.setUrl("comment:" + UUID.randomUUID());
		if (!msg.getDate().startsWith("0001-01-01")) {
			try {
				result.setPublished(ZonedDateTime.parse(msg.getDate(), smtp1).toInstant());
			} catch (RuntimeException e) {
				result.setPublished(ZonedDateTime.parse(msg.getDate(), smtp2).toInstant());
			}
		}
		result.setTitle(msg.getSubject());
		result.setComment(msg.getBody().getHtml() == null ?
			msg.getBody().getText() :
			msg.getBody().getHtml().replaceAll("(?m)^\\s+", ""));
		result.setTags(emailToTags(msg));
		return result;
	}

	private List<String> emailToTags(SmtpWebhookDto msg) {
		var tags = new ArrayList<>(List.of("plugin/email", "plugin/thread"));
		if (msg.getAddresses() != null) {
			if (Optional.ofNullable(msg.getAddresses().getTo())
				.map(SmtpWebhookDto.EmailAddress::getAddress).isPresent()) {
				tags.add(emailAddressToNotification(msg.getAddresses().getTo().getAddress()));
			}
			if (Optional.ofNullable(msg.getAddresses().getFrom())
				.map(SmtpWebhookDto.EmailAddress::getAddress).isPresent()) {
				tags.add(emailAddressFrom(msg.getAddresses().getFrom().getAddress()));
			}
			if (msg.getAddresses().getCc() != null) {
				for (var e : msg.getAddresses().getCc()) {
					if (e.getAddress() != null) {
						tags.add(emailAddressToNotification(e.getAddress()));
					}
				}
			}
			if (msg.getAddresses().getBcc() != null) {
				for (var e : msg.getAddresses().getBcc()) {
					if (e.getAddress() != null) {
						tags.add(emailAddressToNotification(e.getAddress()));
					}
				}
			}
			if (msg.getAddresses().getReplyTo() != null) {
				for (var e : msg.getAddresses().getReplyTo()) {
					if (e.getAddress() != null) {
						tags.add(emailAddressFrom(e.getAddress()));
					}
				}
			}
		}
		return tags;
	}

	private String emailAddressToNotification(String email) {
		var root = configs.root();
		var qt = selector(email);
		var remote = qt.origin;
		if (remote.endsWith("." + root.getEmailHost())) {
			remote = remote.substring(0, remote.length() - root.getEmailHost().length() - 1);
		}
		if (auth.local(remote)) {
			return concat("plugin/inbox", qt.tag);
		} else {
			return concat("plugin/outbox", remote, qt.tag);
		}
	}

	private String emailAddressFrom(String email) {
		var qt = selector(email);
		return concat("plugin/from", qt.origin, qt.tag);
	}

}