1
0

docs: Include website
All checks were successful
Remote Deploy / deploy (push) Successful in 6s

This commit is contained in:
2025-12-20 18:55:49 +01:00
parent e4eadc2b1a
commit 55a2eaaeb4
13 changed files with 465 additions and 10 deletions

7
.gitignore vendored
View File

@@ -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

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "web/themes/PaperMod"]
path = web/themes/PaperMod
url = https://github.com/adityatelange/hugo-PaperMod.git

View File

@@ -10,6 +10,9 @@ COPY package*.json ./
# Install dependencies
RUN npm install
# Build
RUN npm run build
# Copy app source code
COPY . .

View File

@@ -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",

View File

@@ -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 },

View File

@@ -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}`);
});

View File

@@ -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.
<details>
<summary>Zobrazit možné odpovědi</summary>
- **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'."
}
```
</details>
### 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.
<details>
<summary>Zobrazit příklad těla požadavku</summary>
```json
{
"class": "C4a",
"location": "TIMETABLE",
"content": "V páté hodině chybí předmět, ale má tam být suplování."
}
```
</details>
**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."
}
```
</details>

View File

@@ -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`.
<details>
<summary>Zobrazit příklad struktury odpovědi</summary>
```json
{
"schedule": [ /* pole denních rozvrhů */ ],
"props": [ /* pole vlastností dnů */ ],
"status": { /* objekt stavu */ }
}
```
</details>
---
### 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á.
<details>
<summary>Zobrazit příklad rozvrhu pro třídu A1</summary>
```json
"A1": [
"M 5 Kp(Ng)",
null,
null,
"(Me) (bude upřesněno)",
null,
null,
null,
null,
null,
null
]
```
</details>
##### 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.
<details>
<summary>Zobrazit příklady absencí</summary>
**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"
}
```
</details>
#### 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`.
<details>
<summary>Zobrazit příklad props</summary>
```json
"props": [
{
"date": "2025-12-20",
"priprava": false
},
{
"date": "2025-12-21",
"priprava": true
}
]
```
</details>
#### 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).
<details>
<summary>Zobrazit příklad status</summary>
```json
"status": {
"lastUpdated": "08:30",
"currentUpdateSchedule": 5
}
```
</details>
---
### Kompletní Příklad Odpovědi z `GET /versioned/v1`
<details>
<summary>Zobrazit kompletní příklad</summary>
```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
}
}
```
</details>

View File

@@ -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á.

5
web/content/search.md Normal file
View File

@@ -0,0 +1,5 @@
---
title: "Vyhledávání"
placeholder: Vyhledávejte v dokumentaci...
layout: "search"
---

72
web/hugo.yaml Normal file
View File

@@ -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

View File

@@ -0,0 +1 @@
<script type="module" src="https://cdn.jsdelivr.net/npm/@hotwired/turbo@latest/dist/turbo.es2017-esm.min.js"></script>

1
web/themes/PaperMod Submodule

Submodule web/themes/PaperMod added at 7d061d56d4