Browse Source

added tests, preparing submission confirmation

drop_old_mail
Stephan Richter 3 years ago
parent
commit
e2bba174ee
  1. 141
      doc/data structure.dia
  2. 6
      src/main/java/de/srsoftware/widerhall/Configuration.java
  3. 82
      src/main/java/de/srsoftware/widerhall/data/Database.java
  4. 28
      src/main/java/de/srsoftware/widerhall/data/ListMember.java
  5. 39
      src/main/java/de/srsoftware/widerhall/data/MailingList.java
  6. 86
      src/main/java/de/srsoftware/widerhall/data/User.java
  7. 7
      src/main/java/de/srsoftware/widerhall/web/Rest.java
  8. 46
      src/main/java/de/srsoftware/widerhall/web/Web.java
  9. 57
      src/test/java/de/srsoftware/widerhall/data/DatabaseTest.java

141
doc/data structure.dia

@ -1048,13 +1048,13 @@ @@ -1048,13 +1048,13 @@
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O22">
<dia:attribute name="obj_pos">
<dia:point val="22,16"/>
<dia:point val="22,18"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="21.95,15.95;27.05,18.05"/>
<dia:rectangle val="21.95,17.95;27.05,20.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="22,16"/>
<dia:point val="22,18"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="5"/>
@ -1080,7 +1080,7 @@ @@ -1080,7 +1080,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="24.5,17.1941"/>
<dia:point val="24.5,19.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1093,13 +1093,13 @@ @@ -1093,13 +1093,13 @@
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O23">
<dia:attribute name="obj_pos">
<dia:point val="23,20"/>
<dia:point val="23,22"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,19.95;27.05,22.05"/>
<dia:rectangle val="22.95,21.95;27.05,24.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,20"/>
<dia:point val="23,22"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
@ -1125,7 +1125,7 @@ @@ -1125,7 +1125,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,21.1941"/>
<dia:point val="25,23.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1138,13 +1138,13 @@ @@ -1138,13 +1138,13 @@
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O24">
<dia:attribute name="obj_pos">
<dia:point val="23,22"/>
<dia:point val="23,24"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,21.95;27.05,24.05"/>
<dia:rectangle val="22.95,23.95;27.05,26.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,22"/>
<dia:point val="23,24"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
@ -1170,7 +1170,7 @@ @@ -1170,7 +1170,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,23.1941"/>
<dia:point val="25,25.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1183,13 +1183,13 @@ @@ -1183,13 +1183,13 @@
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O25">
<dia:attribute name="obj_pos">
<dia:point val="23,18"/>
<dia:point val="23,20"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,17.95;27.05,20.05"/>
<dia:rectangle val="22.95,19.95;27.05,22.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,18"/>
<dia:point val="23,20"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
@ -1215,7 +1215,7 @@ @@ -1215,7 +1215,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,19.1941"/>
<dia:point val="25,21.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1228,16 +1228,16 @@ @@ -1228,16 +1228,16 @@
</dia:object>
<dia:object type="Standard - ZigZagLine" version="1" id="O26">
<dia:attribute name="obj_pos">
<dia:point val="27.0488,21"/>
<dia:point val="27.0488,23"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="16.95,8.1382;29.05,21.05"/>
<dia:rectangle val="16.95,8.1382;29.05,23.05"/>
</dia:attribute>
<dia:attribute name="orth_points">
<dia:point val="27.0488,21"/>
<dia:point val="29,21"/>
<dia:point val="29,15"/>
<dia:point val="19,15"/>
<dia:point val="27.0488,23"/>
<dia:point val="29,23"/>
<dia:point val="29,17"/>
<dia:point val="19,17"/>
<dia:point val="19,8.5"/>
<dia:point val="17,8.5"/>
</dia:attribute>
@ -1267,13 +1267,13 @@ @@ -1267,13 +1267,13 @@
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O27">
<dia:attribute name="obj_pos">
<dia:point val="23,24"/>
<dia:point val="23,26"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,23.95;27.05,26.05"/>
<dia:rectangle val="22.95,25.95;27.05,28.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,24"/>
<dia:point val="23,26"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
@ -1299,7 +1299,7 @@ @@ -1299,7 +1299,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,25.1941"/>
<dia:point val="25,27.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1312,13 +1312,13 @@ @@ -1312,13 +1312,13 @@
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O28">
<dia:attribute name="obj_pos">
<dia:point val="23,26"/>
<dia:point val="23,28"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,25.95;27.05,28.05"/>
<dia:rectangle val="22.95,27.95;27.05,30.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,26"/>
<dia:point val="23,28"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
@ -1344,7 +1344,7 @@ @@ -1344,7 +1344,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,27.1941"/>
<dia:point val="25,29.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1357,13 +1357,13 @@ @@ -1357,13 +1357,13 @@
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O29">
<dia:attribute name="obj_pos">
<dia:point val="23,28"/>
<dia:point val="23,30"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,27.95;27.05,30.05"/>
<dia:rectangle val="22.95,29.95;27.05,32.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,28"/>
<dia:point val="23,30"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
@ -1389,7 +1389,7 @@ @@ -1389,7 +1389,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,29.1941"/>
<dia:point val="25,31.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1402,13 +1402,13 @@ @@ -1402,13 +1402,13 @@
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O30">
<dia:attribute name="obj_pos">
<dia:point val="23,30"/>
<dia:point val="23,32"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="22.95,29.95;27.05,32.05"/>
<dia:rectangle val="22.95,31.95;27.05,34.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="23,30"/>
<dia:point val="23,32"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="4"/>
@ -1434,7 +1434,7 @@ @@ -1434,7 +1434,7 @@
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="25,31.1941"/>
<dia:point val="25,33.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
@ -1447,16 +1447,16 @@ @@ -1447,16 +1447,16 @@
</dia:object>
<dia:object type="Standard - ZigZagLine" version="1" id="O31">
<dia:attribute name="obj_pos">
<dia:point val="22.9512,25"/>
<dia:point val="22.9512,27"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="20.95,18.6382;23.0012,25.05"/>
<dia:rectangle val="20.95,20.6382;23.0012,27.05"/>
</dia:attribute>
<dia:attribute name="orth_points">
<dia:point val="22.9512,25"/>
<dia:point val="21,25"/>
<dia:point val="21,19"/>
<dia:point val="22.9512,19"/>
<dia:point val="22.9512,27"/>
<dia:point val="21,27"/>
<dia:point val="21,21"/>
<dia:point val="22.9512,21"/>
</dia:attribute>
<dia:attribute name="orth_orient">
<dia:enum val="0"/>
@ -1482,14 +1482,14 @@ @@ -1482,14 +1482,14 @@
</dia:object>
<dia:object type="Standard - ZigZagLine" version="1" id="O32">
<dia:attribute name="obj_pos">
<dia:point val="27.0449,23"/>
<dia:point val="27.0449,25"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="26.9949,8.1382;33.05,23.05"/>
<dia:rectangle val="26.9949,8.1382;33.05,25.05"/>
</dia:attribute>
<dia:attribute name="orth_points">
<dia:point val="27.0449,23"/>
<dia:point val="31,23"/>
<dia:point val="27.0449,25"/>
<dia:point val="31,25"/>
<dia:point val="31,8.5"/>
<dia:point val="33,8.5"/>
</dia:attribute>
@ -1515,5 +1515,50 @@ @@ -1515,5 +1515,50 @@
<dia:connection handle="1" to="O4" connection="9"/>
</dia:connections>
</dia:object>
<dia:object type="Flowchart - Box" version="0" id="O33">
<dia:attribute name="obj_pos">
<dia:point val="22,13"/>
</dia:attribute>
<dia:attribute name="obj_bb">
<dia:rectangle val="21.95,12.95;27.05,15.05"/>
</dia:attribute>
<dia:attribute name="elem_corner">
<dia:point val="22,13"/>
</dia:attribute>
<dia:attribute name="elem_width">
<dia:real val="5"/>
</dia:attribute>
<dia:attribute name="elem_height">
<dia:real val="2"/>
</dia:attribute>
<dia:attribute name="show_background">
<dia:boolean val="true"/>
</dia:attribute>
<dia:attribute name="padding">
<dia:real val="0.5"/>
</dia:attribute>
<dia:attribute name="text">
<dia:composite type="text">
<dia:attribute name="string">
<dia:string>#Token#</dia:string>
</dia:attribute>
<dia:attribute name="font">
<dia:font family="sans" style="0" name="Helvetica"/>
</dia:attribute>
<dia:attribute name="height">
<dia:real val="0.80000000000000004"/>
</dia:attribute>
<dia:attribute name="pos">
<dia:point val="24.5,14.1941"/>
</dia:attribute>
<dia:attribute name="color">
<dia:color val="#000000ff"/>
</dia:attribute>
<dia:attribute name="alignment">
<dia:enum val="1"/>
</dia:attribute>
</dia:composite>
</dia:attribute>
</dia:object>
</dia:layer>
</dia:diagram>

