Browse Source

implemented storing of appointments

+ attachments

next: attach links, tags

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
main
Stephan Richter 6 months ago
parent
commit
fc17bb9168
  1. 2
      de.srsoftware.cal.api/build.gradle.kts
  2. 3
      de.srsoftware.cal.api/src/main/java/de/srsoftware/cal/api/Appointment.java
  3. 2
      de.srsoftware.cal.app/build.gradle.kts
  4. 2
      de.srsoftware.cal.base/build.gradle.kts
  5. 16
      de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseAppointment.java
  6. 65
      de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseImporter.java
  7. 4
      de.srsoftware.cal.db/build.gradle.kts
  8. 4
      de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/Database.java
  9. 22
      de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/Fields.java
  10. 53
      de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/MariaDB.java
  11. 2
      de.srsoftware.cal.importer/build.gradle.kts
  12. 1
      de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/Kassablanca.java
  13. 1
      de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/Rosenkeller.java
  14. 2
      de.srsoftware.cal.web/build.gradle.kts
  15. 39
      de.srsoftware.cal.web/src/main/java/de/srsoftware/cal/ApiHandler.java
  16. 2
      de.srsoftware.cal.web/src/main/resources/script/edit.js

2
de.srsoftware.cal.api/build.gradle.kts

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
description = "OpenCloudCal : API"
dependencies {
implementation("de.srsoftware:tools.util:1.2.2")
implementation("de.srsoftware:tools.util:1.2.3")
implementation("org.json:json:20240303")
}

3
de.srsoftware.cal.api/src/main/java/de/srsoftware/cal/api/Appointment.java

