Browse Source

minor improvements

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
main
Stephan Richter 4 months ago
parent
commit
f60bd90283
  1. 2
      build.gradle.kts
  2. 5
      de.srsoftware.cal.api/src/main/java/de/srsoftware/cal/api/Appointment.java
  3. 1
      de.srsoftware.cal.app/src/main/java/de/srsoftware/cal/app/Application.java
  4. 2
      de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseAppointment.java
  5. 14
      de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseImporter.java
  6. 6
      de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/Database.java
  7. 2
      de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/MariaDB.java
  8. 1
      de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/NotFound.java
  9. 75
      de.srsoftware.cal.web/src/main/java/de/srsoftware/cal/ApiEndpoint.java
  10. 2
      de.srsoftware.cal.web/src/main/java/de/srsoftware/cal/IndexHandler.java
  11. 1
      de.srsoftware.cal.web/src/main/java/de/srsoftware/cal/StaticHandler.java
  12. 2
      de.srsoftware.cal.web/src/main/resources/script/index.js
  13. 8
      doc/openapi3_0.yaml

2
build.gradle.kts

@ -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()
} }

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

@ -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

1
de.srsoftware.cal.app/src/main/java/de/srsoftware/cal/app/Application.java

@ -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
} }

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

@ -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);
} }

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

@ -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;
@ -143,7 +140,7 @@ public abstract class BaseImporter implements Importer {
Payload // Payload //
.of(href) .of(href)
.map(BaseImporter::url) .map(BaseImporter::url)
.map(url -> link(url,text)) .map(url -> link(url, text))
.optional() .optional()
.ifPresent(links::add); .ifPresent(links::add);
}); });
@ -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();

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

@ -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);

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

@ -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();

1
de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/NotFound.java

@ -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());

75
de.srsoftware.cal.web/src/main/java/de/srsoftware/cal/ApiEndpoint.java

@ -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,10 +93,10 @@ 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,11 +301,11 @@ 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");
} }
} }
} }

2
de.srsoftware.cal.web/src/main/java/de/srsoftware/cal/IndexHandler.java

@ -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);
}; };
} }
} }

1
de.srsoftware.cal.web/src/main/java/de/srsoftware/cal/StaticHandler.java

@ -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;

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

@ -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');

8
doc/openapi3_0.yaml

@ -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:

Loading…
Cancel
Save