diff --git a/.classpath b/.classpath
index f3bdce0..607b789 100644
--- a/.classpath
+++ b/.classpath
@@ -12,6 +12,13 @@
+
+
+
+
+
+
+
diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs
index e479558..e889521 100644
--- a/.settings/org.eclipse.buildship.core.prefs
+++ b/.settings/org.eclipse.buildship.core.prefs
@@ -1,13 +1,2 @@
-arguments=
-auto.sync=false
-build.scans.enabled=false
-connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
-gradle.user.home=
-java.home=
-jvm.arguments=
-offline.mode=false
-override.workspace.settings=false
-show.console.view=false
-show.executions.view=false
diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component
index a2ed0c0..31a62d0 100644
--- a/.settings/org.eclipse.wst.common.component
+++ b/.settings/org.eclipse.wst.common.component
@@ -1,8 +1,14 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 0331654..fcfefb2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -18,7 +18,7 @@ apply plugin: 'war'
apply plugin: 'application'
group = 'de.jottyfan.camporganizer'
-version = '0.0.9'
+version = '0.1.0'
sourceCompatibility = 17
mainClassName = "de.jottyfan.camporganizer.Main"
@@ -51,6 +51,8 @@ dependencies {
implementation 'org.webjars:datatables:1.11.4'
implementation 'org.webjars:select2:4.0.13'
+ implementation 'net.sf.biweekly:biweekly:0.6.6'
+
implementation 'org.keycloak:keycloak-spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-jooq'
diff --git a/src/main/java/de/jottyfan/camporganizer/module/ical/ICalController.java b/src/main/java/de/jottyfan/camporganizer/module/ical/ICalController.java
new file mode 100644
index 0000000..8db418f
--- /dev/null
+++ b/src/main/java/de/jottyfan/camporganizer/module/ical/ICalController.java
@@ -0,0 +1,31 @@
+package de.jottyfan.camporganizer.module.ical;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+
+/**
+ *
+ * @author jotty
+ *
+ */
+@Controller
+public class ICalController {
+
+ @Autowired
+ private IICalService service;
+
+ /**
+ * generate the ical response stream
+ *
+ * @throws IOException on io errors
+ */
+ @GetMapping("/ical")
+ public void generate(HttpServletResponse response) throws IOException {
+ service.generate(response);
+ }
+}
diff --git a/src/main/java/de/jottyfan/camporganizer/module/ical/IICalService.java b/src/main/java/de/jottyfan/camporganizer/module/ical/IICalService.java
new file mode 100644
index 0000000..6262941
--- /dev/null
+++ b/src/main/java/de/jottyfan/camporganizer/module/ical/IICalService.java
@@ -0,0 +1,23 @@
+package de.jottyfan.camporganizer.module.ical;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ *
+ * @author jotty
+ *
+ */
+public interface IICalService {
+
+ /**
+ * generate the ical
+ *
+ * @param response the response for the output stream
+ *
+ * @return true if successful, false otherwise
+ * @throws IOException on io errors
+ */
+ public Boolean generate(HttpServletResponse response) throws IOException;
+}
diff --git a/src/main/java/de/jottyfan/camporganizer/module/ical/impl/ICalGateway.java b/src/main/java/de/jottyfan/camporganizer/module/ical/impl/ICalGateway.java
new file mode 100644
index 0000000..ed45fdb
--- /dev/null
+++ b/src/main/java/de/jottyfan/camporganizer/module/ical/impl/ICalGateway.java
@@ -0,0 +1,76 @@
+package de.jottyfan.camporganizer.module.ical.impl;
+
+import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMP;
+import static de.jottyfan.camporganizer.db.jooq.Tables.T_LOCATION;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jooq.DSLContext;
+import org.jooq.Record5;
+import org.jooq.SelectOnConditionStep;
+import org.jooq.exception.DataAccessException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import biweekly.ICalendar;
+import biweekly.component.VEvent;
+import biweekly.io.TimezoneAssignment;
+import biweekly.property.Summary;
+
+/**
+*
+* @author jotty
+*
+*/
+@Repository
+@Transactional(transactionManager = "transactionManager")
+public class ICalGateway {
+ private static final Logger LOGGER = LogManager.getLogger(ICalGateway.class);
+
+ @Autowired
+ private DSLContext jooq;
+
+ /**
+ * load all camp dates from db and generate an ical from it
+ *
+ * @return ical containing all (at least none) of the camp dates
+ * @throws DataAccessException
+ */
+ public ICalendar getIcal() throws DataAccessException {
+ SelectOnConditionStep> sql = jooq
+ // @formatter:off
+ .select(T_CAMP.NAME,
+ T_CAMP.ARRIVE,
+ T_CAMP.DEPART,
+ T_LOCATION.NAME,
+ T_LOCATION.URL)
+ .from(T_CAMP)
+ .leftJoin(T_LOCATION).on(T_LOCATION.PK.eq(T_CAMP.FK_LOCATION));
+ // @formatter:on
+ LOGGER.debug(sql.toString());
+ ICalendar ical = new ICalendar();
+ ical.getTimezoneInfo().setDefaultTimezone(TimezoneAssignment.download(TimeZone.getTimeZone("Europe/Berlin"), false));
+ for (Record5 r : sql.fetch()) {
+ VEvent event = new VEvent();
+ Summary summary = event.setSummary(r.get(T_CAMP.NAME));
+ summary.setLanguage("de");
+ LocalDateTime startDate = r.get(T_CAMP.ARRIVE);
+ LocalDateTime endDate = r.get(T_CAMP.DEPART);
+ // because of the specification, end dates are exclusive - and without time, the day won't be counted
+ endDate = endDate.plusDays(1l);
+ event.setLocation(r.get(T_LOCATION.NAME));
+ event.setDateStart(startDate == null ? null : Date.from(startDate.atZone(ZoneId.of("Europe/Berlin")).toInstant()), false);
+ event.setDateEnd(endDate == null ? null : Date.from(endDate.atZone(ZoneId.of("Europe/Berlin")).toInstant()), false);
+ event.setUrl(r.get(T_LOCATION.URL));
+ event.setDescription("zur Anmeldung: http://anmeldung.onkelwernerfreizeiten.de");
+ ical.addEvent(event);
+ }
+ return ical;
+ }
+}
diff --git a/src/main/java/de/jottyfan/camporganizer/module/ical/impl/ICalService.java b/src/main/java/de/jottyfan/camporganizer/module/ical/impl/ICalService.java
new file mode 100644
index 0000000..ad0d7a4
--- /dev/null
+++ b/src/main/java/de/jottyfan/camporganizer/module/ical/impl/ICalService.java
@@ -0,0 +1,41 @@
+package de.jottyfan.camporganizer.module.ical.impl;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import biweekly.Biweekly;
+import biweekly.ICalendar;
+import de.jottyfan.camporganizer.module.ical.IICalService;
+
+/**
+ *
+ * @author jotty
+ *
+ */
+@Service
+public class ICalService implements IICalService {
+
+ @Autowired
+ private ICalGateway gateway;
+
+ @Override
+ public Boolean generate(HttpServletResponse response) throws IOException {
+ ICalendar ical = gateway.getIcal();
+ String content = Biweekly.write(ical).go();
+
+ response.setHeader("charset", "UTF-8");
+ response.setHeader("Content-Type", "text/calendar");
+ response.setHeader("Content-Disposition", "attachment; filename=\"onkelwernerfreizeiten.de.ics\"");
+
+ response.getWriter().write(content);
+ response.getWriter().flush();
+ response.flushBuffer();
+
+ return true;
+ }
+
+}
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
index 18a3032..acda33b 100644
--- a/src/main/resources/templates/index.html
+++ b/src/main/resources/templates/index.html
@@ -7,15 +7,15 @@