|  |  |  | @ -12,18 +12,30 @@ import java.util.*;@@ -12,18 +12,30 @@ import java.util.*; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | import static de.srsoftware.widerhall.Constants.*; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | /** | 
			
		
	
		
			
				
					|  |  |  |  |  * @author Stephan Richter | 
			
		
	
		
			
				
					|  |  |  |  |  * This class embodies the ListMembers table | 
			
		
	
		
			
				
					|  |  |  |  |  */ | 
			
		
	
		
			
				
					|  |  |  |  | public class ListMember { | 
			
		
	
		
			
				
					|  |  |  |  |     private static final Logger LOG = LoggerFactory.getLogger(ListMember.class); | 
			
		
	
		
			
				
					|  |  |  |  |     public static final String TABLE_NAME = "ListMembers"; | 
			
		
	
		
			
				
					|  |  |  |  |     public static final int STATE_OWNER = 1; | 
			
		
	
		
			
				
					|  |  |  |  |     public static final int STATE_SUBSCRIBER = 2; | 
			
		
	
		
			
				
					|  |  |  |  |     public static final int STATE_AWAITING_CONFIRMATION = 4; | 
			
		
	
		
			
				
					|  |  |  |  |     private static final Logger LOG = LoggerFactory.getLogger(ListMember.class); | 
			
		
	
		
			
				
					|  |  |  |  |     private static final String LIST_EMAIL = "list_email"; | 
			
		
	
		
			
				
					|  |  |  |  |     private static final String USER_EMAIL = "user_email"; | 
			
		
	
		
			
				
					|  |  |  |  |     private static final String STATE = "state"; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     private final String listEmail,token,userEmail; | 
			
		
	
		
			
				
					|  |  |  |  |     private final int state; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * create a new list member object | 
			
		
	
		
			
				
					|  |  |  |  |      * @param listEmail | 
			
		
	
		
			
				
					|  |  |  |  |      * @param userEmail | 
			
		
	
		
			
				
					|  |  |  |  |      * @param state | 
			
		
	
		
			
				
					|  |  |  |  |      * @param token | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     public ListMember(String listEmail, String userEmail, int state, String token){ | 
			
		
	
		
			
				
					|  |  |  |  |         this.listEmail = listEmail; | 
			
		
	
		
			
				
					|  |  |  |  |         this.userEmail = userEmail; | 
			
		
	
	
		
			
				
					|  |  |  | @ -31,10 +43,21 @@ public class ListMember {@@ -31,10 +43,21 @@ public class ListMember { | 
			
		
	
		
			
				
					|  |  |  |  |         this.token = token; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * tries to confirm the token: | 
			
		
	
		
			
				
					|  |  |  |  |      * This method loads the list member, that is assigned with the token. | 
			
		
	
		
			
				
					|  |  |  |  |      * If no db entry is found for the token, null is returned. | 
			
		
	
		
			
				
					|  |  |  |  |      * If an entry is found, the respective User object is loaded. | 
			
		
	
		
			
				
					|  |  |  |  |      * If no User object is assigned, null is returned. | 
			
		
	
		
			
				
					|  |  |  |  |      * If a matching User object is present, the member's state is altered from AWAITING_CONFIRMATION to SUBSCRIBER and the token is dropped. | 
			
		
	
		
			
				
					|  |  |  |  |      * @param token | 
			
		
	
		
			
				
					|  |  |  |  |      * @return | 
			
		
	
		
			
				
					|  |  |  |  |      * @throws SQLException | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     public static User confirm(String token) throws SQLException { | 
			
		
	
		
			
				
					|  |  |  |  |         var rs = Database.open().select(TABLE_NAME).where(TOKEN,token).compile().exec(); | 
			
		
	
		
			
				
					|  |  |  |  |         if (rs.next()){ | 
			
		
	
		
			
				
					|  |  |  |  |             var lm = new ListMember(rs.getString(LIST_EMAIL),rs.getString(USER_EMAIL),rs.getInt(STATE),rs.getString(TOKEN)); | 
			
		
	
		
			
				
					|  |  |  |  |             var lm = ListMember.from(rs); | 
			
		
	
		
			
				
					|  |  |  |  |             rs.close(); | 
			
		
	
		
			
				
					|  |  |  |  |             User user = User.loadAll(List.of(lm.userEmail)).stream().findAny().orElse(null); | 
			
		
	
		
			
				
					|  |  |  |  |             if (user != null){ | 
			
		
	
	
		
			
				
					|  |  |  | @ -54,6 +77,15 @@ public class ListMember {@@ -54,6 +77,15 @@ public class ListMember { | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * Create a new list member entry in the database. | 
			
		
	
		
			
				
					|  |  |  |  |      * If the member has the state AWAITING_CONFIRMATION, a token is assigned with the member, too. | 
			
		
	
		
			
				
					|  |  |  |  |      * @param list | 
			
		
	
		
			
				
					|  |  |  |  |      * @param user | 
			
		
	
		
			
				
					|  |  |  |  |      * @param state | 
			
		
	
		
			
				
					|  |  |  |  |      * @return | 
			
		
	
		
			
				
					|  |  |  |  |      * @throws SQLException | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     public static ListMember create(MailingList list, User user, int state) throws SQLException { | 
			
		
	
		
			
				
					|  |  |  |  |         String token = null; | 
			
		
	
		
			
				
					|  |  |  |  |         if ((state & STATE_AWAITING_CONFIRMATION) > 0){ | 
			
		
	
	
		
			
				
					|  |  |  | @ -62,6 +94,10 @@ public class ListMember {@@ -62,6 +94,10 @@ public class ListMember { | 
			
		
	
		
			
				
					|  |  |  |  |         return new ListMember(list.email(),user.email(),state,token).save(); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * create the table for ListMember objects | 
			
		
	
		
			
				
					|  |  |  |  |      * @throws SQLException | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     public static void createTable() throws SQLException { | 
			
		
	
		
			
				
					|  |  |  |  |         var sql = new StringBuilder() | 
			
		
	
		
			
				
					|  |  |  |  |                 .append("CREATE TABLE ").append(TABLE_NAME) | 
			
		
	
	
		
			
				
					|  |  |  | @ -74,11 +110,35 @@ public class ListMember {@@ -74,11 +110,35 @@ public class ListMember { | 
			
		
	
		
			
				
					|  |  |  |  |         Database.open().query(sql).compile().run(); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * create a new ListMember object from a ResultSet | 
			
		
	
		
			
				
					|  |  |  |  |      * @param rs | 
			
		
	
		
			
				
					|  |  |  |  |      * @return | 
			
		
	
		
			
				
					|  |  |  |  |      * @throws SQLException | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     public static ListMember from(ResultSet rs) throws SQLException { | 
			
		
	
		
			
				
					|  |  |  |  |         return new ListMember( | 
			
		
	
		
			
				
					|  |  |  |  |                 rs.getString(LIST_EMAIL), | 
			
		
	
		
			
				
					|  |  |  |  |                 rs.getString(USER_EMAIL), | 
			
		
	
		
			
				
					|  |  |  |  |                 rs.getInt(STATE), | 
			
		
	
		
			
				
					|  |  |  |  |                 rs.getString(TOKEN)); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * test, if the current object has a given state | 
			
		
	
		
			
				
					|  |  |  |  |      * @param testState | 
			
		
	
		
			
				
					|  |  |  |  |      * @return | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     public boolean hasState(int testState) { | 
			
		
	
		
			
				
					|  |  |  |  |         return (state & testState) > 0; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * return a set of list emails of MailingLists owned by the given user | 
			
		
	
		
			
				
					|  |  |  |  |      * @param user | 
			
		
	
		
			
				
					|  |  |  |  |      * @return | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     public static Set<String> listsOwnedBy(User user) { | 
			
		
	
		
			
				
					|  |  |  |  |         var list = new HashSet<String>(); | 
			
		
	
		
			
				
					|  |  |  |  |         try { | 
			
		
	
	
		
			
				
					|  |  |  | @ -92,6 +152,13 @@ public class ListMember {@@ -92,6 +152,13 @@ public class ListMember { | 
			
		
	
		
			
				
					|  |  |  |  |         return list; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * load the list member specified by a MailingList and as User object | 
			
		
	
		
			
				
					|  |  |  |  |      * @param list | 
			
		
	
		
			
				
					|  |  |  |  |      * @param user | 
			
		
	
		
			
				
					|  |  |  |  |      * @return | 
			
		
	
		
			
				
					|  |  |  |  |      * @throws SQLException | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     public static ListMember load(MailingList list,User user) throws SQLException { | 
			
		
	
		
			
				
					|  |  |  |  |         var rs = Database | 
			
		
	
		
			
				
					|  |  |  |  |                 .open() | 
			
		
	
	
		
			
				
					|  |  |  | @ -101,19 +168,21 @@ public class ListMember {@@ -101,19 +168,21 @@ public class ListMember { | 
			
		
	
		
			
				
					|  |  |  |  |                 .compile() | 
			
		
	
		
			
				
					|  |  |  |  |                 .exec(); | 
			
		
	
		
			
				
					|  |  |  |  |         try { | 
			
		
	
		
			
				
					|  |  |  |  |             if (rs.next()) { | 
			
		
	
		
			
				
					|  |  |  |  |                 return new ListMember(rs.getString(LIST_EMAIL), | 
			
		
	
		
			
				
					|  |  |  |  |                         rs.getString(USER_EMAIL), | 
			
		
	
		
			
				
					|  |  |  |  |                         rs.getInt(STATE), | 
			
		
	
		
			
				
					|  |  |  |  |                         rs.getString(TOKEN)); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |             if (rs.next()) return ListMember.from(rs); | 
			
		
	
		
			
				
					|  |  |  |  |         } finally { | 
			
		
	
		
			
				
					|  |  |  |  |             rs.close(); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         return null; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * return a map of User → State for a given MailingList | 
			
		
	
		
			
				
					|  |  |  |  |      * @param listEmail | 
			
		
	
		
			
				
					|  |  |  |  |      * @return | 
			
		
	
		
			
				
					|  |  |  |  |      * @throws SQLException | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     public static Map<User,Integer> of(String listEmail) throws SQLException { | 
			
		
	
		
			
				
					|  |  |  |  |         // Step 1: create mal USER_EMAIL → STATE for MailingList
 | 
			
		
	
		
			
				
					|  |  |  |  |         var rs = Database.open() | 
			
		
	
		
			
				
					|  |  |  |  |                 .select(TABLE_NAME) | 
			
		
	
		
			
				
					|  |  |  |  |                 .where(LIST_EMAIL,listEmail) | 
			
		
	
	
		
			
				
					|  |  |  | @ -121,6 +190,8 @@ public class ListMember {@@ -121,6 +190,8 @@ public class ListMember { | 
			
		
	
		
			
				
					|  |  |  |  |                 .exec(); | 
			
		
	
		
			
				
					|  |  |  |  |         var temp = new HashMap<String,Integer>(); | 
			
		
	
		
			
				
					|  |  |  |  |         while (rs.next()) temp.put(rs.getString(USER_EMAIL),rs.getInt(STATE)); | 
			
		
	
		
			
				
					|  |  |  |  |         rs.close(); | 
			
		
	
		
			
				
					|  |  |  |  |         // Step 2: map user emails to users
 | 
			
		
	
		
			
				
					|  |  |  |  |         var result = new HashMap<User,Integer>(); | 
			
		
	
		
			
				
					|  |  |  |  |         User.loadAll(temp.keySet()) | 
			
		
	
		
			
				
					|  |  |  |  |                 .stream() | 
			
		
	
	
		
			
				
					|  |  |  | @ -128,6 +199,27 @@ public class ListMember {@@ -128,6 +199,27 @@ public class ListMember { | 
			
		
	
		
			
				
					|  |  |  |  |         return result; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * Save the current ListMember object to the database | 
			
		
	
		
			
				
					|  |  |  |  |      * @return | 
			
		
	
		
			
				
					|  |  |  |  |      * @throws SQLException | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     private ListMember save() throws SQLException { | 
			
		
	
		
			
				
					|  |  |  |  |         var req = Database.open() | 
			
		
	
		
			
				
					|  |  |  |  |                 .insertInto(TABLE_NAME) | 
			
		
	
		
			
				
					|  |  |  |  |                 .set(LIST_EMAIL,listEmail) | 
			
		
	
		
			
				
					|  |  |  |  |                 .set(USER_EMAIL,userEmail) | 
			
		
	
		
			
				
					|  |  |  |  |                 .set(STATE,state); | 
			
		
	
		
			
				
					|  |  |  |  |         if (token != null) req.set(TOKEN,token); | 
			
		
	
		
			
				
					|  |  |  |  |         req.compile().run(); | 
			
		
	
		
			
				
					|  |  |  |  |         return this; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * convert state flag to readable text | 
			
		
	
		
			
				
					|  |  |  |  |      * @param state | 
			
		
	
		
			
				
					|  |  |  |  |      * @return | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     public static String stateText(int state) { | 
			
		
	
		
			
				
					|  |  |  |  |         var words = new ArrayList<String>(); | 
			
		
	
		
			
				
					|  |  |  |  |         if ((state & STATE_OWNER) > 0) words.add("owner"); | 
			
		
	
	
		
			
				
					|  |  |  | @ -136,20 +228,20 @@ public class ListMember {@@ -136,20 +228,20 @@ public class ListMember { | 
			
		
	
		
			
				
					|  |  |  |  |         return String.join(", ",words); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     private ListMember save() throws SQLException { | 
			
		
	
		
			
				
					|  |  |  |  |         var vals = new HashMap<String,Object>(); | 
			
		
	
		
			
				
					|  |  |  |  |         vals.put(LIST_EMAIL,listEmail); | 
			
		
	
		
			
				
					|  |  |  |  |         vals.put(USER_EMAIL,userEmail); | 
			
		
	
		
			
				
					|  |  |  |  |         vals.put(STATE,state); | 
			
		
	
		
			
				
					|  |  |  |  |         if (token != null) vals.put(TOKEN,token); | 
			
		
	
		
			
				
					|  |  |  |  |         Database.open().insertInto(TABLE_NAME).values(vals).compile().run(); | 
			
		
	
		
			
				
					|  |  |  |  |         return this; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * get the token of the current list member | 
			
		
	
		
			
				
					|  |  |  |  |      * @return | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     public String token() { | 
			
		
	
		
			
				
					|  |  |  |  |         return token; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     /** | 
			
		
	
		
			
				
					|  |  |  |  |      * unsubscribe a list member | 
			
		
	
		
			
				
					|  |  |  |  |      * @param list | 
			
		
	
		
			
				
					|  |  |  |  |      * @param user | 
			
		
	
		
			
				
					|  |  |  |  |      * @throws SQLException | 
			
		
	
		
			
				
					|  |  |  |  |      */ | 
			
		
	
		
			
				
					|  |  |  |  |     public static void unsubscribe(MailingList list, User user) throws SQLException { | 
			
		
	
		
			
				
					|  |  |  |  |         var db = Database.open(); | 
			
		
	
		
			
				
					|  |  |  |  |         var rs = db.select(TABLE_NAME) | 
			
		
	
	
		
			
				
					|  |  |  | @ -158,21 +250,9 @@ public class ListMember {@@ -158,21 +250,9 @@ public class ListMember { | 
			
		
	
		
			
				
					|  |  |  |  |                 .compile() | 
			
		
	
		
			
				
					|  |  |  |  |                 .exec(); | 
			
		
	
		
			
				
					|  |  |  |  |         while (rs.next()){ | 
			
		
	
		
			
				
					|  |  |  |  |             int state = Util.unset(rs.getInt(STATE),STATE_SUBSCRIBER,STATE_AWAITING_CONFIRMATION); | 
			
		
	
		
			
				
					|  |  |  |  |             if (state < 1) { // drop entry
 | 
			
		
	
		
			
				
					|  |  |  |  |                 db.deleteFrom(TABLE_NAME) | 
			
		
	
		
			
				
					|  |  |  |  |                         .where(LIST_EMAIL,list.email()) | 
			
		
	
		
			
				
					|  |  |  |  |                         .where(USER_EMAIL,user.email()) | 
			
		
	
		
			
				
					|  |  |  |  |                         .compile() | 
			
		
	
		
			
				
					|  |  |  |  |                         .run(); | 
			
		
	
		
			
				
					|  |  |  |  |             } else { // update entry: whitdraw subscription
 | 
			
		
	
		
			
				
					|  |  |  |  |                 db.update(TABLE_NAME) | 
			
		
	
		
			
				
					|  |  |  |  |                         .set(STATE,state) | 
			
		
	
		
			
				
					|  |  |  |  |                         .where(LIST_EMAIL,list.email()) | 
			
		
	
		
			
				
					|  |  |  |  |                         .where(USER_EMAIL,user.email()) | 
			
		
	
		
			
				
					|  |  |  |  |                         .compile() | 
			
		
	
		
			
				
					|  |  |  |  |                         .run(); | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |             int state = Util.unset(rs.getInt(STATE),STATE_SUBSCRIBER,STATE_AWAITING_CONFIRMATION); // drop subscription and awaiting flags
 | 
			
		
	
		
			
				
					|  |  |  |  |             var req = state < 1 ? db.deleteFrom(TABLE_NAME) : db.update(TABLE_NAME).set(STATE,state).set(TOKEN,null); | 
			
		
	
		
			
				
					|  |  |  |  |             req.where(LIST_EMAIL,list.email()).where(USER_EMAIL,user.email()).compile().run(); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
	
		
			
				
					|  |  |  | 
 |