An app to display events from a nextcloud calendar.
  • TypeScript 92.3%
  • JavaScript 3.6%
  • Dockerfile 2.7%
  • Makefile 1.3%
Find a file
Jan Felix Wiebe ade1b0f1b4
All checks were successful
deploy / build-and-push (release) Successful in 1m8s
CI / check (push) Successful in 2m24s
added api for new info-beamer package
2026-08-25 21:53:26 +02:00
.forgejo/workflows run checks in ci on every push 2026-08-17 01:53:32 +02:00
demo added a later demo entry 2026-08-25 21:52:44 +02:00
deploy/podman deploy/podman: use port 8095, 8094 is taken on web.server.c3h 2026-08-17 02:34:52 +02:00
public remove testData function 2024-06-30 14:27:08 +02:00
src added api for new info-beamer package 2026-08-25 21:53:26 +02:00
.dockerignore add .dockerignore, pin node 24 image, drop docker-compose usage docs 2026-08-17 01:41:05 +02:00
.env.example Add development setup documentation and example files 2025-12-23 12:39:33 +01:00
.gitignore updated ignorefiles 2026-08-17 02:21:22 +02:00
.nvmrc pin toolchain: pnpm 10, node >=20.9, .nvmrc 2026-08-17 01:47:25 +02:00
.prettierignore updated ignorefiles 2026-08-17 02:21:22 +02:00
AGENTS.md add agent guidance files to the branch 2026-08-18 16:48:18 +02:00
ARCHITECTURE.md add /no-zoom to disable the day-view zoom per viewer 2026-08-18 17:39:51 +02:00
CLAUDE.md add agent guidance files to the branch 2026-08-18 16:48:18 +02:00
config.demo.toml scale the whole day view with css zoom instead of font size 2026-08-18 16:41:05 +02:00
config.toml.example scale the whole day view with css zoom instead of font size 2026-08-18 16:41:05 +02:00
Dockerfile add .dockerignore, pin node 24 image, drop docker-compose usage docs 2026-08-17 01:41:05 +02:00
eslint.config.mjs work around next 16.3.1 standalone missing @swc/helpers/esm 2026-08-17 02:09:16 +02:00
LICENSE Create LICENSE 2024-07-04 23:46:08 +02:00
Makefile add Makefile as the task entry point 2026-08-17 01:44:37 +02:00
next.config.js work around next 16.3.1 standalone missing @swc/helpers/esm 2026-08-17 02:09:16 +02:00
package.json update node-ical to 0.27 and drop the rrule workarounds 2026-08-18 15:57:51 +02:00
pnpm-lock.yaml update node-ical to 0.27 and drop the rrule workarounds 2026-08-18 15:57:51 +02:00
postcss.config.cjs fix some bugs 2025-03-03 17:07:34 +01:00
prettier.config.js initial commit 2024-06-14 21:25:28 +02:00
README.md added api for new info-beamer package 2026-08-25 21:53:26 +02:00
tailwind.config.ts fix some bugs 2025-03-03 17:07:34 +01:00
tsconfig.json work around next 16.3.1 standalone missing @swc/helpers/esm 2026-08-17 02:09:16 +02:00
vitest.config.ts update node-ical to 0.27 and drop the rrule workarounds 2026-08-18 15:57:51 +02:00

Event Display

An app to display events from a nextcloud calendar in the entry hall of the Stadtteilzentrum Nordstadt e.V. in Hannover.

Development

Prerequisites

  • Node.js 20.9 or higher (24 LTS recommended, see .nvmrc)
  • pnpm 10 — run corepack enable to get the pinned version automatically (Node 25+ no longer bundles corepack; npm install -g pnpm works too)

Setup

  1. Clone the repository
  2. Copy the example config file and adjust it to your needs:
    cp config.toml.example config.toml
    
  3. Edit config.toml with your Nextcloud calendar URLs and credentials
  4. Install dependencies:
    pnpm install
    
  5. Start the development server:
    make dev
    

The app will be available at http://localhost:3000. The app looks for ./config.toml by default; set the CONFIG_PATH environment variable (e.g. in .env.local, see .env.example) to use a different file.

Demo mode

To try the app without a Nextcloud instance, run

make demo

It starts the dev server with config.demo.toml, which points at the checked-in calendars in demo/ via file:// URLs — no credentials needed.

Development Features

  • Hot reload: Changes to source files are automatically reflected
  • TypeScript support with type checking
  • ESLint for code quality
  • Prettier for code formatting

Useful Commands

make help lists all tasks. The most useful ones:

make dev      # start the development server
make check    # typecheck + lint + format check + tests (same as CI)
make test     # run the test suite
make format   # format the codebase with prettier
make image    # build the container image locally with podman

Deployment

A container image is published to the Forgejo package registry whenever a release is published: git.hannover.ccc.de/buergerschule/event-display:latest (also tagged with the release tag and the commit SHA). It is built by CI from the Dockerfile.

