Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ creates the client role of the same name by hand.
| `GET/POST /registration/{tenant}/delete` | anonymous | Self-service deregistration |
| `DELETE /registration/{tenant}/delete?id=` | anonymous | Delete one registration by UUID (used by the admin UI and the mail link) |
| `GET /registration/{tenant}/ical/{eventId}` | anonymous | Download `.ics` calendar file |
| `GET /webinar/{tenant}/{eventId}` | anonymous | Webinar landing page (today-only in prod) |
| `GET /meeting/{tenant}/{eventId}` | anonymous | Meeting landing page (today-only in prod, and only once a `meetingLink` is stored) |
| `GET /admin/{tenant}/events` | OIDC | Admin overview of all events |
| `GET /admin/{tenant}/events/{eventId}` | OIDC | List registrations for one event (HTML, or JSON with `Accept: application/json`) [^1] |
| `GET/POST /admin/{tenant}/data` | OIDC | View and edit the tenant's master data |
Expand Down Expand Up @@ -120,7 +120,7 @@ script slot, the height reporter). Above it sit two layouts, and a page includes
| Layout | Used by | Adds |
|---|---|---|
| `public.html` | the 6 registration/deregistration pages | the tenant's own stylesheet |
| `webinar/layout.html` (on `public.html`) | the 2 webinar pages | container, logo header, page title |
| `meeting/layout.html` (on `public.html`) | the 2 meeting pages | container, logo header, page title |
| `admin/layout.html` | the 6 admin pages | container, menu, content column, heading, error alert |

A page fills `{#content}` (`{#body}` on the participant pages, which *are* the body) and passes what the
Expand Down Expand Up @@ -239,7 +239,7 @@ in the admin area under *JUG Data*, to match the iframe-embedded pages to their
- It is supplied by **`TenantStyle`**, a `@Named("tenantStyle") @RequestScoped` bean like `CurrentUser`,
reachable as `inject:tenantStyle.present` / `inject:tenantStyle.css`. Not template data, because
`tenant` means two different things across the participant templates -- the bare id string in
`registration.html`/`thanks.html`, the `Tenant` entity in the webinar pages -- and `delete.html` gets
`registration.html`/`thanks.html`, the `Tenant` entity in the meeting pages -- and `delete.html` gets
no tenant at all. The bean loads the entity lazily during rendering; that works, the request context
and the Hibernate session are still around at that point.
- `getCss()` deliberately returns a `RawString`: the CSS lands unescaped inside a `<style>` element.
Expand Down Expand Up @@ -298,7 +298,7 @@ least the viewport height, which would keep the iframe from ever shrinking again
admin URL. Before, all of it was template data on every single resource method, where a forgotten
key surfaced as a 500 at render time. The participant pages still pass their own `tenant` data --
they render *for* a tenant instead of running inside one, and `tenant` means different things to
them (id string in `registration.html`/`thanks.html`, entity in the webinar pages).
them (id string in `registration.html`/`thanks.html`, entity in the meeting pages).
- Logout runs through `AdminLogoutResource` (`/admin/{tenant}/logout`), **not** through
`quarkus.oidc.logout.path`: the built-in logout offers a single static `post-logout-path`, while the
landing page has to carry the tenant. The resource assembles the RP-initiated logout request itself
Expand Down
32 changes: 16 additions & 16 deletions docs/handbuch.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ bestehende Seite eingebettet – typischerweise in die Detailseite einer Veranst
|offen
|Kalenderdatei (`.ics`) zur Veranstaltung

|`/webinar/{mandant}/{eventId}`
|`/meeting/{mandant}/{eventId}`
|offen
|Landingpage mit dem Einwahllink für Online-Events

Expand Down Expand Up @@ -366,7 +366,7 @@ Beschreibung aus einer JSON-Datei, die auf der Website der JUG liegt. Deren Adre
im Admin-Bereich unter <<_jug_data_stammdaten>>.

Diese Daten braucht die App für die Betreffzeilen und Texte der E-Mails, für die
Kalenderdatei, für die Webinar-Seite und für die Anzeige im Admin-Bereich.
Kalenderdatei, für die Meeting-Seite und für die Anzeige im Admin-Bereich.

[NOTE]
====
Expand Down Expand Up @@ -489,19 +489,19 @@ So wird sie eingerichtet:

