{"id":31960064,"url":"https://github.com/zzackllack/school-dashboard","last_synced_at":"2026-07-19T04:36:13.114Z","repository":{"id":317444242,"uuid":"951526676","full_name":"Zzackllack/School-Dashboard","owner":"Zzackllack","description":"A modern, intuitive dashboard designed originally for Goethe Gymnasium Lichterfelde (GGL) to transform the lobby information display into a comprehensive school information hub.","archived":false,"fork":false,"pushed_at":"2025-09-30T21:52:23.000Z","size":2008,"stargazers_count":0,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-30T23:30:00.089Z","etag":null,"topics":["dashboard","school","school-project","website"],"latest_commit_sha":null,"homepage":"https://goethe-dashboard.zacklack.de/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Zzackllack.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-03-19T20:24:34.000Z","updated_at":"2025-09-30T21:52:26.000Z","dependencies_parsed_at":"2025-09-30T23:30:07.480Z","dependency_job_id":"67f51f1b-9d4b-4b1f-9b39-84a158d46a41","html_url":"https://github.com/Zzackllack/School-Dashboard","commit_stats":null,"previous_names":["zzackllack/school-dashboard"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/Zzackllack/School-Dashboard","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zzackllack%2FSchool-Dashboard","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zzackllack%2FSchool-Dashboard/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zzackllack%2FSchool-Dashboard/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zzackllack%2FSchool-Dashboard/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Zzackllack","download_url":"https://codeload.github.com/Zzackllack/School-Dashboard/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Zzackllack%2FSchool-Dashboard/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279019307,"owners_count":26086712,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-14T02:00:06.444Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dashboard","school","school-project","website"],"created_at":"2025-10-14T15:53:03.739Z","updated_at":"2026-07-19T04:36:13.107Z","avatar_url":"https://github.com/Zzackllack.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://shieldcn.dev/header/grid.svg?title=School+Dashboard\u0026amp;subtitle=GGL+lobby+information%2C+rebuilt+as+a+calm+real-time+school+dashboard.\u0026amp;logo=https%3A%2F%2Farchive.org%2Fdownload%2Fggl-logo-head-only%2FGoethe-Nur-Kopf.png\u0026amp;logoColor=c7ceda\u0026amp;mode=dark\" /\u003e\u003cimg alt=\"School Dashboard - GGL lobby information rebuilt as a calm real-time school dashboard\" src=\"https://shieldcn.dev/header/grid.svg?title=School+Dashboard\u0026amp;subtitle=GGL+lobby+information%2C+rebuilt+as+a+calm+real-time+school+dashboard.\u0026amp;logo=https%3A%2F%2Farchive.org%2Fdownload%2Fggl-logo-head-only%2FGoethe-Nur-Kopf.png\u0026amp;logoColor=c7ceda\u0026amp;mode=light\" /\u003e\u003c/picture\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://shieldcn.dev/github/ci/zzackllack/school-dashboard.svg?variant=secondary\" /\u003e\u003cimg alt=\"CI\" src=\"https://shieldcn.dev/github/ci/zzackllack/school-dashboard.svg?variant=secondary\u0026amp;mode=light\" /\u003e\u003c/picture\u003e\n  \u003cpicture\u003e\u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://shieldcn.dev/github/license/zzackllack/school-dashboard.svg?variant=secondary\" /\u003e\u003cimg alt=\"License\" src=\"https://shieldcn.dev/github/license/zzackllack/school-dashboard.svg?variant=secondary\u0026amp;mode=light\" /\u003e\u003c/picture\u003e\n  \u003cpicture\u003e\u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://shieldcn.dev/badge/Frontend-TanStack_Start-0f766e.svg?variant=secondary\u0026amp;logo=react\" /\u003e\u003cimg alt=\"Frontend - TanStack Start\" src=\"https://shieldcn.dev/badge/Frontend-TanStack_Start-0f766e.svg?variant=secondary\u0026amp;mode=light\u0026amp;logo=react\" /\u003e\u003c/picture\u003e\n  \u003cpicture\u003e\u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://shieldcn.dev/badge/Backend-Spring_Boot-6db33f.svg?variant=secondary\u0026amp;logo=springboot\" /\u003e\u003cimg alt=\"Backend - Spring Boot\" src=\"https://shieldcn.dev/badge/Backend-Spring_Boot-6db33f.svg?variant=secondary\u0026amp;mode=light\u0026amp;logo=springboot\" /\u003e\u003c/picture\u003e\n  \u003cpicture\u003e\u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://shieldcn.dev/badge/Package_mgr-pnpm-F69220.svg?variant=secondary\u0026amp;logo=pnpm\" /\u003e\u003cimg alt=\"Package manager - pnpm\" src=\"https://shieldcn.dev/badge/Package_mgr-pnpm-F69220.svg?variant=secondary\u0026amp;mode=light\u0026amp;logo=pnpm\" /\u003e\u003c/picture\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eA production dashboard for Goethe Gymnasium Lichterfelde that turns one passive lobby screen into a useful, readable school information hub.\u003c/strong\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#live-at-ggl\"\u003eLive at GGL\u003c/a\u003e ·\n  \u003ca href=\"#what-it-shows\"\u003eWhat it shows\u003c/a\u003e ·\n  \u003ca href=\"#project-state\"\u003eProject state\u003c/a\u003e ·\n  \u003ca href=\"#developer-notes\"\u003eDeveloper notes\u003c/a\u003e\n\u003c/p\u003e\n\n## Live at GGL\n\nThis dashboard is not just a MVP. It has been running on two locations at Goethe Gymnasium Lichterfelde, showing the substitution plan, weather, holidays, calendar events, and nearby public transport where students actually pass by.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\".github/images/live/loc-1.jpeg\" alt=\"School Dashboard running on a physical lobby display at Goethe Gymnasium Lichterfelde\" width=\"31%\" style=\"border-radius: 16px;\"\u003e\n  \u003cimg src=\".github/images/live/loc-1-1.jpeg\" alt=\"Close view of the production school dashboard on the lobby screen\" width=\"31%\" style=\"border-radius: 16px;\"\u003e\n  \u003cimg src=\".github/images/live/loc-2.jpeg\" alt=\"Side view of the installed dashboard display in the school building\" width=\"31%\" style=\"border-radius: 16px;\"\u003e\n\u003c/p\u003e\n\n## Why this exists\n\nThe old lobby display was hard to read, visually dated, and mostly limited to substitution information. School Dashboard was built to collect the information students and staff repeatedly need during the day into one calm, glanceable display: substitutions, weather, public transport, school events, holidays, and time.\n\nIt started as a GGL-specific project, but the structure is intentionally adaptable for other schools using DSBmobile and similar public data sources.\n\n## What it shows\n\n| Area | What students see | Data source / integration |\n| --- | --- | --- |\n| 📋 Substitution plan | Current class changes, including course-level changes for upper grades, with cached fallback data when upstream calls fail | DSBmobile via a Java integration |\n| 🌤️ Weather | Current conditions and forecast cards | Open-Meteo |\n| 🚌 Public transport | Nearby bus and train departures, route information, and delays | BVG / VBB transport API |\n| 📅 Calendar | Upcoming school events and important dates | iCal calendar feed |\n| 🏖️ Holidays | Berlin school holiday information | Senatsverwaltung für Bildung, Jugend und Familie Berlin |\n| 🕒 Clock | Large date and time display for the lobby screen | Browser/runtime time |\n\n## Technical shape\n\n| Layer | Stack |\n| --- | --- |\n| 🖥️ Frontend | React 19, TypeScript, TanStack Start, TanStack Router, TanStack Query, Tailwind CSS, Vite, Nitro |\n| ☕ Backend | Java 21, Spring Boot, REST endpoints, caching, Flyway migrations |\n| 🗄️ Data | H2 for development, PostgreSQL 17 for production |\n| 🧪 Testing | Vitest, Playwright, JUnit 5, Mockito |\n| 🚢 Deployment | Backend via Docker, frontend via Cloudflare Workers, currently hosted on private infrastructure |\n\n## Project state\n\nThis project is feature-complete for its original school use case. I have finished my Abitur and am leaving the school, so I do not plan to keep adding product features myself.\n\nThe repository may continue under the school's GitHub organization, and I will still take care of critical security updates, hosting-related maintenance, and serious operational issues where needed.\n\n| Phase | Status |\n| --- | --- |\n| Core API integration and first dashboard UI | ✅ Done |\n| Additional modules and visual refinement | ✅ Done |\n| Testing, performance, and deployment hardening | ✅ Done |\n| Documentation refresh | ✅ Done |\n| New feature development by original maintainer | 🛠️ Maintenance only |\n\n## Screenshots\n\n\u003cdetails\u003e\n\u003csummary\u003eOpen application screenshots (Legacy)\u003c/summary\u003e\n\n\u003cbr\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\".github/images/screenshot/dashboard-preview.png\" alt=\"School Dashboard overview screenshot\" width=\"100%\" style=\"border-radius: 12px;\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\".github/images/screenshot/holidays.png\" alt=\"Holiday module screenshot\" width=\"24%\" style=\"border-radius: 12px;\"\u003e\n  \u003cimg src=\".github/images/screenshot/calendar.png\" alt=\"Calendar module screenshot\" width=\"24%\" style=\"border-radius: 12px;\"\u003e\n  \u003cimg src=\".github/images/screenshot/navigation.png\" alt=\"Transportation module screenshot\" width=\"24%\" style=\"border-radius: 12px;\"\u003e\n  \u003cimg src=\".github/images/screenshot/weather.png\" alt=\"Weather module screenshot\" width=\"24%\" style=\"border-radius: 12px;\"\u003e\n\u003c/p\u003e\n\n\u003c/details\u003e\n\n\n## Developer notes\n\n\u003cdetails\u003e\n\u003csummary\u003eQuick local setup\u003c/summary\u003e\n\n### Prerequisites\n\n- JDK 21\n- Node.js 24+ with pnpm enabled through `corepack enable`\n- Maven, only needed once if Maven Wrapper files are missing\n\n### Start locally\n\n```bash\ngit clone https://github.com/Zzacklack/school-dashboard.git\ncd school-dashboard\npnpm run setup\npnpm run dev\n```\n\n`pnpm run setup` performs the local credential and URL bootstrap and writes:\n\n- `Backend/.env`\n- `Frontend/.env`\n\nThese files are local-only and must never be committed.\n\nLocal URLs:\n\n- Frontend: \u003chttp://localhost:3000\u003e\n- Backend API: \u003chttp://localhost:8080\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eBackend and database notes\u003c/summary\u003e\n\nThe backend uses one committed Spring configuration file at `Backend/src/main/resources/application.properties`. Runtime values are provided through environment variables, with safe defaults where possible.\n\nCommon overrides:\n\n- `DSB_USERNAME` -\u003e `dsb.username`\n- `DSB_PASSWORD` -\u003e `dsb.password`\n- `CALENDAR_ICS_URL` -\u003e `calendar.ics-url`\n- `SPRING_DATASOURCE_URL` -\u003e `spring.datasource.url`\n- `SERVER_SERVLET_SESSION_COOKIE_SECURE` -\u003e `server.servlet.session.cookie.secure`\n\nSession and cookie notes:\n\n- `SECURITY_SESSION_IDLE_TIMEOUT` controls app-level idle invalidation.\n- `SERVER_SERVLET_SESSION_TIMEOUT` can set the container session timeout; the shorter timeout effectively applies.\n- If `SECURITY_CORS_ALLOWED_ORIGINS` includes `http://localhost`, set `SERVER_SERVLET_SESSION_COOKIE_SECURE=false` for local HTTP cookie-based auth.\n\nFlyway runs automatically on startup. Production PostgreSQL migrations live under `Backend/src/main/resources/db/migration/postgresql`; local H2 migrations live under `Backend/src/main/resources/db/migration/h2`.\n\nUseful commands:\n\n```bash\nmvn -f Backend/pom.xml spring-boot:run\nmvn -f Backend/pom.xml test\nmvn -f Backend/pom.xml clean package -DskipTests\n```\n\nThe local H2 database persists plan snapshots under `Backend/data/`.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eFrontend, checks, and CI\u003c/summary\u003e\n\nFrontend commands:\n\n```bash\npnpm install --frozen-lockfile\npnpm --dir Frontend run dev\npnpm --dir Frontend run build\npnpm --dir Frontend run lint\npnpm --dir Frontend run test:unit\npnpm --dir Frontend run test:integration\npnpm --dir Frontend run test:web\n```\n\nRepository-level helpers:\n\n```bash\npnpm run format:check\npnpm run format\npnpm run lint\npnpm run test\npnpm run build\n```\n\nCI covers formatting, tests, CodeQL, and Docker image publishing through GitHub Actions.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eBackend API endpoints\u003c/summary\u003e\n\nAll endpoints are served from the backend base URL, usually `http://localhost:8080`.\n\n| Endpoint | Purpose |\n| --- | --- |\n| `GET /health` | Lightweight health response with status and timestamp |\n| `GET /api/substitution/plans` | Substitution plan data with cached fallback on errors |\n| `GET /api/dsb/timetables` | Raw DSBmobile timetables list |\n| `GET /api/dsb/news` | DSBmobile news payload |\n| `GET /api/calendar/events?limit=5` | Parsed calendar events with epoch millis and `allDay` |\n| `GET /error` | HTML error page handler |\n\nOptional actuator endpoints depend on `management.endpoints.web.exposure.include`:\n\n- `GET /actuator/health`\n- `GET /actuator/info`\n- `GET /actuator/metrics`\n- `GET /actuator/env`\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eProduction deployment\u003c/summary\u003e\n\nBackend production deployments use Docker and PostgreSQL. Frontend deployment is configured for Cloudflare Workers.\n\nFrontend Workers configuration:\n\n- Wrangler config: `Frontend/wrangler.toml`\n- Production domain: `goethe-dashboard.zacklack.de`\n- Preview URLs are enabled through `preview_urls = true` and `workers_dev = true`\n\nCloudflare Workers build settings:\n\n- Root directory: repository root (`/`)\n- Build command: `pnpm --dir Frontend run build:workers`\n- Deploy command: `pnpm --dir Frontend run deploy:workers`\n- Non-production branch deploy command: `pnpm --dir Frontend run deploy:workers:preview`\n\nBackend environment essentials:\n\n```bash\nSPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/school_dashboard\nSPRING_DATASOURCE_USERNAME=school_dashboard\nSPRING_DATASOURCE_PASSWORD=\u003cstrong-password\u003e\nSPRING_FLYWAY_LOCATIONS=classpath:db/migration/postgresql\n```\n\nDocker deployment:\n\n```bash\ncd Docker\ndocker compose -f docker-compose.yaml build\ndocker compose -f docker-compose.yaml up -d\ndocker compose -f docker-compose.yaml ps\n```\n\nLegacy H2 backups can be imported into PostgreSQL:\n\n```bash\nPG_URL=\"postgresql://localhost:5432/school_dashboard\" \\\nPG_USER=\"school_dashboard\" \\\nPG_PASSWORD=\"\u003cstrong-password\u003e\" \\\nH2_PASSWORD=\"\" \\\n./scripts/prod/import-h2-legacy-to-postgres.bash Backend/data/backup/20260307-181933\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eThe DSBmobile integration story\u003c/summary\u003e\n\nDSBmobile was the hardest integration in the project. Early attempts with Python libraries ran into authentication problems, inconsistent payloads, and undocumented changes. The practical solution ended up being a much older Java implementation that handled the real service behavior more reliably than the newer options.\n\nThe frustrating part is not the data model itself; it is the lack of a stable public API for a system used by many schools. That opacity forces school-side projects into reverse-engineered integrations for data that should be straightforward to access.\n\n\u003c/details\u003e\n\n## Contributing\n\nContributions are welcome, especially from people continuing the project for GGL or adapting it for another school. Please read [CONTRIBUTING.md](CONTRIBUTING.md), keep secrets out of commits, and run the relevant checks before opening a pull request.\n\n## Acknowledgements\n\n- [DSBmobile-API](https://github.com/Sematre/DSBmobile-API) by [Sematre](https://github.com/Sematre/) for the Java implementation that made the core substitution-plan integration workable.\n- [BVG transport API](https://v6.bvg.transport.rest/) for Berlin public transport data.\n- [Open-Meteo](https://open-meteo.com/) for weather data.\n- [Weather-Sense/Icons](https://github.com/Leftium/weather-sense) by [Leftium](https://github.com/Leftium/) for weather icons.\n- [Saloking / Nikolas](https://github.com/nikolas-bott) for the decisive hint to use the Java DSBmobile API instead of losing more time to broken Python options.\n- Goethe Gymnasium Lichterfelde for the opportunity to improve the school's information display.\n\n## License\n\nSchool Dashboard is licensed under the BSD 3-Clause License. See [LICENSE](LICENSE) for details.\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/zzackllack/school-dashboard/graphs/contributors\"\u003e\u003cpicture\u003e\u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://shieldcn.dev/contributors/zzackllack/school-dashboard.svg?bots=true\u0026amp;mode=dark\" /\u003e\u003cimg alt=\"contributors\" src=\"https://shieldcn.dev/contributors/zzackllack/school-dashboard.svg?bots=true\u0026amp;mode=light\" /\u003e\u003c/picture\u003e\u003c/a\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzzackllack%2Fschool-dashboard","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzzackllack%2Fschool-dashboard","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzzackllack%2Fschool-dashboard/lists"}