* list members may be nominated as moderators by admin
* admin may allow moderators to nominate more moderators
* admin may set allowed senders to one of the following:
* owners and mods
* all subscribers
* everyone
* moderators are now able to remove members from list
413 lines
14 KiB
Java
413 lines
14 KiB
Java
package de.srsoftware.widerhall.data;
|
|
|
|
import de.srsoftware.widerhall.Util;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import java.sql.ResultSet;
|
|
import java.sql.SQLException;
|
|
import java.util.*;
|
|
|
|
import static de.srsoftware.widerhall.Constants.*;
|
|
import static de.srsoftware.widerhall.Util.t;
|
|
|
|
/**
|
|
* @author Stephan Richter
|
|
* This class embodies the ListMembers table
|
|
*/
|
|
public class ListMember {
|
|
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;
|
|
public static final int STATE_MODERATOR = 8;
|
|
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 MailingList list;
|
|
private User user;
|
|
private final String token;
|
|
private final int state;
|
|
|
|
/**
|
|
* create a new list member object
|
|
* @param list
|
|
* @param user
|
|
* @param state
|
|
* @param token
|
|
*/
|
|
public ListMember(MailingList list, User user, int state, String token){
|
|
this.list = list;
|
|
this.user = user;
|
|
this.state = state;
|
|
this.token = token;
|
|
}
|
|
|
|
public String addNewModerator(String userEmail) {
|
|
if (!isAllowedToEditMods()) return t("You are not allowed to nominate new mods for {}",list.email());
|
|
User moderator = null;
|
|
try {
|
|
moderator = User.load(userEmail);
|
|
} catch (SQLException e) {
|
|
LOG.warn("Failed to load user for {}",userEmail,e);
|
|
return t("Failed to load user for {}",userEmail);
|
|
}
|
|
if (moderator == null) return t("No such user: {}",userEmail);
|
|
|
|
ListMember member = null;
|
|
try {
|
|
member = ListMember.load(list,moderator);
|
|
} catch (SQLException e) {
|
|
LOG.warn("Failed to load list member for {}/{}",moderator.email(),list.email(),e);
|
|
return t("Failed to load list member for {}/{}",moderator.email(),list.email());
|
|
}
|
|
try {
|
|
if (member == null) {
|
|
ListMember.create(list, moderator, ListMember.STATE_MODERATOR);
|
|
} else {
|
|
member.setState(ListMember.STATE_MODERATOR);
|
|
}
|
|
} catch (SQLException e) {
|
|
LOG.warn("Failed to make {} a moderator of {}",moderator.email(),list.email(),e);
|
|
return t("Failed to make {} a moderator of {}",moderator.email(),list.email());
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 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 = ListMember.from(rs);
|
|
rs.close();
|
|
if (lm.user != null){
|
|
int newState = lm.state ^ STATE_AWAITING_CONFIRMATION | STATE_SUBSCRIBER;
|
|
Database.open()
|
|
.update(TABLE_NAME)
|
|
.set(TOKEN,null)
|
|
.set(STATE, newState) //drop confirmation state, set subscriber state
|
|
.where(LIST_EMAIL,lm.list.email())
|
|
.where(USER_EMAIL,lm.user.email())
|
|
.compile()
|
|
.run();
|
|
}
|
|
return lm.user;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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){
|
|
token = Util.sha256(String.join("/",list.email(),user.email(),user.salt()));
|
|
}
|
|
return new ListMember(list,user,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)
|
|
.append(" (")
|
|
.append(LIST_EMAIL).append(" ").append(VARCHAR).append(", ")
|
|
.append(USER_EMAIL).append(" ").append(VARCHAR).append(", ")
|
|
.append(STATE).append(" ").append(INT).append(", ")
|
|
.append(TOKEN).append(" ").append(VARCHAR).append(", ")
|
|
.append("PRIMARY KEY (").append(LIST_EMAIL).append(", ").append(USER_EMAIL).append("));");
|
|
Database.open().query(sql).compile().run();
|
|
}
|
|
|
|
public String dropMember(String userEmail) {
|
|
if (!isModerator()) return t("You are not allowed to remove members of {}",list.email());
|
|
User user = null;
|
|
try {
|
|
user = User.load(userEmail);
|
|
} catch (SQLException e) {
|
|
LOG.warn("Failed to load user for {}",userEmail,e);
|
|
return t("Failed to load user for {}",userEmail);
|
|
}
|
|
if (user == null) return t("No such user: {}",userEmail);
|
|
|
|
ListMember member = null;
|
|
try {
|
|
member = ListMember.load(list,user);
|
|
} catch (SQLException e) {
|
|
LOG.warn("Failed to load list member for {}/{}",user.email(),list.email(),e);
|
|
return t("Failed to load list member for {}/{}",user.email(),list.email());
|
|
}
|
|
|
|
if (member == null) return t("{} is no member of {}",user.email(),list.email());
|
|
if (member.isOwner()) return t("You are not allowed to remvoe the list owner!");
|
|
|
|
try {
|
|
member.unsubscribe();
|
|
} catch (SQLException e) {
|
|
LOG.warn("Failed to un-subscribe {} from {}",user.email(),list.email(),e);
|
|
return t("Failed to un-subscribe {} from {}",user.email(),list.email());
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public String dropModerator(String userEmail) {
|
|
if (!isAllowedToEditMods()) return t("You are not allowed to edit mods of {}",list.email());
|
|
User moderator = null;
|
|
try {
|
|
moderator = User.load(userEmail);
|
|
} catch (SQLException e) {
|
|
LOG.warn("Failed to load user for {}",userEmail,e);
|
|
return t("Failed to load user for {}",userEmail);
|
|
}
|
|
if (moderator == null) return t("No such user: {}",userEmail);
|
|
|
|
ListMember member = null;
|
|
try {
|
|
member = ListMember.load(list,moderator);
|
|
} catch (SQLException e) {
|
|
LOG.warn("Failed to load list member for {}/{}",moderator.email(),list.email(),e);
|
|
return t("Failed to load list member for {}/{}",moderator.email(),list.email());
|
|
}
|
|
try {
|
|
if (member == null) {
|
|
ListMember.create(list, moderator, ListMember.STATE_SUBSCRIBER);
|
|
} else {
|
|
member.setState(ListMember.STATE_SUBSCRIBER);
|
|
}
|
|
} catch (SQLException e) {
|
|
LOG.warn("Failed to make {} a subscriber of {}",moderator.email(),list.email(),e);
|
|
return t("Failed to make {} a subscriber of {}",moderator.email(),list.email());
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* create a new ListMember object from a ResultSet
|
|
* @param rs
|
|
* @return
|
|
* @throws SQLException
|
|
*/
|
|
public static ListMember from(ResultSet rs) throws SQLException {
|
|
return new ListMember(
|
|
MailingList.load(rs.getString(LIST_EMAIL)),
|
|
User.load(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;
|
|
}
|
|
|
|
public boolean isAllowedToEditMods(){
|
|
if (isOwner()) return true;
|
|
if (isModerator()) return list.modsMayEditMods();
|
|
return false;
|
|
}
|
|
|
|
public boolean isAwaiting(){
|
|
return hasState(STATE_AWAITING_CONFIRMATION);
|
|
}
|
|
|
|
public boolean isModerator() {
|
|
return hasState(STATE_OWNER|STATE_MODERATOR);
|
|
}
|
|
|
|
public boolean isOwner(){
|
|
return hasState(STATE_OWNER);
|
|
}
|
|
|
|
public boolean isSubscriber(){
|
|
return hasState(STATE_SUBSCRIBER|STATE_MODERATOR|STATE_OWNER);
|
|
}
|
|
|
|
public MailingList list(){
|
|
return list;
|
|
}
|
|
/**
|
|
* return a set of list emails of MailingLists the given user attends
|
|
* @param user
|
|
* @return
|
|
*/
|
|
public static Set<ListMember> listsOf(User user) {
|
|
var listEmails = new HashSet<String>();
|
|
try {
|
|
var request = Database.open()
|
|
.select(TABLE_NAME);
|
|
if (!user.hashPermission(User.PERMISSION_ADMIN)) request = request.where(USER_EMAIL, user.email());
|
|
var rs = request.compile().exec();
|
|
while (rs.next()) listEmails.add(rs.getString(LIST_EMAIL));
|
|
} catch (SQLException e) {
|
|
LOG.warn("Collecting lists of {} failed: ",user.email(),e);
|
|
}
|
|
var lists = MailingList.loadAll(listEmails);
|
|
var result = new HashSet<ListMember>();
|
|
try {
|
|
for (var ml : lists) result.add(ListMember.load(ml,user));
|
|
} catch (SQLException e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
var request = Database.open().select(TABLE_NAME, LIST_EMAIL, STATE+" & "+STATE_OWNER+" as "+STATE);
|
|
if (!user.hashPermission(User.PERMISSION_ADMIN)) request = request.where(USER_EMAIL, user.email()).where(STATE, STATE_OWNER);
|
|
var rs = request.compile().exec();
|
|
while (rs.next()) list.add(rs.getString(LIST_EMAIL));
|
|
} catch (SQLException e) {
|
|
LOG.warn("Collecting lists of {} failed: ",user.email(),e);
|
|
}
|
|
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()
|
|
.select(TABLE_NAME)
|
|
.where(LIST_EMAIL,list.email())
|
|
.where(USER_EMAIL,user.email())
|
|
.compile()
|
|
.exec();
|
|
try {
|
|
if (rs.next()) return ListMember.from(rs);
|
|
} finally {
|
|
rs.close();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
public static Set<ListMember> of(MailingList list) throws SQLException {
|
|
var rs = Database.open().select(TABLE_NAME).where(LIST_EMAIL,list.email()).compile().exec();
|
|
var set = new HashSet<ListMember>();
|
|
try {
|
|
while (rs.next()) set.add(ListMember.from(rs));
|
|
} finally {
|
|
rs.close();
|
|
}
|
|
return set;
|
|
}
|
|
|
|
public Map<String,Object> safeMap(){
|
|
return Map.of(
|
|
EMAIL,user.email(),
|
|
NAME,user.name(),
|
|
STATE,stateText()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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,list.email())
|
|
.set(USER_EMAIL,user.email())
|
|
.set(STATE,state);
|
|
if (token != null) req.set(TOKEN,token);
|
|
req.compile().run();
|
|
return this;
|
|
}
|
|
|
|
public ListMember setState(int newState) throws SQLException {
|
|
Database.open()
|
|
.update(TABLE_NAME)
|
|
.set(STATE,newState)
|
|
.where(USER_EMAIL,user.email())
|
|
.where(LIST_EMAIL,list.email())
|
|
.compile()
|
|
.run();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* convert state flag to readable text
|
|
* @return
|
|
*/
|
|
public String stateText() {
|
|
var words = new ArrayList<String>();
|
|
if (isAwaiting()) words.add("awaiting confirmation");
|
|
if (isModerator()) words.add("moderator");
|
|
if (isOwner()) words.add("owner");
|
|
if (isSubscriber()) words.add("subscriber");
|
|
return String.join(", ",words);
|
|
}
|
|
|
|
/**
|
|
* get the token of the current list member
|
|
* @return
|
|
*/
|
|
public String token() {
|
|
return token;
|
|
}
|
|
|
|
/**
|
|
* unsubscribe a list member
|
|
* @throws SQLException
|
|
*/
|
|
public void unsubscribe() throws SQLException {
|
|
var db = Database.open();
|
|
var rs = db.select(TABLE_NAME)
|
|
.where(LIST_EMAIL,list.email())
|
|
.where(USER_EMAIL,user.email())
|
|
.compile()
|
|
.exec();
|
|
while (rs.next()){
|
|
int state = Util.unset(rs.getInt(STATE),STATE_SUBSCRIBER,STATE_MODERATOR,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();
|
|
}
|
|
}
|
|
|
|
public User user(){
|
|
return user;
|
|
}
|
|
}
|