. In der Events-Datei bei `location` den Wert `Online` (oder `Virtuell`) eintragen.
. Im Admin-Bereich auf der Detailseite des Events unter *Zusätzliche Event-Daten* den
Webinar-Link hinterlegen.
. Fertig – die Seite ist unter `/webinar/{mandant}/{eventId}` erreichbar und zeigt Titel,
Meeting-Link hinterlegen.
. Fertig – die Seite ist unter `/meeting/{mandant}/{eventId}` erreichbar und zeigt Titel,
Uhrzeit, den Einwahl-Button und den Hinweistext zu den eingesetzten Werkzeugen.

[IMPORTANT]
====
Die Webinar-Seite ist *nur am Tag der Veranstaltung* erreichbar. An allen anderen Tagen
Die Meeting-Seite ist *nur am Tag der Veranstaltung* erreichbar. An allen anderen Tagen
erscheint ein Hinweis, dass die Seite nicht verfügbar ist. Das ist Absicht – der Link kann
gefahrlos vorab verschickt werden.
====

Steht `location` auf `Online` oder `Virtuell`, trägt außerdem die Kalenderdatei nicht den
Ort, sondern die Adresse dieser Webinar-Seite als Ort und Link ein. Teilnehmer kommen dann
Ort, sondern die Adresse dieser Meeting-Seite als Ort und Link ein. Teilnehmer kommen dann
direkt aus ihrem Kalendereintrag zur Einwahl.

== Der Admin-Bereich
Expand All @@ -525,7 +525,7 @@ Arbeit:

* *Events / Registrierungen* – Anmeldungen ansehen und verwalten
* *JUG Data* – Stammdaten der JUG
* *Texte* – die Hilfetexte am Anmeldeformular und auf der Webinar-Seite
* *Texte* – die Hilfetexte am Anmeldeformular und auf der Meeting-Seite

Ganz unten in der Leiste steht der eigene Name. Das Klappmenü dahinter führt zum Logout und,
nur bei Konten mit der zusätzlichen Rolle `admin`, zu den beiden Betreiber-Seiten
Expand All @@ -548,7 +548,7 @@ der Zeit von selbst aus der Liste.
=== Teilnehmerliste eines Events

Oben stehen Datum, Titel und die Gesamtzahl der Anmeldungen; der Titel verlinkt auf die
Veranstaltungsseite. Ist ein Webinar-Link hinterlegt, erscheint zusätzlich der Button
Veranstaltungsseite. Ist ein Meeting-Link hinterlegt, erscheint zusätzlich der Button
*Zum Online-Meeting*.

Die Tabelle zeigt pro Anmeldung:
Expand Down Expand Up @@ -607,7 +607,7 @@ Teilnehmerlisten, Verlosungen oder den Einlass.

==== Zusätzliche Event-Daten

Hier wird der *Webinar-Link* für Online-Veranstaltungen hinterlegt und mit *Speichern*
Hier wird der *Meeting-Link* für Online-Veranstaltungen hinterlegt und mit *Speichern*
gesichert. Eine Bestätigung meldet den Erfolg.

==== Schreibe E-Mail an Teilnehmer
Expand Down Expand Up @@ -651,7 +651,7 @@ Ein Beispiel:
Hallo {{name}}!

der Link zur Einwahl für heute Abend:
{{baseUrl}}webinar/jugda/{{eventId}}
{{baseUrl}}meeting/jugda/{{eventId}}

