diff --git a/de.srsoftware.cal.app/build.gradle.kts b/de.srsoftware.cal.app/build.gradle.kts index 935fcef..b9ad26f 100644 --- a/de.srsoftware.cal.app/build.gradle.kts +++ b/de.srsoftware.cal.app/build.gradle.kts @@ -2,6 +2,10 @@ description = "OpenCloudCal : Application" dependencies { implementation(project(":de.srsoftware.cal.api")) + implementation(project(":de.srsoftware.cal.db")) implementation(project(":de.srsoftware.cal.importer")) + + implementation("de.srsoftware:configuration.api:1.0.0") + implementation("de.srsoftware:configuration.json:1.0.0") implementation("de.srsoftware:tools.util:1.1.1") -} + implementation("com.mysql:mysql-connector-j:9.1.0")} diff --git a/de.srsoftware.cal.app/src/main/java/de/srsoftware/cal/app/Application.java b/de.srsoftware.cal.app/src/main/java/de/srsoftware/cal/app/Application.java index 00ff219..767e9e6 100644 --- a/de.srsoftware.cal.app/src/main/java/de/srsoftware/cal/app/Application.java +++ b/de.srsoftware.cal.app/src/main/java/de/srsoftware/cal/app/Application.java @@ -1,38 +1,47 @@ /* © SRSoftware 2024 */ package de.srsoftware.cal.app; -import de.srsoftware.cal.importer.jena.Kassablanca; +import de.srsoftware.cal.db.Database; +import de.srsoftware.cal.db.MariaDB; +import de.srsoftware.configuration.Configuration; +import de.srsoftware.configuration.JsonConfig; +import java.io.IOException; import java.security.NoSuchAlgorithmException; -import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; +import java.util.Optional; /** * Test application */ public class Application { + private static final String JDBC = "opencloudcal.db.jdbc"; + private static final String USER = "opencloudcal.db.user"; + private static final String PASS = "opencloudcal.db.pass"; + private static final String MISSING = "missing required configuration property \"%s\""; + private Application() { } + private static Database connect(Configuration config) throws SQLException { + Optional jdbc = config.get(JDBC); + if (jdbc.isEmpty()) throw new RuntimeException(MISSING.formatted(JDBC)); + + String user = config.get(USER, "opencloudcal"); + + Optional pass = config.get(PASS); + if (pass.isEmpty()) throw new RuntimeException(MISSING.formatted(PASS)); + + return MariaDB.connect(jdbc.get(), user, pass.get()); + } + /** * sandbox * @param args default */ - public static void main(String[] args) throws NoSuchAlgorithmException { - - String host = null; - String database = null; - String user = null; - String pass = null; - int port = 3306; - - // TODO: we need configuration here! - - try (Connection con = DriverManager - .getConnection("jdbc:mysql://%s:%s/%s".formatted(host,port,database), user, pass)) { - // use con here - } catch (SQLException e) { - throw new RuntimeException(e); - } + public static void main(String[] args) throws NoSuchAlgorithmException, IOException, SQLException { + JsonConfig jsonConfig = new JsonConfig("OpenCloudCal"); + var db = connect(jsonConfig); + var appointments = db.list(null, null); + for (var event : appointments) System.out.println(event); } } diff --git a/de.srsoftware.cal.base/build.gradle.kts b/de.srsoftware.cal.base/build.gradle.kts new file mode 100644 index 0000000..7fcb4b7 --- /dev/null +++ b/de.srsoftware.cal.base/build.gradle.kts @@ -0,0 +1,9 @@ +description = "OpenCloudCal : Base" + +dependencies { + implementation(project(":de.srsoftware.cal.api")) + + implementation("de.srsoftware:tools.optionals:1.0.0") + implementation("de.srsoftware:tools.util:1.2.0") + implementation("de.srsoftware:tools.web:1.3.3") +} diff --git a/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/BaseAppointment.java b/de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseAppointment.java similarity index 99% rename from de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/BaseAppointment.java rename to de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseAppointment.java index 37a4ed7..911bda9 100644 --- a/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/BaseAppointment.java +++ b/de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseAppointment.java @@ -1,5 +1,5 @@ /* © SRSoftware 2024 */ -package de.srsoftware.cal.importer; +package de.srsoftware.cal; import static de.srsoftware.tools.Optionals.nullable; diff --git a/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/BaseImporter.java b/de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseImporter.java similarity index 97% rename from de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/BaseImporter.java rename to de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseImporter.java index 5100811..d5023fe 100644 --- a/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/BaseImporter.java +++ b/de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseImporter.java @@ -1,8 +1,6 @@ /* © SRSoftware 2024 */ -package de.srsoftware.cal.importer; +package de.srsoftware.cal; -import static de.srsoftware.tools.Strings.hex; -import static de.srsoftware.tools.TagFilter.ofType; import static java.nio.charset.StandardCharsets.UTF_8; import de.srsoftware.cal.api.*; @@ -42,7 +40,7 @@ public abstract class BaseImporter implements Importer { return extractAttachmentsTag(eventTag) // .optional() .stream() - .flatMap(tag -> tag.find(ofType("img")).stream()) + .flatMap(tag -> tag.find(TagFilter.ofType("img")).stream()) .map(tag -> tag.get("src")) .filter(Objects::nonNull) .map(Payload::of) @@ -211,7 +209,7 @@ public abstract class BaseImporter implements Importer { * @return the hash of the plain text */ protected String hash(String plain){ - return hex(digest.digest(plain.getBytes(UTF_8))); + return Strings.hex(digest.digest(plain.getBytes(UTF_8))); } protected static Result invalidParameter(Result result) { diff --git a/de.srsoftware.cal.db/build.gradle.kts b/de.srsoftware.cal.db/build.gradle.kts index 6cd6918..aeb49c5 100644 --- a/de.srsoftware.cal.db/build.gradle.kts +++ b/de.srsoftware.cal.db/build.gradle.kts @@ -1,4 +1,10 @@ description = "OpenCloudCal : Database" dependencies { + implementation(project(":de.srsoftware.cal.api")) + implementation(project(":de.srsoftware.cal.base")) + + implementation("de.srsoftware:tools.jdbc:1.0.0") + implementation("de.srsoftware:tools.optionals:1.0.0") + implementation("de.srsoftware:tools.util:1.2.0") } diff --git a/de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/Database.java b/de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/Database.java new file mode 100644 index 0000000..0a879f2 --- /dev/null +++ b/de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/Database.java @@ -0,0 +1,36 @@ +/* © SRSoftware 2024 */ +package de.srsoftware.cal.db; + +import de.srsoftware.cal.api.Appointment; +import java.sql.SQLException; +import java.util.List; +import java.util.Set; + +/** + * Interface for calendar database + */ +public interface Database { + /** + * add an appointment to the database + * @param appointment the appointment to store + * @return the Database object + */ + public Database add(Appointment appointment); + + /** + * list appointments unfiltered + * @param count the maximum number of appointments to return + * @param offset the number of appointments to skip + * @return the list of appointments fetched from the db + */ + public List list(Integer count, Integer offset) throws SQLException; + + /** + * list appointments + * @param tags only list appointments which have matching tags + * @param count the maximum number of appointments to return + * @param offset the number of appointments to skip + * @return the list of appointments fetched from the db + */ + public List listByTags(Set tags, Integer count, Integer offset); +} diff --git a/de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/MariaDB.java b/de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/MariaDB.java new file mode 100644 index 0000000..5320cf8 --- /dev/null +++ b/de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/MariaDB.java @@ -0,0 +1,121 @@ +/* © SRSoftware 2024 */ +package de.srsoftware.cal.db; + +import static de.srsoftware.tools.Optionals.allEmpty; +import static de.srsoftware.tools.Optionals.nullable; + +import de.srsoftware.cal.BaseAppointment; +import de.srsoftware.cal.api.Appointment; +import de.srsoftware.tools.Calc; +import de.srsoftware.tools.jdbc.Query; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class MariaDB implements Database { + private static final String SELECT_APPOINTMENTS = "SELECT * FROM appointments"; + private static final String SELECT_APPOINTMENTS_WITH_HASHES = SELECT_APPOINTMENTS + " a LEFT JOIN appointment_hashes h ON a.aid = h.aid"; + private static final String SELECT_VERSION = "SELECT value FROM config WHERE keyname = 'dbversion'"; + private static final String CREATE_HASHES = "CREATE TABLE appointment_hashes (aid INT NOT NULL, hash VARCHAR(255) NOT NULL, UNIQUE(aid), UNIQUE(hash))"; + private static final String INSERT_HASH = "INSERT INTO appointment_hashes (aid, hash) values (?, ?) ON DUPLICATE KEY UPDATE hash=hash;"; + private static final String UPDATE_DB_VERSION = "UPDATE config SET value = ? WHERE keyname = 'dbversion'"; + private static Connection connection; + + private MariaDB(Connection conn) throws SQLException { + connection = conn; + applyUpdates(); + } + + private void applyUpdates() throws SQLException { + var rs = Query.of(SELECT_VERSION).execute(connection); + var version = 0; + if (rs.next()) { + version = rs.getInt("value"); + } + rs.close(); + switch (version) { + case 0: + createTables(); + case 1: + update1(); + } + } + + private void update1() throws SQLException { + var list = new ArrayList(); + var results = Query.of(SELECT_APPOINTMENTS).execute(connection); + while (results.next()) { + var id = results.getInt("aid"); + var title = results.getString("title"); + var description = results.getString("description"); + if (allEmpty(title, description)) continue; + var stamp = results.getTimestamp("start"); + var start = nullable(stamp).map(Timestamp::toLocalDateTime).orElse(null); + var end = nullable(results.getTimestamp("end")).map(Timestamp::toLocalDateTime).orElse(null); + var location = results.getString("location"); + Calc // + .hash(start + "@" + location) + .map(hash -> new BaseAppointment(id, title, description, start, end, location, hash)) + .ifPresent(list::add); + } + results.close(); + connection.setAutoCommit(false); + + Query.of(CREATE_HASHES).statement(connection).execute(); + for (var appointment : list) { + var stmt = Query.of(INSERT_HASH).statement(connection); + stmt.setLong(1, appointment.id()); + stmt.setString(2, appointment.hash()); + stmt.execute(); + } + connection.commit(); + var stmt = Query.of(UPDATE_DB_VERSION).statement(connection); + stmt.setLong(1, 2); + stmt.execute(); + connection.setAutoCommit(true); + } + + private void createTables() { + throw new RuntimeException("%s.createTables() not implemented!"); + } + + + @Override + public Database add(Appointment appointment) { + return null; + } + + public static Database connect(String jdbc, String user, String pass) throws SQLException { + return new MariaDB(DriverManager.getConnection(jdbc, user, pass)); + } + + + @Override + public List list(Integer count, Integer offset) throws SQLException { + var list = new ArrayList(); + var results = Query.of(SELECT_APPOINTMENTS_WITH_HASHES).execute(connection); + while (results.next()) { + var id = results.getInt("aid"); + var title = results.getString("title"); + var description = results.getString("description"); + if (allEmpty(title, description)) continue; + var start = results.getTimestamp("start").toLocalDateTime(); + var end = nullable(results.getTimestamp("end")).map(Timestamp::toLocalDateTime).orElse(null); + var location = results.getString("location"); + var hash = results.getString("hash"); + if (hash == null) hash = Calc.hash(start + "@" + location).orElse(null); + list.add(new BaseAppointment(id, title, description, start, end, location, hash)); + } + results.close(); + return list; + } + + @Override + public List listByTags(Set tags, Integer count, Integer offset) { + return List.of(); + } +} diff --git a/de.srsoftware.cal.importer/build.gradle.kts b/de.srsoftware.cal.importer/build.gradle.kts index e8b50a8..4734eae 100644 --- a/de.srsoftware.cal.importer/build.gradle.kts +++ b/de.srsoftware.cal.importer/build.gradle.kts @@ -2,7 +2,8 @@ description = "OpenCloudCal : Importers" dependencies { implementation(project(":de.srsoftware.cal.api")) + implementation(project(":de.srsoftware.cal.base")) implementation("de.srsoftware:tools.optionals:1.0.0") - implementation("de.srsoftware:tools.util:1.1.3") + implementation("de.srsoftware:tools.util:1.2.0") implementation("de.srsoftware:tools.web:1.3.3") } diff --git a/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/Kassablanca.java b/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/Kassablanca.java index dafcc43..fb4e217 100644 --- a/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/Kassablanca.java +++ b/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/Kassablanca.java @@ -3,7 +3,7 @@ package de.srsoftware.cal.importer.jena; import static de.srsoftware.tools.TagFilter.*; -import de.srsoftware.cal.importer.BaseImporter; +import de.srsoftware.cal.BaseImporter; import de.srsoftware.tools.*; import de.srsoftware.tools.Error; import java.security.NoSuchAlgorithmException; diff --git a/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/Rosenkeller.java b/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/Rosenkeller.java index 47d6ac1..c71eae6 100644 --- a/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/Rosenkeller.java +++ b/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/Rosenkeller.java @@ -4,7 +4,7 @@ package de.srsoftware.cal.importer.jena; import static de.srsoftware.tools.Optionals.nullable; import static de.srsoftware.tools.TagFilter.*; -import de.srsoftware.cal.importer.BaseImporter; +import de.srsoftware.cal.BaseImporter; import de.srsoftware.tools.Error; import de.srsoftware.tools.Payload; import de.srsoftware.tools.Result; diff --git a/settings.gradle.kts b/settings.gradle.kts index 712d1a6..13cbb72 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,3 +4,4 @@ include("de.srsoftware.cal.api") include("de.srsoftware.cal.db") include("de.srsoftware.cal.importer") include("de.srsoftware.cal.web") +include("de.srsoftware.cal.base")