@ -22,6 +22,8 @@ public interface Appointment { @@ -22,6 +22,8 @@ public interface Appointment {
*/
Optional<Coords> coords();
Appointment clone(long newId);
/**
* Descriptive text of the event
* @return the description
@ -41,7 +43,6 @@ public interface Appointment { @@ -41,7 +43,6 @@ public interface Appointment {
*/
long id();
/**
* get a json representation of this Appointment
* @return a JSON Object

2
de.srsoftware.cal.app/build.gradle.kts

@ -8,7 +8,7 @@ dependencies { @@ -8,7 +8,7 @@ dependencies {
implementation("de.srsoftware:configuration.api:1.0.0")
implementation("de.srsoftware:configuration.json:1.0.0")
implementation("de.srsoftware:tools.http:1.0.4")
implementation("de.srsoftware:tools.logging:1.0.1")
implementation("de.srsoftware:tools.logging:1.0.2")
implementation("de.srsoftware:tools.web:1.3.8")
implementation("com.mysql:mysql-connector-j:9.1.0")
}

2
de.srsoftware.cal.base/build.gradle.kts

@ -4,7 +4,7 @@ dependencies { @@ -4,7 +4,7 @@ dependencies {
implementation(project(":de.srsoftware.cal.api"))
implementation("de.srsoftware:tools.optionals:1.0.0")
implementation("de.srsoftware:tools.util:1.2.2")
implementation("de.srsoftware:tools.util:1.2.3")
implementation("de.srsoftware:tools.web:1.3.8")
implementation("org.json:json:20240303")
}

16
de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseAppointment.java

@ -17,10 +17,10 @@ import org.json.JSONObject; @@ -17,10 +17,10 @@ import org.json.JSONObject;
*/
public class BaseAppointment implements Appointment {
private static final DateTimeFormatter DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
private final long id;
private long id;
private final String title, description;
private final LocalDateTime end, start;
private final String hash;
private final String slug;
private Coords coords = null;
private final Set<Attachment> attachments = new HashSet<>();
private final Set<String> tags = new HashSet<>();
@ -39,7 +39,7 @@ public class BaseAppointment implements Appointment { @@ -39,7 +39,7 @@ public class BaseAppointment implements Appointment {
public BaseAppointment(long id, String title, String description, LocalDateTime start, LocalDateTime end, String location, String slug) {
this.description = description;
this.end = end;
this.hash = slug;
this.slug = slug;
this.id = id;
this.location = location;
this.start = start;
@ -98,7 +98,12 @@ public class BaseAppointment implements Appointment { @@ -98,7 +98,12 @@ public class BaseAppointment implements Appointment {
@Override
public Set<Attachment> attachments() {
return Set.of();
return attachments;
}
@Override
public Appointment clone(long newId) {
return new BaseAppointment(newId, title, description, start, end, location, slug).coords(coords).addLinks(links).add(attachments);
}
@Override
@ -116,7 +121,6 @@ public class BaseAppointment implements Appointment { @@ -116,7 +121,6 @@ public class BaseAppointment implements Appointment {
return nullable(end);
}
@Override
public long id() {
return id;
@ -148,7 +152,7 @@ public class BaseAppointment implements Appointment { @@ -148,7 +152,7 @@ public class BaseAppointment implements Appointment {
@Override
public String slug() {
return hash;
return slug;
}

65
de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseImporter.java

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
/* © SRSoftware 2024 */
package de.srsoftware.cal;
import static de.srsoftware.tools.Result.transform;
import static java.nio.charset.StandardCharsets.UTF_8;
import de.srsoftware.cal.api.*;
@ -44,8 +45,8 @@ public abstract class BaseImporter implements Importer { @@ -44,8 +45,8 @@ public abstract class BaseImporter implements Importer {
.map(tag -> tag.get("src"))
.filter(Objects::nonNull)
.map(Payload::of)
.map(this::url)
.map(this::toAttachment)
.map(BaseImporter::url)
.map(BaseImporter::toAttachment)
.map(Result::optional)
.flatMap(Optional::stream)
.toList();
@ -140,9 +141,9 @@ public abstract class BaseImporter implements Importer { @@ -140,9 +141,9 @@ public abstract class BaseImporter implements Importer {
var text = anchor.inner(0).orElse(href);
Payload //
.of(href)
.map(this::url)
.map(BaseImporter::url)
.map(url -> link(url,text))
.optional()
.map(url -> new Link(url, text))
.ifPresent(links::add);
});
return links;
@ -195,7 +196,7 @@ public abstract class BaseImporter implements Importer { @@ -195,7 +196,7 @@ public abstract class BaseImporter implements Importer {
.map(this::extractEventUrls)
.stream();
return stream //
.map(this::url)
.map(BaseImporter::url)
.map(this::loadEvent)
.peek(e -> {
if (e instanceof Error<Appointment> err) System.err.println(err);
@ -216,6 +217,12 @@ public abstract class BaseImporter implements Importer { @@ -216,6 +217,12 @@ public abstract class BaseImporter implements Importer {
return Error.format("Invalid parameter: %s", result.getClass().getSimpleName());
}
protected static Result<Link> link(Result<URL> url, String text) {
var opt = url.optional();
if (opt.isEmpty()) return transform(url);
return Payload.of(new Link(opt.get(),text));
}
protected Result<Appointment> loadEvent(Result<URL> urlResult) {
var link = urlResult //
.optional().map(url -> new Link(url, "Event-Seite")).orElse(null);
@ -271,22 +278,19 @@ public abstract class BaseImporter implements Importer { @@ -271,22 +278,19 @@ public abstract class BaseImporter implements Importer {
protected abstract String programURL();
protected Result<Attachment> toAttachment(Result<URL> urlResult) {
switch (urlResult) {
case Payload<URL> payload:
try {
var mime = payload.get().openConnection().getContentType();
return Payload.of(new Attachment(payload.get(), mime));
} catch (Exception e) {
return Error.format("Failed to read mime type of %s", payload);
}
case Error<URL> err:
return err.transform();
default:
return invalidParameter(urlResult);
protected static Result<Attachment> toAttachment(Result<URL> urlResult) {
var opt = urlResult.optional();
if (opt.isEmpty()) return transform(urlResult);
try {
var mime = opt.get().openConnection().getContentType();
return Payload.of(new Attachment(opt.get(), mime));
} catch (Exception e) {
e.printStackTrace();
return Error.format("Failed to read mime type of %s", opt.get());
}
}
protected static Result<Integer> toNumericMonth(String month) {
month = month.toLowerCase();
if (month.startsWith("ja")) return Payload.of(1);
@ -305,24 +309,13 @@ public abstract class BaseImporter implements Importer { @@ -305,24 +309,13 @@ public abstract class BaseImporter implements Importer {
}
protected <T> Result<T> transform(Result<?> result) {
if (result instanceof Error<?> err) return err.transform();
return invalidParameter(result);
}
protected Result<URL> url(Result<String> urlResult) {
switch (urlResult) {
case Payload<String> payload:
var url = payload.get();
try {
return Payload.of(new URI(url).toURL());
} catch (MalformedURLException | URISyntaxException e) {
return de.srsoftware.tools.Error.of("Failed to create URL of %s".formatted(url), e);
}
case de.srsoftware.tools.Error<String> err:
return err.transform();
default:
return invalidParameter(urlResult);
protected static Result<URL> url(Result<String> urlResult) {
if (urlResult.optional().isEmpty()) return transform(urlResult);
var url = urlResult.optional().get();
try {
return Payload.of(new URI(url).toURL());
} catch (MalformedURLException | URISyntaxException e) {
return Error.of("Failed to create URL of %s".formatted(url), e);
}
}
}

4
de.srsoftware.cal.db/build.gradle.kts

@ -4,7 +4,7 @@ dependencies { @@ -4,7 +4,7 @@ dependencies {
implementation(project(":de.srsoftware.cal.api"))
implementation(project(":de.srsoftware.cal.base"))
implementation("de.srsoftware:tools.jdbc:1.1.0")
implementation("de.srsoftware:tools.jdbc:1.1.1")
implementation("de.srsoftware:tools.optionals:1.0.0")
implementation("de.srsoftware:tools.util:1.2.2")
implementation("de.srsoftware:tools.util:1.2.3")
}

4
de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/Database.java

@ -17,9 +17,9 @@ public interface Database { @@ -17,9 +17,9 @@ public interface Database {
/**
* add an appointment to the database
* @param appointment the appointment to store
* @return the Database object
* @return a clone of the provided appointment with id field set
*/
public Database add(Appointment appointment);
public Result<Appointment> add(Appointment appointment);
/**
* list appointments unfiltered

22
de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/Fields.java

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
/* © SRSoftware 2024 */
package de.srsoftware.cal.db;
public class Fields {
public static final String AID = "aid";
public static final String ALL = "*";
public static final String COORDS = "coords";
public static final String DESCRIPTION = "description";
public static final String END = "end";
public static final String KEYWORD = "keyword";
public static final String LOCATION = "location";
public static final String MIME = "mime";
public static final String SLUG = "slug";
public static final String START = "start";
public static final String TID = "tid";
public static final String TITLE = "title";
public static final String UID = "uid";
public static final String URL = "url";
private Fields() {
}
}

53
de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/MariaDB.java

@ -2,7 +2,8 @@ @@ -2,7 +2,8 @@
package de.srsoftware.cal.db;
import static de.srsoftware.cal.db.Database.slug;
import static de.srsoftware.tools.NotImplemented.notImplemented;
import static de.srsoftware.cal.db.Fields.*;
import static de.srsoftware.cal.db.Fields.ALL;
import static de.srsoftware.tools.Optionals.*;
import static de.srsoftware.tools.Result.transform;
import static de.srsoftware.tools.jdbc.Condition.*;
@ -19,6 +20,7 @@ import de.srsoftware.tools.Result; @@ -19,6 +20,7 @@ import de.srsoftware.tools.Result;
import de.srsoftware.tools.jdbc.Query;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.*;
@ -27,17 +29,9 @@ public class MariaDB implements Database { @@ -27,17 +29,9 @@ public class MariaDB implements Database {
private static final System.Logger LOG = System.getLogger(MariaDB.class.getSimpleName());
private static final String ADD_SLUG = "ALTER TABLE appointments ADD slug VARCHAR(255) UNIQUE";
private static final String APPOINTMENTS = "appointments";
private static final String AID = "aid";
private static final String ALL = "*";
private static final String KEYWORD = "keyword";
private static final String APPOINTMENT_TAGS = "appointment_tags";
private static final String URL = "url";
private static final String APPOINTMENT_URLS = "appointment_urls";
private static final String DESCRIPTION = "description";
private static final String UID = "uid";
private static final String URLS = "urls";
private static final String TID = "tid";
private static final String MIME = "mime";
private static final String APPOINTMENT_ATTACHMENTS = "appointment_attachments";
private static final String TAGS = "tags";
private static final String SLUG = "slug";
@ -106,8 +100,45 @@ public class MariaDB implements Database { @@ -106,8 +100,45 @@ public class MariaDB implements Database {
@Override
public Database add(Appointment appointment) {
throw notImplemented(this, "add(Appointment)");
public Result<Appointment> add(Appointment appointment) {
try {
ResultSet keys = Query //
.insertInto(APPOINTMENTS, TITLE, DESCRIPTION, START, END, LOCATION, COORDS, SLUG)
.values(appointment.title(), appointment.description(), appointment.start(), appointment.end().orElse(null), appointment.location(), appointment.coords().orElse(null), appointment.slug())
.execute(connection)
.getGeneratedKeys();
Appointment saved = null;
if (keys.next()) saved = appointment.clone(keys.getLong(1));
keys.close();
if (saved == null) return Error.of("Insert query did not return appointment id!");
var attachments = saved.attachments();
Query.InsertQuery assignQuery = null;
for (var attachment : attachments){
if (assignQuery == null) assignQuery = Query.insertInto(APPOINTMENT_ATTACHMENTS,AID,UID,MIME);
var urlId = getOrCreateUrl(attachment.url());
if (urlId.isPresent()) assignQuery.values(saved.id(), urlId.get(),attachment.mime());
}
if (assignQuery != null) assignQuery.execute(connection);
return Payload.of(saved);
} catch (SQLException e) {
LOG.log(ERROR,"Failed to store appointment",e);
return Error.of("Failed to store appointment", e);
}
}
private Optional<Long> getOrCreateUrl(URL url) throws SQLException {
var rs = Query.select(UID).from(URLS).where(URL,equal(url.toString())).exec(connection);
Long uid = null;
if (rs.next()) uid = rs.getLong(1);
rs.close();
if (uid == null){
rs = Query.insertInto(URLS,URL).values(url.toString()).execute(connection).getGeneratedKeys();
if (rs.next()) uid = rs.getLong(1);
rs.close();
}
return nullable(uid);
}
public static Database connect(String jdbc, String user, String pass) throws SQLException {

2
de.srsoftware.cal.importer/build.gradle.kts

@ -4,6 +4,6 @@ dependencies { @@ -4,6 +4,6 @@ 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.2.2")
implementation("de.srsoftware:tools.util:1.2.3")
implementation("de.srsoftware:tools.web:1.3.8")
}

1
de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/Kassablanca.java

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
/* © SRSoftware 2024 */
package de.srsoftware.cal.importer.jena;
import static de.srsoftware.tools.Result.transform;
import static de.srsoftware.tools.TagFilter.*;
import de.srsoftware.cal.BaseImporter;

1
de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/Rosenkeller.java

@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
package de.srsoftware.cal.importer.jena;
import static de.srsoftware.tools.Optionals.nullable;
import static de.srsoftware.tools.Result.transform;
import static de.srsoftware.tools.TagFilter.*;
import de.srsoftware.cal.BaseImporter;

2
de.srsoftware.cal.web/build.gradle.kts

@ -7,6 +7,6 @@ dependencies { @@ -7,6 +7,6 @@ dependencies {
implementation("de.srsoftware:tools.http:1.0.4")
implementation("de.srsoftware:tools.optionals:1.0.0")
implementation("de.srsoftware:tools.util:1.2.2")
implementation("de.srsoftware:tools.util:1.2.3")
implementation("org.json:json:20240303")
}

39
de.srsoftware.cal.web/src/main/java/de/srsoftware/cal/ApiHandler.java

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
/* © SRSoftware 2024 */
package de.srsoftware.cal;
import static de.srsoftware.cal.db.Fields.*;
import static de.srsoftware.tools.Optionals.nullIfEmpty;
import static de.srsoftware.tools.Optionals.nullable;
import static java.lang.System.*;
@ -9,6 +10,7 @@ import static java.time.format.DateTimeFormatter.ISO_DATE_TIME; @@ -9,6 +10,7 @@ import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.cal.api.Appointment;
import de.srsoftware.cal.api.Link;
import de.srsoftware.cal.db.Database;
import de.srsoftware.tools.Error;
import de.srsoftware.tools.PathHandler;
@ -25,12 +27,8 @@ import org.json.JSONObject; @@ -25,12 +27,8 @@ import org.json.JSONObject;
public class ApiHandler extends PathHandler {
private static final Logger LOG = getLogger(ApiHandler.class.getSimpleName());
private static final String SLUG = "slug";
private static final String DESCRIPTION = "description";
private static final String TITLE = "title";
private static final String START = "start";
private static final String END = "end";
private static final String LOCATION = "location";
private static final String ATTACHMENTS = "attachments";
private static final String LINKS = "links";
private final Database db;
public ApiHandler(Database db) {
@ -89,8 +87,33 @@ public class ApiHandler extends PathHandler { @@ -89,8 +87,33 @@ public class ApiHandler extends PathHandler {
var serverSlug = Database.slug(location, startDate);
if (!serverSlug.equals(clientSlug)) return sendContent(ex, Error.of("Slug mismatch!"));
var event = new BaseAppointment(0, title, description, startDate, endDate, location, serverSlug);
db.add(event);
return sendContent(ex, Error.of("createEvent not implemented"));
if (json.has(ATTACHMENTS)) {
json.getJSONArray(ATTACHMENTS).forEach(att -> {
Payload //
.of(att.toString())
.map(BaseImporter::url)
.map(BaseImporter::toAttachment)
.optional()
.ifPresent(event::add);
});
}
if (json.has(LINKS)) {
json.getJSONArray(LINKS).forEach(o -> {
if (o instanceof JSONObject j) toLink(j).optional().ifPresent(event::addLinks);
});
}
var res = db.add(event).map(ApiHandler::toJson);
return sendContent(ex, res);
}
protected static Result<Link> toLink(JSONObject json) {
try {
var description = json.getString(DESCRIPTION);
return Payload.of(json.getString(URL)).map(BaseImporter::url).map(url -> BaseImporter.link(url, description));
} catch (Exception e) {
return Error.of("Failed to create link from %s".formatted(json), e);
}
}
private boolean update(HttpExchange ex, Appointment event, JSONObject json) throws IOException {

2
de.srsoftware.cal.web/src/main/resources/script/edit.js

@ -140,6 +140,8 @@ async function slug(start,location){ @@ -140,6 +140,8 @@ async function slug(start,location){
}
async function saveEvent(){
element('save').toggleAttribute("disabled");
var location = element('location').value;
var start = element('start').value;
var slugVal = await slug(start,location);

Loading…
Cancel
Save