The production deployment — rootless Podman with systemd Quadlets, pulling this image via podman auto-update — is documented in deploy/podman/README.md.

Configuration

There is a config.toml to configure every important aspect of the app. The path for this file can be set using the CONFIG_PATH environment variable. If this variable is not set, the app will look for a config.toml in the current working directory.

Here is an example configuration:

[frontend]
# The title of the page displayed at the top of the page
title = "Veranstaltungen in der Bürgerschule"
# The url of the logo displayed in the top right corner
icon = "/logo.png"
# The theme color, used for everything colored in the app
themeColor = "#EC6500"
# the time (in seconds) for scrolling to the next page of events when pagination is happening because of a lot of events (optional, default: 10)
scrollInterval = 10
# Display scale factor for the day view, like an OS display-scaling setting: scales the whole
# UI (text, spacing, borders, logo) so one build can be tuned to the physical screen it runs
# on. Does not affect /wochenplan. (optional, default: 1, range: 0.5-3)
scale = 1

[calendar]
# The timezone of the calendar
timeZone = "Europe/Berlin"
# The interval (in seconds) to refresh loaded events from the calendar
refreshInterval = 300
# The calendars to load events from
# name: The name of the room the calendar is for (displayed below the title of the event)
# url: The url of the calendar (found as the "Internal URL" in the nextcloud calendar settings)
# location: The location of the room the calendar is for, displayed on the right of the event list (optional, default: "")
# hideName: If the name of the room should be hidden in the event list (below the title) (optional, default: false)
calendars = [
    { name = "Raum A", url = "https://cloud.example.de/remote.php/dav/calendars/kalendar/raum-a/", location = "2. OG", hideName = false },
    { name = "Raum B", url = "https://cloud.example.de/remote.php/dav/calendars/kalendar/raum-b/", location = "2. OG", hideName = false },
]
# events that contain these keywords in their iCal categories will be ignored and not shown in the event list
ignoreKeywords = ["!intern!", "!privat!"]
# keywords that will be removed from event titles and mark events as "open end" if found in iCal categories
openEndKeywords = ["offen", "openend"]
# time in minutes after which an event is considered to be in the past and will be removed from the event list (0 means events will not be removed)
timeout = 0
# the auth information for the nextcloud instance (app password), the user must have read access to the calendars specified above
# type: The type of authentication, currently only "basic" is supported (optional)
auth = { type = "basic", username = "kalendar", password = "abcdefg" }

API

GET /api/hours

Returns the start time of the first event and the end time of the last event of a given day as JSON. All-day events and private events (see ignoreKeywords) are excluded.

Query parameters:

  • date (optional): the day to query, in YYYY-MM-DD format. Defaults to the current day if omitted. The date is interpreted in the timezone configured under [calendar] timeZone.

Example:

curl "http://localhost:3000/api/hours?date=2026-06-10"

Example response:

{
  "date": "2026-06-10",
  "timeZone": "Europe/Berlin",
  "first": "2026-06-10T08:00:00+02:00",
  "last": "2026-06-10T18:00:00+02:00",
  "eventCount": 5
}
  • first / last: ISO 8601 timestamps of the earliest start and latest end, in the configured timezone (including its UTC offset). null if there are no matching events.
  • eventCount: the number of events considered.

A request with an invalid date returns HTTP 400; an error while fetching the calendar returns HTTP 502.

GET /api/events

Schedule feed for the info-beamer signage package (package-buergerschule). Returns the events of yesterday, today and the following days as raw data — private events are excluded, but unlike /, no timeout filter is applied (hiding ended events is the renderer's job) and no display strings for header or date are included.

Query parameters:

  • days (optional): number of lookahead days after today, integer 1..14, default 5. The response always contains 2 + days day entries (yesterday + today + lookahead); days without events carry an empty events array.

Example:

curl "http://localhost:3000/api/events?days=3"

Response shape, field semantics and the consuming sync service are documented in the package: package-buergerschule/docs/event-display-api.md. Invalid days returns HTTP 400; a calendar fetch error returns HTTP 502.

GET /no-zoom

Disables the day-view zoom ([frontend] scale) for the browser that opens it, then redirects to /. Useful when staff open the display page on a laptop or phone, where the scale factor chosen for the kiosk screen is far too large.

The opt-out is a session cookie: it applies to that one browser only, never to the kiosk itself, and disappears when the browser is closed. config.toml is not touched, so no restart is involved.

While it is active, / shows a red bar reading "Der Zoom ist für dieses Gerät deaktiviert." with a link back to /no-zoom?clear=1, which removes the cookie and restores the configured zoom. /wochenplan is unaffected either way — it is never zoomed.

License

This software is licensed under GNU Affero General Public License V3. For more details see LICENSE.

Financing

This software has been built from public money. For more info, why publicly funded software should be public code, visit https://publiccode.eu/, an initiative by Free Software Foundation Europe.