implemented storing of appointments
+ attachments next: attach links, tags Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
*/
|
||||
long id();
|
||||
|
||||
|
||||
/**
|
||||
* get a json representation of this Appointment
|
||||
* @return a JSON Object
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
|
||||
@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 {
|
||||
return nullable(end);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long id() {
|
||||
return id;
|
||||
@@ -148,7 +152,7 @@ public class BaseAppointment implements Appointment {
|
||||
|
||||
@Override
|
||||
public String slug() {
|
||||
return hash;
|
||||
return slug;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
.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 {
|
||||
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 {
|
||||
.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 {
|
||||
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 {
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
|
||||
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,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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
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 {
|
||||
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 {
|
||||
|
||||
|
||||
@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 {
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
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;
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user