From 55a2eaaeb4b4e3a18b2147b2c6b06773ef2b4828 Mon Sep 17 00:00:00 2001 From: jzitnik-dev Date: Sat, 20 Dec 2025 18:55:49 +0100 Subject: [PATCH] docs: Include website --- .gitignore | 7 + .gitmodules | 3 + Dockerfile | 3 + package.json | 4 +- scheduleRules.js | 1 + server.js | 26 +- web/content/posts/api-usage/index.md | 103 ++++++++ web/content/posts/api-usage/v1.md | 235 ++++++++++++++++++ .../posts/scraper-documentation/index.md | 14 ++ web/content/search.md | 5 + web/hugo.yaml | 72 ++++++ web/layouts/partials/extend_head.html | 1 + web/themes/PaperMod | 1 + 13 files changed, 465 insertions(+), 10 deletions(-) create mode 100644 .gitmodules create mode 100644 web/content/posts/api-usage/index.md create mode 100644 web/content/posts/api-usage/v1.md create mode 100644 web/content/posts/scraper-documentation/index.md create mode 100644 web/content/search.md create mode 100644 web/hugo.yaml create mode 100644 web/layouts/partials/extend_head.html create mode 160000 web/themes/PaperMod diff --git a/.gitignore b/.gitignore index 462657e..4470b4a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,10 @@ volume/browser db downloads errors + +# Web +web/public +web/resources/_gen/ +web/assets/jsconfig.json +web/hugo_stats.json +web/.hugo_build.lock diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7d7547b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "web/themes/PaperMod"] + path = web/themes/PaperMod + url = https://github.com/adityatelange/hugo-PaperMod.git diff --git a/Dockerfile b/Dockerfile index f38a540..543dee9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,9 @@ COPY package*.json ./ # Install dependencies RUN npm install +# Build +RUN npm run build + # Copy app source code COPY . . diff --git a/package.json b/package.json index 01ce6c3..88c81bf 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "type": "module", "main": "server.js", "scripts": { - "start": "concurrently \"node server.js\" \"node cron-runner.js\"" + "start": "concurrently \"node server.js\" \"node cron-runner.js\"", + "build": "cd web && hugo --gc --minify", + "dev-web": "cd web && hugo serve" }, "dependencies": { "body-parser": "^2.2.0", diff --git a/scheduleRules.js b/scheduleRules.js index eb247f0..c5c6c0f 100644 --- a/scheduleRules.js +++ b/scheduleRules.js @@ -1,4 +1,5 @@ // Rules: start and end in 24h format, interval in minutes + export const scheduleRules = [ { start: "0:00", end: "3:00", interval: 180 }, { start: "3:00", end: "4:00", interval: 60 }, diff --git a/server.js b/server.js index 15b5580..5b00764 100644 --- a/server.js +++ b/server.js @@ -11,13 +11,20 @@ globalThis.File = class File {}; app.use(bodyParser.json()); -app.get('/', async (_, res) => { - const dataStr = await fs.readFile(path.join(process.cwd(), "db", "current.json"), "utf8"); - const data = JSON.parse(dataStr); +app.get('/', async (req, res) => { + const userAgent = req.headers['user-agent'] || ''; + const isBrowser = /Mozilla|Chrome|Firefox|Safari|Edg/.test(userAgent); - data["status"]["currentUpdateSchedule"] = getCurrentInterval(); + if (isBrowser) { + res.sendFile(path.join(process.cwd(), "web", "public", "index.html")); + } else { + const dataStr = await fs.readFile(path.join(process.cwd(), "db", "current.json"), "utf8"); + const data = JSON.parse(dataStr); - res.json(data); + data["status"]["currentUpdateSchedule"] = getCurrentInterval(); + + res.json(data); + } }); app.get('/versioned/v1', async (_, res) => { @@ -49,13 +56,9 @@ app.post("/report", async (req, res) => { return res.status(400).json({ error: "Invalid location value." }); } - const url = `https://n8n.local.jzitnik.dev/webhook/${process.env.WEBHOOK_UUID}`; - console.log(url) - let resp; - try { resp = await fetch(url, { method: "POST", @@ -77,6 +80,11 @@ app.post("/report", async (req, res) => { res.status(200).json({ message: "Report received successfully." }); }); +app.use(express.static(path.join(process.cwd(), 'web/public'), { + index: 'index.html', + extensions: ['html'], +})); + app.listen(PORT, () => { console.log(`Server is running at http://localhost:${PORT}`); }); diff --git a/web/content/posts/api-usage/index.md b/web/content/posts/api-usage/index.md new file mode 100644 index 0000000..fd27863 --- /dev/null +++ b/web/content/posts/api-usage/index.md @@ -0,0 +1,103 @@ +--- +title: "API Dokumentace Ječná Rozvrh" +date: 2025-12-20 +tags: ["api", "docs"] +--- + +Vítejte v dokumentaci pro API systému Ječná Rozvrh. Toto API poskytuje programový přístup k rozvrhům, suplování a dalším informacím. + +## Základní Informace + +### URL +Všechny cesty v této dokumentaci jsou relativní k následující základní URL: +`https://jecnarozvrh.jzitnik.dev/` + +### Zastaralý Endpoint: `GET /` + +Kořenový endpoint (`/`) je **zastaralý (deprecated)**. + +Ačkoliv v současnosti vrací stejná data jako `/versioned/v1`, jeho podpora může být v budoucnu ukončena. **Prosím, nepoužívejte tento endpoint pro nové projekty a existující projekty aktualizujte na verziovaný endpoint.** + +--- + +## Dostupné Verze API + +API je verziované, aby byla zajištěna zpětná kompatibilita. Zde je seznam dostupných verzí: + +- ### [Verze 1 (v1)](v1) + **Status:** Stabilní + **Endpoint:** `/versioned/v1` + + Toto je aktuální a doporučená verze API. Klikněte na odkaz pro zobrazení kompletní dokumentace pro v1. + +--- + +## Nezařazené Endpointy + +Některé jednodušší endpointy nejsou verziované. + +### Stavový Endpoint: `GET /status` + +Tento endpoint slouží k rychlé kontrole stavu scraperu, který je zodpovědný za stahování a zpracování dat. + +
+Zobrazit možné odpovědi + +- **Scraper je v pořádku:** + ```json + { + "working": true + } + ``` + +- **Scraper narazil na problém:** + ```json + { + "working": false, + "message": "Zde bude podrobnější popis chyby, např. 'Failed to log in'." + } + ``` +
+ +### Report Endpoint: `POST /report` + +Tento endpoint slouží k nahlášení chyby nebo nesrovnalosti v datech. + +**Tělo požadavku (Request Body):** + +Požadavek musí obsahovat JSON objekt s následujícími poli: + +- `class` (string, povinné): Název třídy, které se hlášení týká. +- `location` (string, povinné): Místo, kde se chyba vyskytla. Povolené hodnoty jsou: + - `"TIMETABLE"` + - `"ABSENCES"` + - `"OTHER"` +- `content` (string, povinné): Popis problému. + +
+Zobrazit příklad těla požadavku + +```json +{ + "class": "C4a", + "location": "TIMETABLE", + "content": "V páté hodině chybí předmět, ale má tam být suplování." +} +``` +
+ +**Odpovědi (Responses):** + +- **`200 OK`**: Hlášení bylo úspěšně přijato. + ```json + { + "message": "Report received successfully." + } + ``` +- **`400 Bad Request`**: Pokud chybí povinná pole nebo hodnota `location` je neplatná. + ```json + { + "error": "Missing required fields." + } + ``` + diff --git a/web/content/posts/api-usage/v1.md b/web/content/posts/api-usage/v1.md new file mode 100644 index 0000000..262b9e2 --- /dev/null +++ b/web/content/posts/api-usage/v1.md @@ -0,0 +1,235 @@ +--- +title: "API Dokumentace - Verze 1" +date: 2025-12-20 +tags: ["api", "docs", "v1"] +hiddenInHomelist: true +--- + +Tato stránka detailně popisuje **Verzi 1 (v1)** API Ječná Rozvrh. + +## Endpoint: `GET /versioned/v1` + +Toto je hlavní endpoint, který poskytuje veškerá data o rozvrhu pro v1. + +### Struktura Odpovědi + +Odpověď je JSON objekt, který obsahuje tři hlavní klíče: `schedule`, `props`, a `status`. + +
+Zobrazit příklad struktury odpovědi + +```json +{ + "schedule": [ /* pole denních rozvrhů */ ], + "props": [ /* pole vlastností dnů */ ], + "status": { /* objekt stavu */ } +} +``` +
+ +--- + +### Datové Struktury + +#### Sekce: `schedule` + +Tato sekce je pole, kde každý prvek představuje jeden den. Každý den je objekt, jehož klíče jsou názvy jednotlivých tříd (např. `A1`, `C2a`, `E4`) a speciální klíč `ABSENCE`. + +##### Rozvrh Třídy +- **Klíč:** Název třídy (např. `"A1"`) +- **Hodnota:** Pole s 10 prvky, které reprezentují 10 vyučovacích hodin. + - `string`: Pokud je hodina normálně vyučována, obsahuje název předmětu nebo informaci o změně. + - `null`: Pokud hodina odpadá nebo pro ni není záznam. + - Text `(bude upřesněno)` může být připojen k předmětu, pokud je změna nejistá. + +
+Zobrazit příklad rozvrhu pro třídu A1 + +```json +"A1": [ + "M 5 Kp(Ng)", + null, + null, + "(Me) (bude upřesněno)", + null, + null, + null, + null, + null, + null +] +``` +
+ +##### Absence Učitelů +- **Klíč:** `"ABSENCE"` +- **Hodnota:** Pole objektů, kde každý objekt specifikuje jednu absenci. Struktura objektu je následující: + - `teacher` (string | null): Celé jméno učitele, pokud je známé. + - `teacherCode` (string | null): Zkratka jména učitele (např. "me", "ad"). + - `type` (string): Typ absence. Může nabývat následujících hodnot: + - `"wholeDay"`: Učitel chybí celý den. + - `"single"`: Učitel chybí jednu vyučovací hodinu. + - `"range"`: Učitel chybí v rozmezí několika hodin. + - `"exkurze"`: Učitel je na exkurzi. + - `"invalid"`: Záznam o absenci se nepodařilo zpracovat. + - `hours` (object | number | null): Specifikuje hodiny absence. + - `null`: Pro typy `wholeDay`, `exkurze`, a `invalid`. + - `number` (např. `3`): Pro typ `single`. + - `object` (např. `{ "from": 2, "to": 4 }`): Pro typ `range`. + - `original` (string | null): Pouze pro typ `invalid`, obsahuje původní nezpracovaný text. + +
+Zobrazit příklady absencí + +**Celý den:** +```json +{ + "teacher": "Jan Novák", + "teacherCode": "no", + "type": "wholeDay", + "hours": null +} +``` + +**Jedna hodina:** +```json +{ + "teacher": "Jan Novák", + "teacherCode": "no", + "type": "single", + "hours": 1 +} +``` + +**Rozsah hodin:** +```json +{ + "teacher": "Jan Novák", + "teacherCode": "no", + "type": "range", + "hours": { "from": 2, "to": 4 } +} +``` + +**Exkurze:** +```json +{ + "teacher": "Jan Novák", + "teacherCode": "no", + "type": "exkurze", + "hours": null +} +``` + +**Neplatný záznam:** +```json +{ + "type": "invalid", + "teacher": null, + "teacherCode": null, + "hours": null, + "original": "Nezpracovatelný text" +} +``` +
+ +#### Sekce: `props` - Vlastnosti Dnů + +Pole objektů, které doplňují metadata ke každému dni v poli `schedule`. Pořadí prvků v `props` přesně odpovídá pořadí dnů v `schedule`. + +- `date` (string): Datum daného rozvrhu ve formátu `YYYY-MM-DD`. +- `priprava` (boolean): Hodnota je `true`, pokud je den součástí "přípravného týdne", jinak `false`. + +
+Zobrazit příklad props + +```json +"props": [ + { + "date": "2025-12-20", + "priprava": false + }, + { + "date": "2025-12-21", + "priprava": true + } +] +``` +
+ +#### Sekce: `status` - Stav a Metadata + +Objekt poskytující informace o aktuálnosti dat. + +- `lastUpdated` (string): Čas poslední úspěšné aktualizace dat ve formátu `HH:MM`. +- `currentUpdateSchedule` (number): Interval v **minutách**, ve kterém scraper interně kontroluje a stahuje novou verzi rozvrhu. Tento interval se dynamicky mění v závislosti na denní době (kratší během vyučování, delší v noci). + +
+Zobrazit příklad status + +```json +"status": { + "lastUpdated": "08:30", + "currentUpdateSchedule": 5 +} +``` +
+ +--- + +### Kompletní Příklad Odpovědi z `GET /versioned/v1` + +
+Zobrazit kompletní příklad + +```json +{ + "schedule": [ + { + "A1": [ + "M 6 Kp(Ng)", + null, + null, + null, + "(Me) (bude upřesněno)", + null, + null, + null, + null, + null + ], + "C2": [ + "M 6 Kp(Ng)", + null, + null, + null, + "(Me) (bude upřesněno)", + null, + null, + null, + null, + null + ], + "ABSENCE": [ + { + "teacher": "Jan Novák", + "teacherCode": "no", + "type": "range", + "hours": {"from": 1, "to": 3} + } + ] + } + ], + "props": [ + { + "date": "2025-12-20", + "priprava": false + } + ], + "status": { + "lastUpdated": "14:30", + "currentUpdateSchedule": 5 + } +} +``` +
diff --git a/web/content/posts/scraper-documentation/index.md b/web/content/posts/scraper-documentation/index.md new file mode 100644 index 0000000..ea1a6d8 --- /dev/null +++ b/web/content/posts/scraper-documentation/index.md @@ -0,0 +1,14 @@ +--- +title: "Jak Funguje Scraper" +date: 2025-12-20 +tags: ["scraper", "backend"] +--- + +Celý proces je automatizovaný a běží v pravidelných intervalech, které se mění v závislosti na denní době. + +1. **Stažení Souboru:** Scraper se pomocí automatizovaného prohlížeče přihlásí na SharePoint SPŠE Ječná, kde je uložen oficiální Excel soubor s mimořádným rozvrhem. Po přihlášení tento soubor stáhne. + +2. **Parsování Dat:** Po stažení skript otevře Excel soubor a začne z něj "číst" data. Prochází jednotlivé řádky a sloupce, aby identifikoval rozvrhy pro jednotlivé třídy a informace o absencích učitelů. + +3. **Generování JSONu:** Všechna přečtená a zpracovaná data jsou následně uložena do jednoho souboru ve formátu JSON. +4. **Poskytnutí přes API:** Tento JSON soubor je finálním zdrojem dat, který API server používá. diff --git a/web/content/search.md b/web/content/search.md new file mode 100644 index 0000000..6a40149 --- /dev/null +++ b/web/content/search.md @@ -0,0 +1,5 @@ +--- +title: "Vyhledávání" +placeholder: Vyhledávejte v dokumentaci... +layout: "search" +--- diff --git a/web/hugo.yaml b/web/hugo.yaml new file mode 100644 index 0000000..a1300ad --- /dev/null +++ b/web/hugo.yaml @@ -0,0 +1,72 @@ +baseURL: https://jecnarozvrh.jzitnik.dev +title: Ječná Rozvrh API +theme: ["PaperMod"] + +enableInlineShortcodes: true +enableRobotsTXT: true +buildDrafts: false +buildFuture: false +buildExpired: false +enableEmoji: true +pygmentsUseClasses: true +mainsections: ["posts", "papermod"] + +minify: + disableXML: true + # minifyOutput: true + +pagination: + disableAliases: false + pagerSize: 5 + + +menu: + main: + - name: Vyhledávání + url: search/ + weight: 10 + - name: Autor + url: https://jzitnik.dev + +outputs: + home: + - HTML + - RSS + - JSON + +params: + env: production + author: Jakub Žitník + + defaultTheme: auto + ShowShareButtons: false + ShowReadingTime: true + displayFullLangName: true + ShowPostNavLinks: true + ShowBreadCrumbs: true + ShowCodeCopyButtons: true + ShowRssButtonInSectionTermList: true + ShowAllPagesInArchive: true + ShowPageNums: true + ShowToc: true + + homeInfoParams: + Title: "Ječná Rozvrh API" + Content: > + Oficiální webová stránka SPŠE Ječná Rozvrh API. + + - Ječná Rozvrh API je API pro získávání mimořádného rozvrhu z SPŠE Ječná tabulky. + + +markup: + goldmark: + renderer: + unsafe: true + highlight: + noClasses: false + +services: + instagram: + disableInlineCSS: true + x: + disableInlineCSS: true diff --git a/web/layouts/partials/extend_head.html b/web/layouts/partials/extend_head.html new file mode 100644 index 0000000..a260963 --- /dev/null +++ b/web/layouts/partials/extend_head.html @@ -0,0 +1 @@ + diff --git a/web/themes/PaperMod b/web/themes/PaperMod new file mode 160000 index 0000000..7d061d5 --- /dev/null +++ b/web/themes/PaperMod @@ -0,0 +1 @@ +Subproject commit 7d061d56d4664bd9c8241eb904994c98b928f0c8