diff --git a/pom.xml b/pom.xml index 2739c40..4e3326c 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.example Widerhall - 0.0.6 + 0.0.7 diff --git a/src/main/java/de/srsoftware/widerhall/data/MailingList.java b/src/main/java/de/srsoftware/widerhall/data/MailingList.java index ad16927..b879ea2 100644 --- a/src/main/java/de/srsoftware/widerhall/data/MailingList.java +++ b/src/main/java/de/srsoftware/widerhall/data/MailingList.java @@ -15,6 +15,9 @@ import static de.srsoftware.widerhall.Constants.*; import static de.srsoftware.widerhall.Util.t; import static de.srsoftware.widerhall.data.User.PERMISSION_ADMIN; +/** + * this class encapsulates a MailingList db object + */ public class MailingList { private static final Logger LOG = LoggerFactory.getLogger(MailingList.class); private static final String IMAP_HOST = "imap_host"; @@ -25,12 +28,12 @@ public class MailingList { private static final String SMTP_PORT = "smtp_port"; private static final String SMTP_USER = "smtp_user"; private static final String SMTP_PASS = "smtp_pass"; + public static final String TABLE_NAME = "Lists"; private static final int STATE_PENDING = 0; private static final int STATE_ENABLED = 1; private static final int STATE_PUBLIC = 2; private final String name; private final String email; - public static final String TABLE_NAME = "Lists"; private final String imapPass, imapHost, imapUser; private final int imapPort; private int state; @@ -38,6 +41,20 @@ public class MailingList { private static final HashMap lists = new HashMap<>(); + /** + * create a new ML object + * @param email + * @param name + * @param imapHost + * @param imapPort + * @param imapUser + * @param imapPass + * @param smtpHost + * @param smtpPort + * @param smtpUser + * @param smtpPass + * @param state + */ public MailingList(String email, String name, String imapHost, int imapPort, String imapUser, String imapPass, String smtpHost, int smtpPort, String smtpUser, String smtpPass, int state) { this.email = email; this.name = name; @@ -49,10 +66,29 @@ public class MailingList { this.smtp = new SmtpClient(smtpHost,smtpPort,smtpUser,smtpPass); } + /** + * create a new ML object int the database + * @param email + * @param name + * @param imapHost + * @param imapPort + * @param imapUser + * @param imapPass + * @param smtpHost + * @param smtpPort + * @param smtpUser + * @param smtpPass + * @return + * @throws SQLException + */ public static MailingList create(String email, String name, String imapHost, int imapPort, String imapUser, String imapPass, String smtpHost, int smtpPort, String smtpUser, String smtpPass) throws SQLException { return new MailingList(email, name, imapHost, imapPort, imapUser, imapPass, smtpHost, smtpPort, smtpUser, smtpPass, STATE_PENDING).save(); } + /** + * create underlying db table + * @throws SQLException + */ public static void createTable() throws SQLException { var sql = new StringBuilder() .append("CREATE TABLE ").append(TABLE_NAME) @@ -87,24 +123,42 @@ public class MailingList { Database.open().update(TABLE_NAME).set(STATE,state).where(EMAIL, email()).compile().run(); } + /** + * test, whether the current ML is subscribable by a given user + * @param user + * @return + */ public boolean isOpenFor(User user) { - if ((state & STATE_PUBLIC) > 0) return true; + if ((state & STATE_PUBLIC) > 0) return true; // all users may subscribe public mailing lists if (user == null) return false; try { var member = ListMember.load(this,user); - return member.hasState(ListMember.STATE_OWNER|ListMember.STATE_SUBSCRIBER); + return member.hasState(ListMember.STATE_OWNER|ListMember.STATE_SUBSCRIBER); // owners may subscribe their own mailing lists } catch (SQLException e) { LOG.warn("Was not able to load ListMember: ",e); return false; } } + /** + * load the set of mailing lists a given user is allowed to edit + * @param user + * @return + */ public static Set editableBy(User user) { var list = new HashSet(); for (String key : ListMember.listsOwnedBy(user)) list.add(load(key)); return list; } + /** + * Create a mailing list object from a ResultSet. + * This is a cached method: if the ML has been loaded before, the already-loaded object will be returned. + * + * @param rs + * @return + * @throws SQLException + */ private static MailingList from(ResultSet rs) throws SQLException { String email = rs.getString(EMAIL); var ml = lists.get(email); @@ -122,6 +176,12 @@ public class MailingList { return ml; } + /** + * Load a ML object by it's identifying email address. + * This is a cached method: if the ML has been loaded before, the already-loaded object will be returned. + * @param listEmail + * @return + */ public static MailingList load(String listEmail) { if (listEmail == null) return null; var ml = lists.get(listEmail); @@ -130,13 +190,17 @@ public class MailingList { .select(TABLE_NAME) .where(EMAIL,listEmail) .compile().exec(); - if (rs.next()) lists.put(listEmail,ml = MailingList.from(rs)); + if (rs.next()) ml = MailingList.from(rs); } catch (SQLException e) { LOG.debug("Failed to load MailingList: ",e); } return ml; } + /** + * creates a map from the current ML object containing only email and name of the ML + * @return + */ public HashMap minimalMap() { var map = new HashMap(); String[] parts = email.split("@", 2); @@ -150,17 +214,20 @@ public class MailingList { return name; } + /** + * provide the set of mailing lists that are publicy open to subscriptions + * @return + */ public static Set openLists() { var list = new HashSet(); try { var rs = Database.open() - .select(TABLE_NAME,EMAIL, "(" + STATE + " & " + STATE_PUBLIC + ") as test") + .select(TABLE_NAME,"*", "(" + STATE + " & " + STATE_PUBLIC + ") as test") .where("test", STATE_PUBLIC) - .compile().exec(); - var emails = new ArrayList(); - while (rs.next()) emails.add(rs.getString(EMAIL)); + .compile() + .exec(); + while (rs.next()) list.add(MailingList.from(rs)); rs.close(); - for (String email : emails) list.add(load(email)); } catch (SQLException e) { LOG.warn("Listing mailing lists failed: ", e); } @@ -168,6 +235,10 @@ public class MailingList { } + /** + * creates a map of the current ML containing all fields but passwords. + * @return + */ public Map safeMap() { var map = minimalMap(); if (imapHost != null) map.put(IMAP_HOST, imapHost); @@ -180,27 +251,37 @@ public class MailingList { } - + /** + * save the current ML object to the database + * @return + * @throws SQLException + */ private MailingList save() throws SQLException { Database.open() .insertInto(TABLE_NAME) - .values(Map.ofEntries( - Map.entry(EMAIL, email), - Map.entry(NAME, name), - Map.entry(IMAP_HOST, imapHost), - Map.entry(IMAP_PORT, imapPort), - Map.entry(IMAP_USER, imapUser), - Map.entry(IMAP_PASS, imapPass), - Map.entry(SMTP_HOST, smtp.host()), - Map.entry(SMTP_PORT, smtp.port()), - Map.entry(SMTP_USER, smtp.username()), - Map.entry(SMTP_PASS, smtp.password()), - Map.entry(STATE, state))) + .set(EMAIL, email) + .set(NAME, name) + .set(IMAP_HOST, imapHost) + .set(IMAP_PORT, imapPort) + .set(IMAP_USER, imapUser) + .set(IMAP_PASS, imapPass) + .set(SMTP_HOST, smtp.host()) + .set(SMTP_PORT, smtp.port()) + .set(SMTP_USER, smtp.username()) + .set(SMTP_PASS, smtp.password()) + .set(STATE, state) .compile() .run(); return this; } + /** + * send an email to a potential subscriber asking for confirmation + * @param user + * @param token + * @throws MessagingException + * @throws UnsupportedEncodingException + */ private void sendConfirmationRequest(User user, String token) throws MessagingException, UnsupportedEncodingException { var subject = t("Please confirm your list subscription"); var config = Configuration.instance(); @@ -209,6 +290,11 @@ public class MailingList { smtp.login().send(email(),name(),user.email(),subject,text); } + /** + * convert state to readable string + * @param state + * @return + */ private static String stateString(int state) { var states = new ArrayList(); states.add((state & STATE_ENABLED) == STATE_ENABLED ? "enabled" : "disabled"); @@ -216,10 +302,22 @@ public class MailingList { return String.join(", ", states); } + /** + * Provides the set of MailingLists subscribable to visitors. Returns the same value as subscribable(null); + * @return + */ public static Set subscribable() { return subscribable(null); } + /** + * Provides the set of MailingLists subscribable to a given user. + * If the user is null, the set of openLists is returned. + * If the user is an admin, the set of all MLs is returned. + * Otherwise the set of MLs owned by the given user + the set of openLists is returned + * @param user + * @return + */ public static Set subscribable(User user) { try { if (user == null) return openLists(); @@ -242,24 +340,36 @@ public class MailingList { } } + /** + * Request list subscription for the given user. + * Usually creates a ListMember entry with AWAITING_CONFIRMATION state is created and a confirmation request email is sent. + * However, if skipConfirmation is set to true, no email is sent and the ListMember's state is set to SUBSCRIBER immediately. + * @param user + * @param skipConfirmation + * @throws SQLException + * @throws MessagingException + */ public void requestSubscription(User user, boolean skipConfirmation) throws SQLException, MessagingException { var state = skipConfirmation ? ListMember.STATE_SUBSCRIBER : ListMember.STATE_AWAITING_CONFIRMATION; var member = ListMember.create(this,user,state); if (skipConfirmation) return; - var token = member.token(); try { - sendConfirmationRequest(user, token); + sendConfirmationRequest(user, member.token()); } catch (UnsupportedEncodingException e) { throw new MessagingException("Failed to send email to "+user.email(),e); } } + /** + * send a test email to the provided user + * @param user + * @throws MessagingException + * @throws UnsupportedEncodingException + */ public void test(User user) throws MessagingException, UnsupportedEncodingException { var subject = t("{}: test mail",name()); var text = t("If you received this mail, the SMTP settings of your mailing list are correct."); smtp.login().send(email(),name(),user.email(),subject,text); } - - }