|
|
|
@ -51,8 +51,8 @@ public class ApiEndpoint extends PathHandler {
@@ -51,8 +51,8 @@ public class ApiEndpoint extends PathHandler {
|
|
|
|
|
if (opt.isEmpty()) return transform(res); |
|
|
|
|
var event = opt.get(); |
|
|
|
|
var title = getHeader(ex, TITLE); |
|
|
|
|
if (title.isEmpty()) return error("Missing title header"); |
|
|
|
|
if (!title.get().equals(event.title())) return error("Title mismatch!"); |
|
|
|
|
if (title.isEmpty()) return HttpError.of(400,"Missing title header"); |
|
|
|
|
if (!title.get().equals(event.title())) return HttpError.of(400,"Title mismatch!"); |
|
|
|
|
return db.add(opt.get()).map(ApiEndpoint::toJson); |
|
|
|
|
} catch (IOException e) { |
|
|
|
|
return error(e, "Failed to read event data from request body"); |
|
|
|
@ -67,7 +67,7 @@ public class ApiEndpoint extends PathHandler {
@@ -67,7 +67,7 @@ public class ApiEndpoint extends PathHandler {
|
|
|
|
|
try { |
|
|
|
|
aid = Long.parseLong(id); |
|
|
|
|
} 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"); |
|
|
|
|
if (opt.isEmpty()) return HttpError.of(412, "title missing"); |
|
|
|
@ -83,7 +83,6 @@ public class ApiEndpoint extends PathHandler {
@@ -83,7 +83,6 @@ public class ApiEndpoint extends PathHandler {
|
|
|
|
|
return db.removeAppointment(event.id()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public boolean doDelete(String path, HttpExchange ex) throws IOException { |
|
|
|
|
if ("/event".equals(path)) return sendContent(ex, delete(path, ex)); |
|
|
|
@ -94,233 +93,219 @@ public class ApiEndpoint extends PathHandler {
@@ -94,233 +93,219 @@ public class ApiEndpoint extends PathHandler {
|
|
|
|
|
public boolean doGet(String path, HttpExchange ex) throws IOException { |
|
|
|
|
return switch (path) { |
|
|
|
|
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 "/tags" -> listTags(ex); |
|
|
|
|
default -> unknownPath(ex,path); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public boolean doPatch(String path, HttpExchange ex) throws IOException { |
|
|
|
|
if ("/event".equals(path)) return sendContent(ex, updateEvent(ex)); |
|
|
|
|
return unknownPath(ex, path); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public boolean doPost(String path, HttpExchange ex) throws IOException { |
|
|
|
|
if ("/event".equals(path)) return sendContent(ex, createEvent(ex)); |
|
|
|
|
return unknownPath(ex, path); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Result<List<Appointment>> eventList(HttpExchange ex) { |
|
|
|
|
var param = queryParam(ex); |
|
|
|
|
var tags = nullable(param.get(TAGS)) //
|
|
|
|
|
.stream() |
|
|
|
|
.flatMap(s -> Arrays.stream(s.split(","))) |
|
|
|
|
.map(s -> s.trim().toLowerCase()) |
|
|
|
|
.toList(); |
|
|
|
|
|
|
|
|
|
Result<LocalDateTime> start = parseDate(param.get(START)); |
|
|
|
|
if (start == null) start = parsePast(param.get(PAST)); |
|
|
|
|
if (start instanceof de.srsoftware.tools.Error<LocalDateTime> err) return err.transform(); |
|
|
|
|
var startDate = (start == null) ? null: |
|
|
|
|
start.optional().orElse(null); |
|
|
|
|
Integer offset = null; |
|
|
|
|
|
|
|
|
|
var o = param.get(OFFSET); |
|
|
|
|
if (o != null) { |
|
|
|
|
try { |
|
|
|
|
offset = Integer.parseInt(o); |
|
|
|
|
} catch (NumberFormatException e) { |
|
|
|
|
return error("Offset (offset=%s) is not a number!", o); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Integer count = null; |
|
|
|
|
o = param.get(COUNT); |
|
|
|
|
if (o != null) { |
|
|
|
|
try { |
|
|
|
|
count = Integer.parseInt(o); |
|
|
|
|
} catch (NumberFormatException e) { |
|
|
|
|
return error("Count (count=%s) is not a number!", o); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return db.list(startDate, null, count, offset).map(res -> filterByTags(res, tags)); |
|
|
|
|
case "/events/json" -> sendContent(ex,eventList(ex).map(ApiEndpoint::toJsonList).map(ApiEndpoint::httpError)); |
|
|
|
|
case "/tags" -> listTags(ex); |
|
|
|
|
default -> unknownPath(ex, path); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public boolean doPatch(String path, HttpExchange ex) throws IOException { |
|
|
|
|
if ("/event".equals(path)) return sendContent(ex, updateEvent(ex)); |
|
|
|
|
return unknownPath(ex, path); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public boolean doPost(String path, HttpExchange ex) throws IOException { |
|
|
|
|
if ("/event".equals(path)) return sendContent(ex, createEvent(ex)); |
|
|
|
|
return unknownPath(ex, path); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Result<List<Appointment>> eventList(HttpExchange ex) { |
|
|
|
|
var param = queryParam(ex); |
|
|
|
|
var tags = nullable(param.get(TAGS)).stream() |
|
|
|
|
.flatMap(s -> Arrays.stream(s.split(","))) |
|
|
|
|
.map(s -> s.trim().toLowerCase()) |
|
|
|
|
.toList(); |
|
|
|
|
|
|
|
|
|
Result<LocalDateTime> start = parseDate(param.get(START)); |
|
|
|
|
if (start == null) start = parsePast(param.get(PAST)); |
|
|
|
|
if (start instanceof de.srsoftware.tools.Error<LocalDateTime> err) return err.transform(); |
|
|
|
|
var startDate = (start == null) ? null: start.optional().orElse(null); |
|
|
|
|
Integer offset = null; |
|
|
|
|
|
|
|
|
|
var o = param.get(OFFSET); |
|
|
|
|
if (o != null) try { |
|
|
|
|
offset = Integer.parseInt(o); |
|
|
|
|
} catch (NumberFormatException e) { |
|
|
|
|
return HttpError.of(400,"Offset (offset=%s) is not a number!", o); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Result<List<Appointment>> filterByTags(Result<List<Appointment>> res, List<String> tags) { |
|
|
|
|
if (tags == null || tags.isEmpty()) return res; |
|
|
|
|
var opt = res.optional(); |
|
|
|
|
if (opt.isEmpty()) return transform(res); |
|
|
|
|
var list = opt.get().stream().filter(event -> tagsMatch(event, tags)).toList(); |
|
|
|
|
return Payload.of(list); |
|
|
|
|
Integer count = null; |
|
|
|
|
o = param.get(COUNT); |
|
|
|
|
if (o != null) try { |
|
|
|
|
count = Integer.parseInt(o); |
|
|
|
|
} catch (NumberFormatException e) { |
|
|
|
|
return HttpError.of(400,"Count (count=%s) is not a number!", o); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return db.list(startDate, null, count, offset).map(res -> filterByTags(res, tags)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Result<Appointment> getEvent(HttpExchange ex) { |
|
|
|
|
var params = queryParam(ex); |
|
|
|
|
var o = params.get(ID); |
|
|
|
|
if (o == null) return error("id parameter missing!"); |
|
|
|
|
try { |
|
|
|
|
long id = Long.parseLong(o); |
|
|
|
|
return db.loadEvent(id); |
|
|
|
|
} catch (NumberFormatException e) { |
|
|
|
|
return error(e, "Illegal format for id parameter (%s)", o); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static <T> Result<T> httpError(Result<T> res) { |
|
|
|
|
if (res instanceof NotFound<T> notFound) return HttpError.of(404, notFound.message()); |
|
|
|
|
return res; |
|
|
|
|
} |
|
|
|
|
private Result<List<Appointment>> filterByTags(Result<List<Appointment>> res, List<String> tags) { |
|
|
|
|
if (tags == null || tags.isEmpty()) return res; |
|
|
|
|
var opt = res.optional(); |
|
|
|
|
if (opt.isEmpty()) return transform(res); |
|
|
|
|
var list = opt.get().stream().filter(event -> tagsMatch(event, tags)).toList(); |
|
|
|
|
return Payload.of(list); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private boolean listTags(HttpExchange ex) throws IOException { |
|
|
|
|
var params = queryParam(ex); |
|
|
|
|
var infix = params.get("infix"); |
|
|
|
|
if (infix == null) return sendContent(ex, error("No infix set in method call parameters")); |
|
|
|
|
var res = db.findTags(infix).map(ApiEndpoint::sortTags); |
|
|
|
|
return sendContent(ex, res); |
|
|
|
|
private Result<Appointment> getEvent(HttpExchange ex) { |
|
|
|
|
var params = queryParam(ex); |
|
|
|
|
var o = params.get(ID); |
|
|
|
|
if (o == null) return HttpError.of(400,"id parameter missing!"); |
|
|
|
|
try { |
|
|
|
|
return db.loadEvent(Long.parseLong(o)); |
|
|
|
|
} catch (NumberFormatException e) { |
|
|
|
|
return HttpError.of(400,e, "Illegal format for id parameter (%s)", o); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public static Result<LocalDateTime> parseDate(String s) { |
|
|
|
|
if (s == null) return null; |
|
|
|
|
var matcher = DATE_TIME.matcher(s); |
|
|
|
|
if (matcher.find()) { |
|
|
|
|
int year = Integer.parseInt(matcher.group(1)); |
|
|
|
|
int month = Integer.parseInt(matcher.group(2)); |
|
|
|
|
int day = nullable(matcher.group(4)).map(Integer::parseInt).orElse(1); |
|
|
|
|
int hour = nullable(matcher.group(6)).map(Integer::parseInt).orElse(0); |
|
|
|
|
int minute = nullable(matcher.group(7)).map(Integer::parseInt).orElse(0); |
|
|
|
|
int second = nullable(matcher.group(9)).map(Integer::parseInt).orElse(0); |
|
|
|
|
return Payload.of(LocalDateTime.of(year, month, day, hour, minute, second)); |
|
|
|
|
} |
|
|
|
|
return error("invalid date time format: %s", s); |
|
|
|
|
} |
|
|
|
|
private static <T> Result<T> httpError(Result<T> res) { |
|
|
|
|
if (res instanceof NotFound<T> notFound) return HttpError.of(404, notFound.message()); |
|
|
|
|
return res; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private boolean listTags(HttpExchange ex) throws IOException { |
|
|
|
|
var params = queryParam(ex); |
|
|
|
|
var infix = nullIfEmpty(params.get("infix")); |
|
|
|
|
if (infix == null) return sendContent(ex, HttpError.of(400,"No infix set in method call parameters")); |
|
|
|
|
var res = db.findTags(infix).map(ApiEndpoint::sortTags); |
|
|
|
|
return sendContent(ex, res); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public static Result<LocalDateTime> parsePast(String s) { |
|
|
|
|
var start = LocalDateTime.now().withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0); |
|
|
|
|
if (s == null) return Payload.of(start); |
|
|
|
|
var matcher = PAST_PATTERN.matcher(s); |
|
|
|
|
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); |
|
|
|
|
switch (nullable(matcher.group(2)).orElse("")) { |
|
|
|
|
case "m": |
|
|
|
|
start = start.minusMonths(num); |
|
|
|
|
break; |
|
|
|
|
case "y": |
|
|
|
|
start = start.minusYears(num); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
var all = matcher.group(3); |
|
|
|
|
if ("all".equals(all)) return null; |
|
|
|
|
return Payload.of(start); |
|
|
|
|
} |
|
|
|
|
return error("invalid past format: %s", s); |
|
|
|
|
public static Result<LocalDateTime> parseDate(String s) { |
|
|
|
|
if (s == null) return null; |
|
|
|
|
var matcher = DATE_TIME.matcher(s); |
|
|
|
|
if (matcher.find()) { |
|
|
|
|
int year = Integer.parseInt(matcher.group(1)); |
|
|
|
|
int month = Integer.parseInt(matcher.group(2)); |
|
|
|
|
int day = nullable(matcher.group(4)).map(Integer::parseInt).orElse(1); |
|
|
|
|
int hour = nullable(matcher.group(6)).map(Integer::parseInt).orElse(0); |
|
|
|
|
int minute = nullable(matcher.group(7)).map(Integer::parseInt).orElse(0); |
|
|
|
|
int second = nullable(matcher.group(9)).map(Integer::parseInt).orElse(0); |
|
|
|
|
return Payload.of(LocalDateTime.of(year, month, day, hour, minute, second)); |
|
|
|
|
} |
|
|
|
|
return error("invalid date time format: %s", s); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<List<String>> sortTags(Result<List<String>> listResult) { |
|
|
|
|
if (listResult.optional().isEmpty()) return listResult; |
|
|
|
|
List<String> list = listResult.optional().get(); |
|
|
|
|
while (list.size() > 15) { |
|
|
|
|
int longest = list.stream().map(String::length).reduce(0, Integer::max); |
|
|
|
|
var subset = list.stream().filter(s -> s.length() < longest).toList(); |
|
|
|
|
if (subset.size() < 3) return Payload.of(list); |
|
|
|
|
list = subset; |
|
|
|
|
public static Result<LocalDateTime> parsePast(String s) { |
|
|
|
|
var start = LocalDateTime.now().withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0); |
|
|
|
|
if (s == null) return Payload.of(start); |
|
|
|
|
var matcher = PAST_PATTERN.matcher(s); |
|
|
|
|
if (matcher.find()) { |
|
|
|
|
int num = nullable(matcher.group(1)).map(Integer::parseInt).orElse(0); |
|
|
|
|
var g2 = nullable(matcher.group(2)).orElse(""); |
|
|
|
|
switch (g2) { |
|
|
|
|
case "m": |
|
|
|
|
start = start.minusMonths(num); |
|
|
|
|
break; |
|
|
|
|
case "y": |
|
|
|
|
start = start.minusYears(num); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
return Payload.of(list); |
|
|
|
|
var all = matcher.group(3); |
|
|
|
|
if ("all".equals(all)) return null; |
|
|
|
|
return Payload.of(start); |
|
|
|
|
} |
|
|
|
|
return error("invalid past format: %s", s); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static boolean tagsMatch(Appointment event, List<String> tags) { |
|
|
|
|
return new HashSet<>(event.tags().stream().map(String::toLowerCase).toList()).containsAll(tags); |
|
|
|
|
private static Result<List<String>> sortTags(Result<List<String>> listResult) { |
|
|
|
|
if (listResult.optional().isEmpty()) return listResult; |
|
|
|
|
List<String> list = listResult.optional().get(); |
|
|
|
|
while (list.size() > 15) { |
|
|
|
|
int longest = list.stream().map(String::length).reduce(0, Integer::max); |
|
|
|
|
var subset = list.stream().filter(s -> s.length() < longest).toList(); |
|
|
|
|
if (subset.size() < 3) return Payload.of(list); |
|
|
|
|
list = subset; |
|
|
|
|
} |
|
|
|
|
return Payload.of(list); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static boolean tagsMatch(Appointment event, List<String> tags) { |
|
|
|
|
return new HashSet<>(event.tags().stream().map(String::toLowerCase).toList()).containsAll(tags); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Result<BaseAppointment> toEvent(JSONObject json) { |
|
|
|
|
var description = json.has(DESCRIPTION) ? nullIfEmpty(json.getString(DESCRIPTION)) : null; |
|
|
|
|
var title = json.has(TITLE) ? nullIfEmpty(json.getString(TITLE)) : null; |
|
|
|
|
if (title == null) return error("title missing"); |
|
|
|
|
var start = json.has(START) ? nullIfEmpty(json.getString(START)) : null; |
|
|
|
|
if (start == null) return error("start missing"); |
|
|
|
|
var startDate = nullable(start).map(dt -> LocalDateTime.parse(dt, ISO_DATE_TIME)).orElse(null); |
|
|
|
|
var end = json.has(END) ? nullIfEmpty(json.getString(END)) : null; |
|
|
|
|
var endDate = nullable(end).map(dt -> LocalDateTime.parse(dt, ISO_DATE_TIME)).orElse(null); |
|
|
|
|
var location = json.has(LOCATION) ? json.getString(LOCATION) : null; |
|
|
|
|
if (location == null) return error("location missing"); |
|
|
|
|
var aid = json.has(AID) ? json.getLong(AID) : 0; |
|
|
|
|
var event = new BaseAppointment(aid, title, description, startDate, endDate, location); |
|
|
|
|
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); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
if (json.has(TAGS)) json.getJSONArray(TAGS).forEach(o -> event.tags(o.toString())); |
|
|
|
|
return Payload.of(event); |
|
|
|
|
private Result<BaseAppointment> toEvent(JSONObject json) { |
|
|
|
|
var description = json.has(DESCRIPTION) ? nullIfEmpty(json.getString(DESCRIPTION)) : null; |
|
|
|
|
var title = json.has(TITLE) ? nullIfEmpty(json.getString(TITLE)) : null; |
|
|
|
|
if (title == null) return error("title missing"); |
|
|
|
|
var start = json.has(START) ? nullIfEmpty(json.getString(START)) : null; |
|
|
|
|
if (start == null) return error("start missing"); |
|
|
|
|
var startDate = nullable(start).map(dt -> LocalDateTime.parse(dt, ISO_DATE_TIME)).orElse(null); |
|
|
|
|
var end = json.has(END) ? nullIfEmpty(json.getString(END)) : null; |
|
|
|
|
var endDate = nullable(end).map(dt -> LocalDateTime.parse(dt, ISO_DATE_TIME)).orElse(null); |
|
|
|
|
var location = json.has(LOCATION) ? json.getString(LOCATION) : null; |
|
|
|
|
if (location == null) return error("location missing"); |
|
|
|
|
var aid = json.has(AID) ? json.getLong(AID) : 0; |
|
|
|
|
var event = new BaseAppointment(aid, title, description, startDate, endDate, location); |
|
|
|
|
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); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
if (json.has(TAGS)) json.getJSONArray(TAGS).forEach(o -> event.tags(o.toString())); |
|
|
|
|
return Payload.of(event); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<String> toIcal(Result<List<Appointment>> res) { |
|
|
|
|
var opt = res.optional(); |
|
|
|
|
if (opt.isEmpty()) return transform(res); |
|
|
|
|
var list = opt.get().stream().map(Appointment::ical).collect(Collectors.joining("\n")); |
|
|
|
|
return Payload.of(list); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<String> toIcal(Result<List<Appointment>> res) { |
|
|
|
|
var opt = res.optional(); |
|
|
|
|
if (opt.isEmpty()) return transform(res); |
|
|
|
|
var list = opt.get().stream().map(Appointment::ical).collect(Collectors.joining("\n")); |
|
|
|
|
return Payload.of(list); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<JSONObject> toJson(Result<? extends Appointment> res) { |
|
|
|
|
var opt = res.optional(); |
|
|
|
|
if (opt.isEmpty()) return transform(res); |
|
|
|
|
return Payload.of(opt.get().json()); |
|
|
|
|
} |
|
|
|
|
private static Result<JSONObject> toJson(Result<? extends Appointment> res) { |
|
|
|
|
var opt = res.optional(); |
|
|
|
|
return opt.isEmpty() ? transform(res) : Payload.of(opt.get().json()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<List<JSONObject>> toJsonList(Result<List<Appointment>> res) { |
|
|
|
|
var opt = res.optional(); |
|
|
|
|
if (opt.isEmpty()) return transform(res); |
|
|
|
|
var list = opt.get().stream().map(Appointment::json).toList(); |
|
|
|
|
return Payload.of(list); |
|
|
|
|
} |
|
|
|
|
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)); |
|
|
|
|
private static Result<List<JSONObject>> toJsonList(Result<List<Appointment>> res) { |
|
|
|
|
var opt = res.optional(); |
|
|
|
|
if (opt.isEmpty()) return transform(res); |
|
|
|
|
var list = opt.get().stream().map(Appointment::json).toList(); |
|
|
|
|
return Payload.of(list); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
return error(e, "Failed to create link from %s", json); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
private boolean unknownPath(HttpExchange ex, String path) throws IOException { |
|
|
|
|
return sendContent(ex, HttpError.of(404, "%s is not known to this API", path)); |
|
|
|
|
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(e, "Failed to create link from %s", json); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private boolean unknownPath(HttpExchange ex, String path) throws IOException { |
|
|
|
|
return sendContent(ex, HttpError.of(404, "%s is not known to this API", path)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Result<JSONObject> updateEvent(HttpExchange ex) { |
|
|
|
|
try { |
|
|
|
|
var res = toEvent(json(ex)); |
|
|
|
|
var opt = res.optional(); |
|
|
|
|
if (opt.isEmpty()) return transform(res); |
|
|
|
|
var event = opt.get(); |
|
|
|
|
var title = getHeader(ex, TITLE); |
|
|
|
|
if (title.isEmpty()) return error("Missing title header"); |
|
|
|
|
if (!title.get().equals(event.title())) return error("Title mismatch!"); |
|
|
|
|
return db.update(opt.get()).map(ApiEndpoint::toJson); |
|
|
|
|
} catch (IOException e) { |
|
|
|
|
return error(e, "Failed to read event data from request body"); |
|
|
|
|
} |
|
|
|
|
private Result<JSONObject> updateEvent(HttpExchange ex) { |
|
|
|
|
try { |
|
|
|
|
var res = toEvent(json(ex)); |
|
|
|
|
var opt = res.optional(); |
|
|
|
|
if (opt.isEmpty()) return transform(res); |
|
|
|
|
var event = opt.get(); |
|
|
|
|
var title = getHeader(ex, TITLE); |
|
|
|
|
if (title.isEmpty()) return HttpError.of(400,"Missing title header"); |
|
|
|
|
if (!title.get().equals(event.title())) return HttpError.of(400,"Title mismatch!"); |
|
|
|
|
return db.update(opt.get()).map(ApiEndpoint::toJson); |
|
|
|
|
} catch (IOException e) { |
|
|
|
|
return error(e, "Failed to read event data from request body"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|