6
src/main/java/de/srsoftware/widerhall/Configuration.java

@ -86,6 +86,12 @@ public class Configuration { @@ -86,6 +86,12 @@ public class Configuration {
return new File((String) locations.get(DB));
}
public Configuration dbFile(File dbFile){
var locations = locations();
locations.put(DB,dbFile.toString());
return this;
}
public int serverPort() {
if (!data.containsKey(PORT)) data.put(PORT,80L);
var o = data.get(PORT);

82
src/main/java/de/srsoftware/widerhall/data/Database.java

@ -11,7 +11,6 @@ import java.sql.SQLException; @@ -11,7 +11,6 @@ import java.sql.SQLException;
import java.util.*;
import static de.srsoftware.widerhall.Util.t;
import static de.srsoftware.widerhall.Constants.*;
public class Database {
public static final String HASHED_PASS = "hashedPassword";
@ -21,28 +20,26 @@ public class Database { @@ -21,28 +20,26 @@ public class Database {
private static Database singleton = null;
private final Connection conn;
public Request insertInto(String tbName) {
return query("INSERT INTO "+tbName);
}
public class Request{
private final String sql;
private final StringBuilder sql;
private final HashMap<String, List<Object>> where = new HashMap<>();
private final HashMap<String,Object> values = new HashMap<>();
private final HashMap<String,Object> setValues = new HashMap<>();
public Request(String sql) {
this.sql = sql;
this(new StringBuilder(sql));
}
public Request(StringBuilder sql) {
this.sql = sql;
}
public ResultSet exec() throws SQLException {
var sb = new StringBuilder(sql);
var args = new ArrayList<>();
if (!where.isEmpty()){
var clauses = new ArrayList<String>();
sb.append(" WHERE ");
sql.append(" WHERE ");
for (var entry : where.entrySet()){
var arr = new String[entry.getValue().size()];
@ -51,13 +48,12 @@ public class Database { @@ -51,13 +48,12 @@ public class Database {
clauses.add("("+entry.getKey()+" IN ("+marks+"))");
args.addAll(entry.getValue());
}
sb.append(String.join(" AND ",clauses));
sql.append(String.join(" AND ",clauses));
}
var sql = sb.toString();
LOG.debug(sql);
LOG.debug("SQL: {}",sql);
try {
var stmt = Database.this.conn.prepareStatement(sql);
var stmt = Database.this.conn.prepareStatement(sql());
if (!args.isEmpty()) {
for (int i = 0; i < args.size(); i++) stmt.setObject(i+1, args.get(i));
}
@ -68,7 +64,6 @@ public class Database { @@ -68,7 +64,6 @@ public class Database {
}
public void run() throws SQLException {
var sb = new StringBuilder(sql);
var args = new ArrayList<>();
if (!setValues.isEmpty()){
@ -78,7 +73,7 @@ public class Database { @@ -78,7 +73,7 @@ public class Database {
expressions.add(" SET "+entry.getKey()+" = ?");
args.add(entry.getValue());
}
sb.append(String.join(", ",expressions));
sql.append(String.join(", ",expressions));
}
if (!values.isEmpty()){
@ -87,19 +82,19 @@ public class Database { @@ -87,19 +82,19 @@ public class Database {
keys.add(entry.getKey());
args.add(entry.getValue());
}
sb.append("(")
sql.append("(")
.append(String.join(", ",keys))
.append(")")
.append(" VALUES ");
var arr = new String[args.size()];
Arrays.fill(arr,"?");
var marks = String.join(", ",arr);
sb.append("(").append(marks).append(")");
sql.append("(").append(marks).append(")");
}
if (!where.isEmpty()){
var clauses = new ArrayList<String>();
sb.append(" WHERE ");
sql.append(" WHERE ");
for (var entry : where.entrySet()){
var arr = new String[entry.getValue().size()];
@ -108,13 +103,12 @@ public class Database { @@ -108,13 +103,12 @@ public class Database {
clauses.add("("+entry.getKey()+" IN ("+marks+"))");
args.addAll(entry.getValue());
}
sb.append(String.join(" AND ",clauses));
sql.append(String.join(" AND ",clauses));
}
var sql = sb.toString();
LOG.debug(sql);
LOG.debug("SQL: {}",sql);
try {
var stmt = conn.prepareStatement(sql);
var stmt = conn.prepareStatement(sql());
if (!args.isEmpty()) {
for (int i = 0; i < args.size(); i++) stmt.setObject(i+1, args.get(i));
}
@ -129,6 +123,10 @@ public class Database { @@ -129,6 +123,10 @@ public class Database {
return this;
}
public String sql() {
return sql.toString();
}
public Request where(String key, Object ... values) {
for (var val : values) where(key,val);
@ -169,6 +167,18 @@ public class Database { @@ -169,6 +167,18 @@ public class Database {
return this;
}
public Request deleteFrom(String tableName){
return new Request(new StringBuilder("DELETE FROM ").append(tableName).append(" "));
}
public Request insertInto(String tableName){
return new Request(new StringBuilder("INSERT INTO ").append(tableName).append(" "));
}
public boolean isOpen() {
return conn instanceof Connection;
}
public static Database open() {
if (singleton == null){
Configuration config = Configuration.instance();
@ -186,13 +196,27 @@ public class Database { @@ -186,13 +196,27 @@ public class Database {
return singleton;
}
public Request query(String sql) {
public Request query(StringBuilder sql) {
return new Request(sql);
}
private boolean tableExists(String tbName) throws SQLException {
public Request select(String tableName,String ... fields) {
StringBuilder sql = new StringBuilder("SELECT ");
if (fields == null || fields.length == 0){
sql.append("*");
} else {
sql.append(String.join(", ",fields));
}
return new Request(sql.append(" FROM ").append(tableName));
}
public boolean tableExists(String tbName) throws SQLException {
try {
ResultSet rs = query("SELECT EXISTS (SELECT name FROM sqlite_schema WHERE type='table' AND name='" + tbName + "')").exec();
var sql = new StringBuilder("SELECT EXISTS (SELECT name FROM sqlite_schema WHERE type='table' AND name='")
.append(tbName)
.append("')");
ResultSet rs = query( sql).exec();
int val = 0;
if (rs.next()) val = rs.getInt(1);
rs.close();
@ -201,4 +225,12 @@ public class Database { @@ -201,4 +225,12 @@ public class Database {
throw new SQLException(t("Was not able to check existence of table {}!",tbName),e);
}
}
public Request update(String tableName,String ...expressions) {
var sql = new StringBuilder("UPDATE ").append(tableName);
if (expressions != null && expressions.length > 0) {
sql.append(" SET ").append(String.join(", ",expressions));
}
return new Request(sql);
}
}

28
src/main/java/de/srsoftware/widerhall/data/ListMember.java

@ -19,9 +19,11 @@ public class ListMember { @@ -19,9 +19,11 @@ 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_UNCONFIRMED = 4;
private static final String LIST_EMAIL = "list_email";
private static final String USER_EMAIL = "user_email";
private static final String STATE = "state";
private static final String TOKEN = "token";
private final String listEmail;
private final String userEmail;
private final int state;
@ -32,8 +34,8 @@ public class ListMember { @@ -32,8 +34,8 @@ public class ListMember {
this.state = state;
}
public static ListMember create(String listEmail, String userEmail, int state) throws SQLException {
return new ListMember(listEmail,userEmail,state).save();
public static ListMember create(MailingList list, User user, int state) throws SQLException {
return new ListMember(list.email(),user.email(),state).save();
}
public static void createTable() throws SQLException {
@ -43,15 +45,15 @@ public class ListMember { @@ -43,15 +45,15 @@ public class ListMember {
.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.toString()).run();
Database.open().query(sql).run();
}
public static List<String> listsOwnedBy(User user) {
var list = new ArrayList<String>();
try {
var rs = Database.open()
.query("SELECT "+LIST_EMAIL+" FROM " + TABLE_NAME)
var rs = Database.open().select(TABLE_NAME,LIST_EMAIL)
.where(USER_EMAIL,user.email())
.where(STATE,STATE_OWNER)
.exec();
@ -64,7 +66,7 @@ public class ListMember { @@ -64,7 +66,7 @@ public class ListMember {
public static Map<User,Integer> of(String listEmail) throws SQLException {
var rs = Database.open()
.query("SELECT * FROM "+TABLE_NAME)
.select(TABLE_NAME)
.where(LIST_EMAIL,listEmail)
.exec();
var temp = new HashMap<String,Integer>();
@ -92,23 +94,23 @@ public class ListMember { @@ -92,23 +94,23 @@ public class ListMember {
return this;
}
public static void unsubscribe(String listEmail, User user) throws SQLException {
public static void unsubscribe(MailingList list, User user) throws SQLException {
var db = Database.open();
var rs = db.query("SELECT * FROM "+TABLE_NAME)
.where(LIST_EMAIL,listEmail)
var rs = db.select(TABLE_NAME)
.where(LIST_EMAIL,list.email())
.where(USER_EMAIL,user.email())
.exec();
while (rs.next()){
int state = rs.getInt(STATE) ^ STATE_SUBSCRIBER;
if (state < 1) { // drop entry
db.query("DELETE FROM "+TABLE_NAME)
.where(LIST_EMAIL,listEmail)
db.deleteFrom(TABLE_NAME)
.where(LIST_EMAIL,list.email())
.where(USER_EMAIL,user.email())
.run();
} else { // update entry: whitdraw subscription
db.query("UPDATE "+TABLE_NAME)
db.update(TABLE_NAME)
.set(STATE,state)
.where(LIST_EMAIL,listEmail)
.where(LIST_EMAIL,list.email())
.where(USER_EMAIL,user.email())
.run();
}

39
src/main/java/de/srsoftware/widerhall/data/MailingList.java

@ -3,6 +3,7 @@ package de.srsoftware.widerhall.data; @@ -3,6 +3,7 @@ package de.srsoftware.widerhall.data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.xml.crypto.Data;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
@ -63,19 +64,19 @@ public class MailingList { @@ -63,19 +64,19 @@ public class MailingList {
.append(SMTP_PASS).append(" ").append(VARCHAR).append(", ")
.append(STATE).append(" ").append(INT)
.append(");");
Database.open().query(sql.toString()).run();
Database.open().query(sql).run();
}
public static void enable(String listEmail, boolean enable) throws SQLException {
// https://stackoverflow.com/questions/16440831/bitwise-xor-in-sqlite-bitwise-not-not-working-as-i-expect
String expression = enable ? "state = state | "+ENABLED : "state = (~(state & "+ENABLED+"))&(state|"+ENABLED+")";
Database.open().query("UPDATE " + TABLE_NAME + " SET "+expression).where(EMAIL, listEmail).run();
Database.open().update(TABLE_NAME,expression).where(EMAIL, listEmail).run();
}
public static void hide(String listEmail, boolean hide) throws SQLException {
// https://stackoverflow.com/questions/16440831/bitwise-xor-in-sqlite-bitwise-not-not-working-as-i-expect
String expression = hide ? "state = (~(state & "+PUBLIC+"))&(state|"+PUBLIC+")" : ("state = state | "+PUBLIC);
Database.open().query("UPDATE " + TABLE_NAME + " SET "+expression).where(EMAIL, listEmail).run();
Database.open().update(TABLE_NAME,expression).where(EMAIL, listEmail).run();
}
public static List<MailingList> listsOf(User user) {
@ -84,7 +85,7 @@ public class MailingList { @@ -84,7 +85,7 @@ public class MailingList {
var list = new ArrayList<MailingList>();
if (keys != null && keys.isEmpty()) return list;
try {
Database.Request query = Database.open().query("SELECT * FROM " + TABLE_NAME);
Database.Request query = Database.open().select(TABLE_NAME);
if (keys != null) query.where(EMAIL, keys);
var rs = query.exec();
while (rs.next()) {
@ -110,7 +111,10 @@ public class MailingList { @@ -110,7 +111,10 @@ public class MailingList {
public static List<MailingList> openLists() {
var list = new ArrayList<MailingList>();
try {
var rs = Database.open().query("SELECT *, (" + STATE + " & " + PUBLIC + ") as test FROM " + TABLE_NAME).where("test", PUBLIC).exec();
var rs = Database.open()
.select(TABLE_NAME,"*", "(" + STATE + " & " + PUBLIC + ") as test")
.where("test", PUBLIC)
.exec();
while (rs.next()) {
var email = rs.getString(EMAIL);
var name = rs.getString(NAME);
@ -127,6 +131,31 @@ public class MailingList { @@ -127,6 +131,31 @@ public class MailingList {
return openLists().stream().filter(ml -> ml.email.equals(list)).count() > 0;
}
public static MailingList load(String listEmail) {
try {
var rs = Database.open()
.select(TABLE_NAME)
.where(EMAIL,listEmail)
.exec();
if (rs.next()){
return new MailingList(rs.getString(EMAIL),
rs.getString(NAME),
rs.getString(IMAP_HOST),
rs.getInt(IMAP_PORT),
rs.getString(IMAP_USER),
rs.getString(IMAP_PASS),
rs.getString(SMTP_HOST),
rs.getInt(SMTP_PORT),
rs.getString(SMTP_USER),
rs.getString(SMTP_PASS),
rs.getInt(STATE));
}
} catch (SQLException e) {
LOG.debug("Failed to load MailingList: ",e);
}
return null;
}
public Map<String, Object> safeMap() {
var map = new HashMap<String,Object>();
String[] parts = email.split("@", 2);

86
src/main/java/de/srsoftware/widerhall/data/User.java

@ -16,6 +16,7 @@ import static de.srsoftware.widerhall.Constants.*; @@ -16,6 +16,7 @@ import static de.srsoftware.widerhall.Constants.*;
public class User {
public static final String TABLE_NAME = "Users";
private static final Logger LOG = LoggerFactory.getLogger(User.class);
private String email, salt, hashedPass, name;
public User(String email, String name, String salt, String hashedPass) {
@ -25,6 +26,25 @@ public class User { @@ -25,6 +26,25 @@ public class User {
this.hashedPass = hashedPass;
}
/*********** field accessors ***************/
public String email() {
return email;
}
public String hashedPassword() {
return hashedPass;
}
public String name() {
return name;
}
public String salt(){
return salt;
}
/************** end of field accessors ****************/
public static User create(String email, String name, String password) throws SQLException {
String salt = null;
String hashedPass = null;
@ -44,32 +64,7 @@ public class User { @@ -44,32 +64,7 @@ public class User {
.append(HASHED_PASS).append(" ").append(VARCHAR).append(", ")
.append(NAME).append(" ").append(VARCHAR)
.append(");");
Database.open().query(sql.toString()).run();
}
public static List<User> loadAll(Collection<String> emails) throws SQLException {
var rs = Database.open()
.query("SELECT * FROM "+TABLE_NAME)
.where(EMAIL,emails)
.exec();
var list = new ArrayList<User>();
while (rs.next()){
var email = rs.getString(EMAIL);
var name = rs.getString(NAME);
var salt = rs.getString(SALT);
var hashedPass = rs.getString(HASHED_PASS);
list.add(new User(email,name,salt,hashedPass));
}
return list;
}
public String email() {
return email;
}
public String hashedPassword() {
return hashedPass;
Database.open().query(sql).run();
}
public boolean is(String test){
@ -77,29 +72,31 @@ public class User { @@ -77,29 +72,31 @@ public class User {
return test.equals(name) || test.equals(email);
}
public static List<User> list() {
var userList = new ArrayList<User>();
try {
var rs = Database.open().query("SELECT * FROM Users").exec();
while (rs.next()){
var email = rs.getString(EMAIL);
var name = rs.getString(NAME);
var salt = rs.getString(SALT);
var hashedPassword = rs.getString(HASHED_PASS);
userList.add(new User(email,name,salt,hashedPassword));
}
} catch (SQLException e) {
LOG.warn("Error loading user list!",e);
public static List<User> loadAll() throws SQLException {
return loadAll(null);
}
public static List<User> loadAll(Collection<String> emails) throws SQLException {
var userList = new ArrayList<User>();
var query = Database.open().select(TABLE_NAME);
if (emails != null && !emails.isEmpty()) query.where(EMAIL,emails);
var rs = query.exec();
while (rs.next()) userList.add(new User(
rs.getString(EMAIL),
rs.getString(NAME),
rs.getString(SALT),
rs.getString(HASHED_PASS)));
return userList;
}
public static User load(String email, String password) throws InvalidKeyException, SQLException {
public static User loadUser(String email, String password) throws InvalidKeyException, SQLException {
ResultSet rs = Database.open()
.query("SELECT * FROM Users")
.select(TABLE_NAME)
.where(EMAIL,email)
.exec();
try {
if (rs.next()) {
email = rs.getString(EMAIL);
@ -124,12 +121,9 @@ public class User { @@ -124,12 +121,9 @@ public class User {
return hashedPass.equals(Util.sha256(password+salt));
}
public String name() {
return name;
}
public static boolean noUsers() throws SQLException {
var rs = Database.open().query("SELECT count(*) FROM users").exec();
var rs = Database.open().select(TABLE_NAME,"count(*)").exec();
try {
if (rs.next()) {
return rs.getInt(1) < 1;

7
src/main/java/de/srsoftware/widerhall/web/Rest.java

@ -53,7 +53,12 @@ public class Rest extends HttpServlet { @@ -53,7 +53,12 @@ public class Rest extends HttpServlet {
json.put(USER,user.safeMap());
switch (path) {
case USER_LIST:
json.put("users", (user.is(ADMIN) ? User.list() : List.of(user)).stream().map(User::safeMap).toList());
try {
json.put("users", (user.is(ADMIN) ? User.loadAll() : List.of(user)).stream().map(User::safeMap).toList());
} catch (SQLException e) {
LOG.debug("Failed to load user list:",e);
json.put(ERROR,"failed to load user list");
}
break;
case LIST_LIST:
json.put("lists", MailingList.listsOf(user).stream().map(MailingList::safeMap).toList());

46
src/main/java/de/srsoftware/widerhall/web/Web.java

@ -116,8 +116,8 @@ public class Web extends HttpServlet { @@ -116,8 +116,8 @@ public class Web extends HttpServlet {
}
try {
MailingList.create(email,name,imapHost,imapPort,imapUser,imapPass,smtpHost,smtpPort,smtpUser,smtpPass);
ListMember.create(email,user.email(),ListMember.STATE_OWNER);
var list = MailingList.create(email,name,imapHost,imapPort,imapUser,imapPass,smtpHost,smtpPort,smtpUser,smtpPass);
ListMember.create(list,user,ListMember.STATE_OWNER);
return redirectTo(INDEX,resp);
} catch (SQLException e) {
return t("Failed to create list '{}': {}",name,e.getMessage());
@ -129,13 +129,13 @@ public class Web extends HttpServlet { @@ -129,13 +129,13 @@ public class Web extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String error = handleGet(req, resp);
if (error != null) resp.sendError(400,error);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String error = handlePost(req, resp);
if (error != null) resp.sendError(400,error);
}
@ -208,7 +208,7 @@ public class Web extends HttpServlet { @@ -208,7 +208,7 @@ public class Web extends HttpServlet {
if (email == null || pass == null) return loadTemplate("login", Map.of("error",t("Missing username or password!")), resp);
if (!Util.isEmail(email)) return loadTemplate("login", Map.of("error",t("'{}' is not a valid email address!",email)), resp);
try {
var user = User.load(email,pass);
var user = User.loadUser(email,pass);
req.getSession().setAttribute("user",user);
// loading user successfull: goto index
resp.sendRedirect(String.join("/",WEB_ROOT,"admin"));
@ -244,7 +244,6 @@ public class Web extends HttpServlet { @@ -244,7 +244,6 @@ public class Web extends HttpServlet {
private String loadFile(String filename, HttpServletResponse resp) {
var path = String.join(File.separator,baseDir,"static",filename);
LOG.debug("loading {}",path);
var file = new File(path);
if (!file.exists()) return t("File {} does not exist!",filename);
try {
@ -324,12 +323,15 @@ public class Web extends HttpServlet { @@ -324,12 +323,15 @@ public class Web extends HttpServlet {
var name = req.getParameter(NAME);
var email = req.getParameter(EMAIL);
var pass = req.getParameter(PASSWORD);
var list = req.getParameter(LIST);
var listEmail = req.getParameter(LIST);
var data = new HashMap<String,Object>();
data.put(NAME,name);
data.put(EMAIL,email);
data.put(LIST,list);
if (list == null || list.isBlank()){
data.put(LIST,listEmail);
var list = MailingList.load(listEmail);
if (list == null){
data.put(ERROR,"No list provided by form data!");
return loadTemplate(SUBSCRIBE,data,resp);
@ -340,13 +342,14 @@ public class Web extends HttpServlet { @@ -340,13 +342,14 @@ public class Web extends HttpServlet {
}
if (pass != null && pass.isBlank()) pass = null;
User user = null;
try {
data.put(USER,User.create(email,name,pass).safeMap());
user = User.create(email,name,pass);
} catch (SQLException sqle) {
var cause = getCausingException(sqle);
int code = cause.getErrorCode();
if (code == PRIMARY_KEY_CONSTRAINT) try {// user already exists
data.put(USER,User.load(email,pass).safeMap());
user = User.loadUser(email,pass);
// success → subscribe
} catch (InvalidKeyException | SQLException e) {
// invalid credentials
@ -354,9 +357,11 @@ public class Web extends HttpServlet { @@ -354,9 +357,11 @@ public class Web extends HttpServlet {
return loadTemplate(SUBSCRIBE,data,resp);
}
}
data.put(USER,user.safeMap());
try {
ListMember.create(list,email,ListMember.STATE_SUBSCRIBER);
data.put(NOTES,t("Successfully subscribed '{}' to '{}'.",email,list));
ListMember.create(list,user,ListMember.STATE_SUBSCRIBER);
data.put(NOTES,t("Successfully subscribed '{}' to '{}'.",user.email(),list.email()));
return loadTemplate(INDEX,data,resp);
} catch (SQLException sqle) {
LOG.debug("List subscription failed: ",sqle);
@ -376,11 +381,14 @@ public class Web extends HttpServlet { @@ -376,11 +381,14 @@ public class Web extends HttpServlet {
var user = getSessionUser(req);
var email = req.getParameter(EMAIL);
var pass = req.getParameter(PASSWORD);
var list = req.getParameter(LIST);
var listEmail = req.getParameter(LIST);
data.put(EMAIL,email);
data.put(LIST,list);
data.put(LIST,listEmail);
var list = MailingList.load(listEmail);
if (user != null) data.put(USER,user.safeMap());
if (list == null || list.isBlank()){
if (list == null){
data.put(ERROR,"No list provided by form data!");
return loadTemplate(UNSUBSCRIBE,data,resp);
@ -393,7 +401,7 @@ public class Web extends HttpServlet { @@ -393,7 +401,7 @@ public class Web extends HttpServlet {
if (pass != null && pass.isBlank()) pass = null;
try {
user = User.load(email,pass);
user = User.loadUser(email,pass);
req.getSession().setAttribute(USER,user);
data.put(USER,user.safeMap());
} catch (InvalidKeyException | SQLException e) {
@ -404,10 +412,10 @@ public class Web extends HttpServlet { @@ -404,10 +412,10 @@ public class Web extends HttpServlet {
// if we get here, we should have a valid user
try {
ListMember.unsubscribe(list,user);
data.put(NOTES,t("Sucessfully un-subscribed from '{}'.",list));
data.put(NOTES,t("Sucessfully un-subscribed from '{}'.",list.email()));
return loadTemplate(INDEX,data,resp);
} catch (SQLException e) {
LOG.warn("Problem during unscubsription of {} from {}:",user.email(),list,e);
LOG.warn("Problem during unscubsription of {} from {}:",user.email(),list.email(),e);
data.put(ERROR,"Failed to unsubscribe!");
return loadTemplate(UNSUBSCRIBE,data,resp);

57
src/test/java/de/srsoftware/widerhall/data/DatabaseTest.java

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
package de.srsoftware.widerhall.data;
import de.srsoftware.widerhall.Configuration;
import junit.framework.TestCase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.sql.SQLException;
public class DatabaseTest extends TestCase {
private static final Logger LOG = LoggerFactory.getLogger(DatabaseTest.class);
public void setUp() throws Exception {
super.setUp();
File dbTemp = File.createTempFile("Widerhall",".sqlite");
Configuration.instance().dbFile(dbTemp);
}
public void tearDown() {
Configuration.instance().dbFile().delete();
}
public void testOpen() throws SQLException {
Database db = Database.open();
assertTrue(db.isOpen());
assertTrue(db.tableExists(User.TABLE_NAME));
assertTrue(db.tableExists(MailingList.TABLE_NAME));
assertTrue(db.tableExists(ListMember.TABLE_NAME));
}
public void testDeleteFrom(){
assertEquals("DELETE FROM Test ",Database.open().deleteFrom("Test").sql());
}
public void testInsertInto(){
assertEquals("INSERT INTO Test ",Database.open().insertInto("Test").sql());
}
public void testQuery() {
assertEquals("Test", Database.open().query(new StringBuilder("Test")).sql());
}
public void testSelect(){
assertEquals("SELECT * FROM Test",Database.open().select("Test").sql());
assertEquals("SELECT * FROM Test",Database.open().select("Test","*").sql());
assertEquals("SELECT Field FROM Test",Database.open().select("Test","Field").sql());
assertEquals("SELECT Field1, Field2, Field3 FROM Test",Database.open().select("Test","Field1","Field2","Field3").sql());
}
public void testUpdate(){
assertEquals("UPDATE Test",Database.open().update("Test").sql());
assertEquals("UPDATE Test SET x = 5",Database.open().update("Test","x = 5").sql());
assertEquals("UPDATE Test SET x = 5, y = 6",Database.open().update("Test","x = 5","y = 6").sql());
}
}
Loading…
Cancel
Save