minor improvements
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -11,8 +11,6 @@ spotless {
|
|||||||
target("**/src/**/java/**/*.java")
|
target("**/src/**/java/**/*.java")
|
||||||
removeUnusedImports()
|
removeUnusedImports()
|
||||||
importOrder()
|
importOrder()
|
||||||
//cleanthat()
|
|
||||||
clangFormat("18.1.8").style("file:config/clang-format")
|
|
||||||
licenseHeader("/* © SRSoftware 2024 */")
|
licenseHeader("/* © SRSoftware 2024 */")
|
||||||
toggleOffOn()
|
toggleOffOn()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,21 +30,18 @@ public interface Appointment {
|
|||||||
*/
|
*/
|
||||||
String description();
|
String description();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The date and time when the appointment is scheduled to end
|
* The date and time when the appointment is scheduled to end
|
||||||
* @return an optionals LocalDateTime
|
* @return an optionals LocalDateTime
|
||||||
*/
|
*/
|
||||||
Optional<LocalDateTime> end();
|
Optional<LocalDateTime> end();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* represent this event as ical entry
|
* represent this event as ical entry
|
||||||
* @return the ical string
|
* @return the ical string
|
||||||
*/
|
*/
|
||||||
String ical();
|
String ical();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ID of the appointment – unique within this system
|
* ID of the appointment – unique within this system
|
||||||
* @return the appointment`s id
|
* @return the appointment`s id
|
||||||
@@ -75,14 +72,12 @@ public interface Appointment {
|
|||||||
*/
|
*/
|
||||||
LocalDateTime start();
|
LocalDateTime start();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tags i.e. keywords may be used to filter appointments
|
* tags i.e. keywords may be used to filter appointments
|
||||||
* @return the set of tags associated with the current appointment
|
* @return the set of tags associated with the current appointment
|
||||||
*/
|
*/
|
||||||
Set<String> tags();
|
Set<String> tags();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the title of the appointment
|
* the title of the appointment
|
||||||
* @return the title
|
* @return the title
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ public class Application {
|
|||||||
|
|
||||||
server.start();
|
server.start();
|
||||||
// TODO: schedule imports
|
// TODO: schedule imports
|
||||||
// TODO: update URL, when tags are selected
|
|
||||||
// TODO: allow list start time as URL param
|
// TODO: allow list start time as URL param
|
||||||
// TODO: provide ICAL and WEBDAV links
|
// TODO: provide ICAL and WEBDAV links
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ public class BaseAppointment implements Appointment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String ical() {
|
public String ical() { // TODO: implement
|
||||||
return "converting event (%s) to ical not implemented".formatted(title);
|
return "converting event (%s) to ical not implemented".formatted(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ public abstract class BaseImporter implements Importer {
|
|||||||
return extractDescriptionTag(eventTag);
|
return extractDescriptionTag(eventTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Result<String> extractDescription(Tag eventTag) {
|
protected Result<String> extractDescription(Tag eventTag) {
|
||||||
Result<Tag> titleTag = extractDescriptionTag(eventTag);
|
Result<Tag> titleTag = extractDescriptionTag(eventTag);
|
||||||
if (titleTag.optional().isEmpty()) return transform(titleTag);
|
if (titleTag.optional().isEmpty()) return transform(titleTag);
|
||||||
@@ -73,7 +72,6 @@ public abstract class BaseImporter implements Importer {
|
|||||||
return error("not implemented");
|
return error("not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Result<LocalDateTime> extractEnd(Tag eventTag) {
|
protected Result<LocalDateTime> extractEnd(Tag eventTag) {
|
||||||
Result<Tag> endTag = extractEndTag(eventTag);
|
Result<Tag> endTag = extractEndTag(eventTag);
|
||||||
if (endTag.optional().isEmpty()) return transform(endTag);
|
if (endTag.optional().isEmpty()) return transform(endTag);
|
||||||
@@ -114,7 +112,6 @@ public abstract class BaseImporter implements Importer {
|
|||||||
return Payload.of(event);
|
return Payload.of(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Result<Appointment> extractEvent(Result<Tag> domResult, Link eventPage) {
|
private Result<Appointment> extractEvent(Result<Tag> domResult, Link eventPage) {
|
||||||
return switch (domResult) {
|
return switch (domResult) {
|
||||||
case Payload<Tag> payload -> extractEvent(payload.get(), eventPage);
|
case Payload<Tag> payload -> extractEvent(payload.get(), eventPage);
|
||||||
@@ -127,7 +124,6 @@ public abstract class BaseImporter implements Importer {
|
|||||||
|
|
||||||
protected abstract Result<List<String>> extractEventUrls(Result<Tag> programPage);
|
protected abstract Result<List<String>> extractEventUrls(Result<Tag> programPage);
|
||||||
|
|
||||||
|
|
||||||
protected List<Link> extractLinks(Tag appointmentTag) {
|
protected List<Link> extractLinks(Tag appointmentTag) {
|
||||||
var links = new ArrayList<Link>();
|
var links = new ArrayList<Link>();
|
||||||
|
|
||||||
@@ -135,7 +131,8 @@ public abstract class BaseImporter implements Importer {
|
|||||||
.map(this::extractLinkAnchors)
|
.map(this::extractLinkAnchors)
|
||||||
.optional()
|
.optional()
|
||||||
.stream()
|
.stream()
|
||||||
.flatMap(List::stream).forEach(anchor -> {
|
.flatMap(List::stream)
|
||||||
|
.forEach(anchor -> {
|
||||||
var href = anchor.get("href");
|
var href = anchor.get("href");
|
||||||
if (href == null) return;
|
if (href == null) return;
|
||||||
if (!href.contains("://")) href = baseUrl() + href;
|
if (!href.contains("://")) href = baseUrl() + href;
|
||||||
@@ -164,7 +161,6 @@ public abstract class BaseImporter implements Importer {
|
|||||||
|
|
||||||
protected abstract Result<Tag> extractLocationTag(Tag eventTag);
|
protected abstract Result<Tag> extractLocationTag(Tag eventTag);
|
||||||
|
|
||||||
|
|
||||||
protected Result<LocalDateTime> extractStart(Tag eventTag) {
|
protected Result<LocalDateTime> extractStart(Tag eventTag) {
|
||||||
Result<Tag> startTag = extractStartTag(eventTag);
|
Result<Tag> startTag = extractStartTag(eventTag);
|
||||||
if (startTag.optional().isEmpty()) return transform(startTag);
|
if (startTag.optional().isEmpty()) return transform(startTag);
|
||||||
@@ -173,7 +169,6 @@ public abstract class BaseImporter implements Importer {
|
|||||||
|
|
||||||
protected abstract Result<Tag> extractStartTag(Tag eventTag);
|
protected abstract Result<Tag> extractStartTag(Tag eventTag);
|
||||||
|
|
||||||
|
|
||||||
protected abstract List<String> extractTags(Tag eventTag);
|
protected abstract List<String> extractTags(Tag eventTag);
|
||||||
|
|
||||||
protected Result<String> extractTitle(Tag eventTag) {
|
protected Result<String> extractTitle(Tag eventTag) {
|
||||||
@@ -186,7 +181,6 @@ public abstract class BaseImporter implements Importer {
|
|||||||
|
|
||||||
protected abstract Result<Tag> extractTitleTag(Tag eventTag);
|
protected abstract Result<Tag> extractTitleTag(Tag eventTag);
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<Appointment> fetch() {
|
public Stream<Appointment> fetch() {
|
||||||
var url = Payload.of(programURL());
|
var url = Payload.of(programURL());
|
||||||
@@ -284,7 +278,6 @@ public abstract class BaseImporter implements Importer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected static Result<Integer> toNumericMonth(String month) {
|
protected static Result<Integer> toNumericMonth(String month) {
|
||||||
month = month.toLowerCase();
|
month = month.toLowerCase();
|
||||||
if (month.startsWith("ja")) return Payload.of(1);
|
if (month.startsWith("ja")) return Payload.of(1);
|
||||||
@@ -302,7 +295,6 @@ public abstract class BaseImporter implements Importer {
|
|||||||
return error("Failed to recognize \"%s\" as a month!", month);
|
return error("Failed to recognize \"%s\" as a month!", month);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected static Result<URL> url(Result<String> urlResult) {
|
protected static Result<URL> url(Result<String> urlResult) {
|
||||||
if (urlResult.optional().isEmpty()) return transform(urlResult);
|
if (urlResult.optional().isEmpty()) return transform(urlResult);
|
||||||
var url = urlResult.optional().get();
|
var url = urlResult.optional().get();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public interface Database {
|
|||||||
* @param appointment the appointment to store
|
* @param appointment the appointment to store
|
||||||
* @return a clone of the provided appointment with id field set
|
* @return a clone of the provided appointment with id field set
|
||||||
*/
|
*/
|
||||||
public Result<Appointment> add(Appointment appointment);
|
Result<Appointment> add(Appointment appointment);
|
||||||
|
|
||||||
Result<List<String>> findTags(String infix);
|
Result<List<String>> findTags(String infix);
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ public interface Database {
|
|||||||
* @param till restrict appointments to times before this date time
|
* @param till restrict appointments to times before this date time
|
||||||
* @return list of appointments in this time span
|
* @return list of appointments in this time span
|
||||||
*/
|
*/
|
||||||
public Result<List<Appointment>> list(LocalDateTime from, LocalDateTime till, Integer count, Integer offset);
|
Result<List<Appointment>> list(LocalDateTime from, LocalDateTime till, Integer count, Integer offset);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* list appointments
|
* list appointments
|
||||||
@@ -37,7 +37,7 @@ public interface Database {
|
|||||||
* @param offset the number of appointments to skip
|
* @param offset the number of appointments to skip
|
||||||
* @return the list of appointments fetched from the db
|
* @return the list of appointments fetched from the db
|
||||||
*/
|
*/
|
||||||
public List<Appointment> listByTags(Set<String> tags, Integer count, Integer offset);
|
List<Appointment> listByTags(Set<String> tags, Integer count, Integer offset);
|
||||||
|
|
||||||
Result<Appointment> loadEvent(long id);
|
Result<Appointment> loadEvent(long id);
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ public class MariaDB implements Database {
|
|||||||
throw new RuntimeException("%s.createTables() not implemented!");
|
throw new RuntimeException("%s.createTables() not implemented!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Appointment> add(Appointment appointment) {
|
public Result<Appointment> add(Appointment appointment) {
|
||||||
try {
|
try {
|
||||||
@@ -290,7 +289,6 @@ public class MariaDB implements Database {
|
|||||||
return Payload.of(appointment);
|
return Payload.of(appointment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Appointment> listByTags(Set<String> tags, Integer count, Integer offset) {
|
public List<Appointment> listByTags(Set<String> tags, Integer count, Integer offset) {
|
||||||
return List.of();
|
return List.of();
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ public class NotFound<None> extends Error<None> {
|
|||||||
return new NotFound<>(message.formatted(fills), null, null);
|
return new NotFound<>(message.formatted(fills), null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <NewType> NotFound<NewType> transform() {
|
public <NewType> NotFound<NewType> transform() {
|
||||||
return new NotFound<>(message(), data(), exceptions());
|
return new NotFound<>(message(), data(), exceptions());
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
if (opt.isEmpty()) return transform(res);
|
if (opt.isEmpty()) return transform(res);
|
||||||
var event = opt.get();
|
var event = opt.get();
|
||||||
var title = getHeader(ex, TITLE);
|
var title = getHeader(ex, TITLE);
|
||||||
if (title.isEmpty()) return error("Missing title header");
|
if (title.isEmpty()) return HttpError.of(400,"Missing title header");
|
||||||
if (!title.get().equals(event.title())) return error("Title mismatch!");
|
if (!title.get().equals(event.title())) return HttpError.of(400,"Title mismatch!");
|
||||||
return db.add(opt.get()).map(ApiEndpoint::toJson);
|
return db.add(opt.get()).map(ApiEndpoint::toJson);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return error(e, "Failed to read event data from request body");
|
return error(e, "Failed to read event data from request body");
|
||||||
@@ -67,7 +67,7 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
try {
|
try {
|
||||||
aid = Long.parseLong(id);
|
aid = Long.parseLong(id);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return HttpError.of(400, "%s is not a valid appointment id!", id);
|
return HttpError.of(404, "%s is not a valid appointment id!", id);
|
||||||
}
|
}
|
||||||
var opt = getHeader(ex, "title");
|
var opt = getHeader(ex, "title");
|
||||||
if (opt.isEmpty()) return HttpError.of(412, "title missing");
|
if (opt.isEmpty()) return HttpError.of(412, "title missing");
|
||||||
@@ -83,7 +83,6 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
return db.removeAppointment(event.id());
|
return db.removeAppointment(event.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean doDelete(String path, HttpExchange ex) throws IOException {
|
public boolean doDelete(String path, HttpExchange ex) throws IOException {
|
||||||
if ("/event".equals(path)) return sendContent(ex, delete(path, ex));
|
if ("/event".equals(path)) return sendContent(ex, delete(path, ex));
|
||||||
@@ -94,8 +93,8 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
public boolean doGet(String path, HttpExchange ex) throws IOException {
|
public boolean doGet(String path, HttpExchange ex) throws IOException {
|
||||||
return switch (path) {
|
return switch (path) {
|
||||||
case "/event" -> sendContent(ex,getEvent(ex).map(ApiEndpoint::toJson).map(ApiEndpoint::httpError));
|
case "/event" -> sendContent(ex,getEvent(ex).map(ApiEndpoint::toJson).map(ApiEndpoint::httpError));
|
||||||
case "/events/json" -> sendContent(ex,eventList(ex).map(ApiEndpoint::toJsonList).map(ApiEndpoint::httpError));
|
|
||||||
case "/events/ical"-> sendContent(ex,eventList(ex).map(ApiEndpoint::toIcal).map(ApiEndpoint::httpError));
|
case "/events/ical"-> sendContent(ex,eventList(ex).map(ApiEndpoint::toIcal).map(ApiEndpoint::httpError));
|
||||||
|
case "/events/json" -> sendContent(ex,eventList(ex).map(ApiEndpoint::toJsonList).map(ApiEndpoint::httpError));
|
||||||
case "/tags" -> listTags(ex);
|
case "/tags" -> listTags(ex);
|
||||||
default -> unknownPath(ex, path);
|
default -> unknownPath(ex, path);
|
||||||
};
|
};
|
||||||
@@ -115,8 +114,7 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
|
|
||||||
private Result<List<Appointment>> eventList(HttpExchange ex) {
|
private Result<List<Appointment>> eventList(HttpExchange ex) {
|
||||||
var param = queryParam(ex);
|
var param = queryParam(ex);
|
||||||
var tags = nullable(param.get(TAGS)) //
|
var tags = nullable(param.get(TAGS)).stream()
|
||||||
.stream()
|
|
||||||
.flatMap(s -> Arrays.stream(s.split(",")))
|
.flatMap(s -> Arrays.stream(s.split(",")))
|
||||||
.map(s -> s.trim().toLowerCase())
|
.map(s -> s.trim().toLowerCase())
|
||||||
.toList();
|
.toList();
|
||||||
@@ -124,33 +122,28 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
Result<LocalDateTime> start = parseDate(param.get(START));
|
Result<LocalDateTime> start = parseDate(param.get(START));
|
||||||
if (start == null) start = parsePast(param.get(PAST));
|
if (start == null) start = parsePast(param.get(PAST));
|
||||||
if (start instanceof de.srsoftware.tools.Error<LocalDateTime> err) return err.transform();
|
if (start instanceof de.srsoftware.tools.Error<LocalDateTime> err) return err.transform();
|
||||||
var startDate = (start == null) ? null:
|
var startDate = (start == null) ? null: start.optional().orElse(null);
|
||||||
start.optional().orElse(null);
|
|
||||||
Integer offset = null;
|
Integer offset = null;
|
||||||
|
|
||||||
var o = param.get(OFFSET);
|
var o = param.get(OFFSET);
|
||||||
if (o != null) {
|
if (o != null) try {
|
||||||
try {
|
|
||||||
offset = Integer.parseInt(o);
|
offset = Integer.parseInt(o);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
return error("Offset (offset=%s) is not a number!", o);
|
return HttpError.of(400,"Offset (offset=%s) is not a number!", o);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Integer count = null;
|
Integer count = null;
|
||||||
o = param.get(COUNT);
|
o = param.get(COUNT);
|
||||||
if (o != null) {
|
if (o != null) try {
|
||||||
try {
|
|
||||||
count = Integer.parseInt(o);
|
count = Integer.parseInt(o);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
return error("Count (count=%s) is not a number!", o);
|
return HttpError.of(400,"Count (count=%s) is not a number!", o);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.list(startDate, null, count, offset).map(res -> filterByTags(res, tags));
|
return db.list(startDate, null, count, offset).map(res -> filterByTags(res, tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Result<List<Appointment>> filterByTags(Result<List<Appointment>> res, List<String> tags) {
|
private Result<List<Appointment>> filterByTags(Result<List<Appointment>> res, List<String> tags) {
|
||||||
if (tags == null || tags.isEmpty()) return res;
|
if (tags == null || tags.isEmpty()) return res;
|
||||||
var opt = res.optional();
|
var opt = res.optional();
|
||||||
@@ -159,16 +152,14 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
return Payload.of(list);
|
return Payload.of(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Result<Appointment> getEvent(HttpExchange ex) {
|
private Result<Appointment> getEvent(HttpExchange ex) {
|
||||||
var params = queryParam(ex);
|
var params = queryParam(ex);
|
||||||
var o = params.get(ID);
|
var o = params.get(ID);
|
||||||
if (o == null) return error("id parameter missing!");
|
if (o == null) return HttpError.of(400,"id parameter missing!");
|
||||||
try {
|
try {
|
||||||
long id = Long.parseLong(o);
|
return db.loadEvent(Long.parseLong(o));
|
||||||
return db.loadEvent(id);
|
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
return error(e, "Illegal format for id parameter (%s)", o);
|
return HttpError.of(400,e, "Illegal format for id parameter (%s)", o);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,8 +170,8 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
|
|
||||||
private boolean listTags(HttpExchange ex) throws IOException {
|
private boolean listTags(HttpExchange ex) throws IOException {
|
||||||
var params = queryParam(ex);
|
var params = queryParam(ex);
|
||||||
var infix = params.get("infix");
|
var infix = nullIfEmpty(params.get("infix"));
|
||||||
if (infix == null) return sendContent(ex, error("No infix set in method call parameters"));
|
if (infix == null) return sendContent(ex, HttpError.of(400,"No infix set in method call parameters"));
|
||||||
var res = db.findTags(infix).map(ApiEndpoint::sortTags);
|
var res = db.findTags(infix).map(ApiEndpoint::sortTags);
|
||||||
return sendContent(ex, res);
|
return sendContent(ex, res);
|
||||||
}
|
}
|
||||||
@@ -200,16 +191,14 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
return error("invalid date time format: %s", s);
|
return error("invalid date time format: %s", s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Result<LocalDateTime> parsePast(String s) {
|
public static Result<LocalDateTime> parsePast(String s) {
|
||||||
var start = LocalDateTime.now().withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
var start = LocalDateTime.now().withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||||
if (s == null) return Payload.of(start);
|
if (s == null) return Payload.of(start);
|
||||||
var matcher = PAST_PATTERN.matcher(s);
|
var matcher = PAST_PATTERN.matcher(s);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
for (int i = 0; i <= matcher.groupCount(); i++) System.out.printf("%s: %s\n", i, matcher.group(i));
|
|
||||||
|
|
||||||
int num = nullable(matcher.group(1)).map(Integer::parseInt).orElse(0);
|
int num = nullable(matcher.group(1)).map(Integer::parseInt).orElse(0);
|
||||||
switch (nullable(matcher.group(2)).orElse("")) {
|
var g2 = nullable(matcher.group(2)).orElse("");
|
||||||
|
switch (g2) {
|
||||||
case "m":
|
case "m":
|
||||||
start = start.minusMonths(num);
|
start = start.minusMonths(num);
|
||||||
break;
|
break;
|
||||||
@@ -236,12 +225,10 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
return Payload.of(list);
|
return Payload.of(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static boolean tagsMatch(Appointment event, List<String> tags) {
|
private static boolean tagsMatch(Appointment event, List<String> tags) {
|
||||||
return new HashSet<>(event.tags().stream().map(String::toLowerCase).toList()).containsAll(tags);
|
return new HashSet<>(event.tags().stream().map(String::toLowerCase).toList()).containsAll(tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Result<BaseAppointment> toEvent(JSONObject json) {
|
private Result<BaseAppointment> toEvent(JSONObject json) {
|
||||||
var description = json.has(DESCRIPTION) ? nullIfEmpty(json.getString(DESCRIPTION)) : null;
|
var description = json.has(DESCRIPTION) ? nullIfEmpty(json.getString(DESCRIPTION)) : null;
|
||||||
var title = json.has(TITLE) ? nullIfEmpty(json.getString(TITLE)) : null;
|
var title = json.has(TITLE) ? nullIfEmpty(json.getString(TITLE)) : null;
|
||||||
@@ -265,16 +252,14 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
.ifPresent(event::add);
|
.ifPresent(event::add);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (json.has(LINKS)) {
|
if (json.has(LINKS)) json.getJSONArray(LINKS).forEach(o -> {
|
||||||
json.getJSONArray(LINKS).forEach(o -> {
|
|
||||||
if (o instanceof JSONObject j) toLink(j).optional().ifPresent(event::addLinks);
|
if (o instanceof JSONObject j) toLink(j).optional().ifPresent(event::addLinks);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
if (json.has(TAGS)) json.getJSONArray(TAGS).forEach(o -> event.tags(o.toString()));
|
if (json.has(TAGS)) json.getJSONArray(TAGS).forEach(o -> event.tags(o.toString()));
|
||||||
return Payload.of(event);
|
return Payload.of(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Result<String> toIcal(Result<List<Appointment>> res) {
|
private static Result<String> toIcal(Result<List<Appointment>> res) {
|
||||||
var opt = res.optional();
|
var opt = res.optional();
|
||||||
if (opt.isEmpty()) return transform(res);
|
if (opt.isEmpty()) return transform(res);
|
||||||
@@ -282,11 +267,9 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
return Payload.of(list);
|
return Payload.of(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Result<JSONObject> toJson(Result<? extends Appointment> res) {
|
private static Result<JSONObject> toJson(Result<? extends Appointment> res) {
|
||||||
var opt = res.optional();
|
var opt = res.optional();
|
||||||
if (opt.isEmpty()) return transform(res);
|
return opt.isEmpty() ? transform(res) : Payload.of(opt.get().json());
|
||||||
return Payload.of(opt.get().json());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Result<List<JSONObject>> toJsonList(Result<List<Appointment>> res) {
|
private static Result<List<JSONObject>> toJsonList(Result<List<Appointment>> res) {
|
||||||
@@ -295,20 +278,22 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
var list = opt.get().stream().map(Appointment::json).toList();
|
var list = opt.get().stream().map(Appointment::json).toList();
|
||||||
return Payload.of(list);
|
return Payload.of(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Result<Link> toLink(JSONObject json) {
|
protected static Result<Link> toLink(JSONObject json) {
|
||||||
try {
|
try {
|
||||||
var description = json.getString(DESCRIPTION);
|
var description = json.getString(DESCRIPTION);
|
||||||
return Payload.of(json.getString(URL)).map(BaseImporter::url).map(url -> BaseImporter.link(url, description));
|
return Payload.of(json.getString(URL))
|
||||||
|
.map(BaseImporter::url)
|
||||||
|
.map(url -> BaseImporter.link(url, description));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return error(e, "Failed to create link from %s", json);
|
return error(e, "Failed to create link from %s", json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean unknownPath(HttpExchange ex, String path) throws IOException {
|
private boolean unknownPath(HttpExchange ex, String path) throws IOException {
|
||||||
return sendContent(ex, HttpError.of(404, "%s is not known to this API", path));
|
return sendContent(ex, HttpError.of(404, "%s is not known to this API", path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Result<JSONObject> updateEvent(HttpExchange ex) {
|
private Result<JSONObject> updateEvent(HttpExchange ex) {
|
||||||
try {
|
try {
|
||||||
var res = toEvent(json(ex));
|
var res = toEvent(json(ex));
|
||||||
@@ -316,8 +301,8 @@ public class ApiEndpoint extends PathHandler {
|
|||||||
if (opt.isEmpty()) return transform(res);
|
if (opt.isEmpty()) return transform(res);
|
||||||
var event = opt.get();
|
var event = opt.get();
|
||||||
var title = getHeader(ex, TITLE);
|
var title = getHeader(ex, TITLE);
|
||||||
if (title.isEmpty()) return error("Missing title header");
|
if (title.isEmpty()) return HttpError.of(400,"Missing title header");
|
||||||
if (!title.get().equals(event.title())) return error("Title mismatch!");
|
if (!title.get().equals(event.title())) return HttpError.of(400,"Title mismatch!");
|
||||||
return db.update(opt.get()).map(ApiEndpoint::toJson);
|
return db.update(opt.get()).map(ApiEndpoint::toJson);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return error(e, "Failed to read event data from request body");
|
return error(e, "Failed to read event data from request body");
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import java.io.IOException;
|
|||||||
|
|
||||||
public class IndexHandler extends PathHandler {
|
public class IndexHandler extends PathHandler {
|
||||||
PathHandler staticPages;
|
PathHandler staticPages;
|
||||||
|
|
||||||
public IndexHandler(PathHandler staticPages) {
|
public IndexHandler(PathHandler staticPages) {
|
||||||
this.staticPages = staticPages;
|
this.staticPages = staticPages;
|
||||||
}
|
}
|
||||||
@@ -18,6 +19,5 @@ public class IndexHandler extends PathHandler {
|
|||||||
case "/favicon.ico" -> staticPages.doGet("/images/%s".formatted(path), ex);
|
case "/favicon.ico" -> staticPages.doGet("/images/%s".formatted(path), ex);
|
||||||
default -> super.doGet(path, ex);
|
default -> super.doGet(path, ex);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
|
||||||
public class StaticHandler extends PathHandler {
|
public class StaticHandler extends PathHandler {
|
||||||
private final Optional<String> staticPath;
|
private final Optional<String> staticPath;
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ function openIcal(){
|
|||||||
var pos = url.indexOf('?');
|
var pos = url.indexOf('?');
|
||||||
if (pos>1) url = url.substring(0,pos);
|
if (pos>1) url = url.substring(0,pos);
|
||||||
if (!url.endsWith('/')) url += '/';
|
if (!url.endsWith('/')) url += '/';
|
||||||
url += 'api/ical'+query;
|
url += 'api/events/ical'+query;
|
||||||
var elem = create('a');
|
var elem = create('a');
|
||||||
elem.setAttribute('href',url);
|
elem.setAttribute('href',url);
|
||||||
elem.setAttribute('download','calendar.ical');
|
elem.setAttribute('download','calendar.ical');
|
||||||
|
|||||||
@@ -266,6 +266,14 @@ paths:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
|
'400':
|
||||||
|
description: invalid input
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: {"message":"No infix set in method call parameters"}
|
||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
Appointment:
|
Appointment:
|
||||||
|
|||||||
Reference in New Issue
Block a user