Viele Grüße
Dein {{tenant.name}} Orga-Team
Expand Down Expand Up @@ -721,7 +721,7 @@ h3 { color: #005f73; }
Ein paar Punkte dazu:

* Das Stylesheet wirkt *nur auf die Teilnehmer-Seiten*: Anmeldung, Danke-Seite, Abmeldung,
„Anmeldung geschlossen", „Anmeldung noch nicht offen" und die Webinar-Seite. Der Admin-Bereich
„Anmeldung geschlossen", „Anmeldung noch nicht offen" und die Meeting-Seite. Der Admin-Bereich
bleibt bewusst unberührt – ein missglücktes Stylesheet soll nicht ausgerechnet das Formular
unbedienbar machen, in dem man es wieder repariert. Zum Ausprobieren also immer die
Anmeldeseite selbst aufrufen.
Expand All @@ -740,7 +740,7 @@ Stylesheet. Bei größeren Anpassungen lohnt sich eine Kopie außerhalb der Anwe

=== Texte (Hilfetexte)

Hier stehen die erklärenden Texte, die auf dem Anmeldeformular und auf der Webinar-Seite
Hier stehen die erklärenden Texte, die auf dem Anmeldeformular und auf der Meeting-Seite
erscheinen – dieselben Schlüssel wie unter <<_hilfetexte_anpassen>>. Jedes Feld zeigt seinen
Schlüssel und einen Hinweis, wo der Text auftaucht.

Expand Down Expand Up @@ -848,8 +848,8 @@ anpassbar:
|`registration.waitlist`
|Text im Wartelisten-Modus

|`webinar.tools`
|Hinweis zu den eingesetzten Konferenzwerkzeugen auf der Webinar-Seite
|`meeting.tools`
|Hinweis zu den eingesetzten Konferenzwerkzeugen auf der Meeting-Seite
|===

Bearbeitet werden diese Texte im Admin-Bereich unter *Texte*, siehe
Expand Down Expand Up @@ -882,8 +882,8 @@ mit dem Datum überein, siehe <<_die_events_datei>>.
|Im Admin-Bereich erscheint „No access to tenant"
|Dem Konto fehlt im Keycloak die Rolle mit dem Namen der Mandanten-ID.

|Die Webinar-Seite meldet „nicht verfügbar"
|Sie ist nur am Tag der Veranstaltung erreichbar. Andernfalls fehlt der Webinar-Link oder
|Die Meeting-Seite meldet „nicht verfügbar"
|Sie ist nur am Tag der Veranstaltung erreichbar. Andernfalls fehlt der Meeting-Link oder
die Veranstaltung steht nicht in der Events-Datei.

|Das eingebettete Formular hat eine eigene Scrollleiste
Expand Down
8 changes: 4 additions & 4 deletions misc/registration-requests.http
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ Content-Type: application/x-www-form-urlencoded

eventId={{eventId}}&name=Gerd&email=gerd@aschemann.net

### Upload additional webinar data to event <3>
### Upload additional meeting data to event <3>
PUT {{baseurl}}/admin/events/{{eventId}}/data
Content-Type: application/json

{"url" : "https://example.com/webinar", "title": "Titel"}
{"url" : "https://example.com/meeting", "title": "Titel"}

### Send e-mail message to participants <4>
PUT {{baseurl}}/admin/events/{{eventId}}/message
Expand All @@ -30,8 +30,8 @@ GET {{baseurl}}/admin/events/{{eventId}}
GET {{baseurl}}/admin/events/{{eventId}}
Accept: application/json

### Show Webinar data <8>
GET {{baseurl}}/webinar/{{eventId}}
### Show meeting data <8>
GET {{baseurl}}/meeting/{{eventId}}

### Delete my registration - You need to append the registration UUID received in #<2> <9>
DELETE {{baseurl}}/delete?id=3053214b-39fb-448a-8dbb-eb148c984a99
Expand Down
8 changes: 4 additions & 4 deletions misc/run-http-requests-manually.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ include::registration-requests.http[]
----
<1> Show the current number of registrations and the HTML form to register
<2> Register participant for an event
<3> Upload additional webinar data to event
<3> Upload additional meeting data to event
<4> Send e-mail message to participants
<5> Show overview of events
<6> Event administration UI (send mail, upload Webinar data)
<7> Optional: Event administration REST (send mail, upload Webinar data)
<8> Show Webinar data
<6> Event administration UI (send mail, upload meeting data)
<7> Optional: Event administration REST (send mail, upload meeting data)
<8> Show meeting data
<9> Delete registration by unique key (_Caution_: This request is incomplete, you need to add the unique id you received on registration)
<10> Delete registration of participant by eventId and e-mail address
<11> Delete registration by eventId and unique key (_Caution_: This request is incomplete, you need to add the unique id you received on registration)
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/de/jugda/registration/TenantStyle.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* <p>
* Named CDI bean rather than per-page template data on purpose: {@code tenant} means two different
* things in the participant templates -- the bare id string in {@code registration.html} and the
* {@link Tenant} entity in the webinar pages -- so there is no one expression every page could use.
* {@link Tenant} entity in the meeting pages -- so there is no one expression every page could use.
* The bean also reaches the pages that pass no tenant at all ({@code delete.html}).
* <p>
* The CSS is rendered unescaped inside a {@code <style>} element, hence {@link RawString}. The one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public Response getICalEntry(@PathParam("eventId") String eventId) {
vEvent.setUrl(event.getUrl());

if (event.getLocation().equalsIgnoreCase("online") || event.getLocation().equalsIgnoreCase("virtuell")) {
String link = String.format("%swebinar/%s/%s", uriInfo.getBaseUri(), tenantCtx.getTenantId(), eventId);
String link = String.format("%smeeting/%s/%s", uriInfo.getBaseUri(), tenantCtx.getTenantId(), eventId);
vEvent.setLocation(link);
vEvent.setUrl(link);
}
Expand Down
70 changes: 70 additions & 0 deletions src/main/java/de/jugda/registration/api/MeetingResource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package de.jugda.registration.api;

import de.jugda.registration.TenantContext;
import de.jugda.registration.domain.Content;
import de.jugda.registration.service.EventService;
import io.quarkus.qute.Location;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.runtime.LaunchMode;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Map;

@Path("meeting/{tenant}")
@Produces(MediaType.TEXT_HTML)
public class MeetingResource {

/** Key of the per-event meeting link in {@code event_data}, written by the admin event page. */
static final String MEETING_LINK = "meetingLink";

@Inject
EventService eventService;
@Inject
LaunchMode launchMode;
@Location("meeting/meeting")
Template meeting;
@Location("meeting/notAvailable")
Template meetingNotAvailable;

@Inject
TenantContext tenantCtx;

@GET
@Path("{eventId}")
public TemplateInstance getMeeting(@PathParam("eventId") String eventId) {
String today = LocalDate.now().format(DateTimeFormatter.ISO_DATE);
if (!launchMode.isDevOrTest() && !eventId.equals(today)) {
return notAvailable();
}

Map<String, String> eventData = eventService.getEventData(eventId);
// The link is the whole page. Until the orga team pastes it in -- which happens on the day,
// sometimes minutes before the start -- there is nothing to show, and the handbuch has always
// promised "nicht verfügbar" for that. The page used to answer 500 instead, because Qute
// renders strictly and {eventData.meetingLink} has no key to resolve.
String meetingLink = eventData.get(MEETING_LINK);
if (meetingLink == null || meetingLink.isBlank()) {
return notAvailable();
}

return eventService.getEvent(eventId)
.map(event -> meeting.data("event", event)
.data("tenant", tenantCtx.getTenant())
.data("eventData", eventData)
.data("helptext", Content.asMap()))
.orElseGet(this::notAvailable);
}

private TemplateInstance notAvailable() {
return meetingNotAvailable.data("tenant", tenantCtx.getTenant());
}

}
56 changes: 0 additions & 56 deletions src/main/java/de/jugda/registration/api/WebinarResource.java

This file was deleted.

4 changes: 2 additions & 2 deletions src/main/java/de/jugda/registration/domain/ContentKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public enum ContentKey {
"Text über der Einwilligungs-Checkbox am Ende des Anmeldeformulars."),
REGISTRATION_WAITLIST("registration.waitlist", "Hinweis: Warteliste",
"Erscheint statt der normalen Einleitung, sobald das Event ausgebucht ist."),
WEBINAR_TOOLS("webinar.tools", "Hinweis: Konferenzwerkzeuge",
"Steht auf der Webinar-Seite unter dem Meeting-Link.");
MEETING_TOOLS("meeting.tools", "Hinweis: Konferenzwerkzeuge",
"Steht auf der Meeting-Seite unter dem Meeting-Link.");

private final String key;
private final String label;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- The webinar page became the meeting page (see MeetingResource). The two stored keys carry the old
-- name, so they are renamed here rather than read under both names in code.
--
-- Deliberately not edited into V4/V8: Flyway checksums migrations that already ran, and changing one
-- makes a running instance refuse to start.

update public.event_data set key = 'meetingLink' where key = 'webinarLink';

update public.content set key = 'meeting.tools' where key = 'webinar.tools';
2 changes: 1 addition & 1 deletion src/main/resources/templates/admin/content.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{#include admin/layout.html nav="content" heading="Texte"}
{#content}
<p class="text-muted">
Die erklärenden Texte auf dem Anmeldeformular und der Webinar-Seite. Änderungen
Die erklärenden Texte auf dem Anmeldeformular und der Meeting-Seite. Änderungen
wirken sofort, eine Historie gibt es nicht.
</p>
<form method="post" action="/admin/{inject:tenant.id}/content">
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/admin/data.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<textarea class="form-control font-monospace" id="css" name="css" rows="12"
spellcheck="false">{form.css ?: ''}</textarea>
<small class="form-text text-muted">
Optional. Wird nur auf den Teilnehmer-Seiten (Anmeldung, Abmeldung, Webinar)
Optional. Wird nur auf den Teilnehmer-Seiten (Anmeldung, Abmeldung, Meeting)
eingebunden, nicht hier im Admin-Bereich, und steht dort hinter Bootstrap &ndash;
eigene Regeln gewinnen also. Gedacht, um die per <code>&lt;iframe&gt;</code>
eingebundenen Seiten an die eigene Website anzupassen, z.B.
Expand Down
Loading
Loading