Compare commits
No commits in common. "main" and "feat/test" have entirely different histories.
|
|
@ -1,4 +0,0 @@
|
||||||
[*.{kt,kts}]
|
|
||||||
# Erlaubt Wildcard-Imports für das gesamte Projekt:
|
|
||||||
ij_kotlin_allow_trailing_comma = true
|
|
||||||
ktlint_standard_no-wildcard-imports = disabled
|
|
||||||
33
.github/workflows/ci.yml
vendored
33
.github/workflows/ci.yml
vendored
|
|
@ -1,33 +0,0 @@
|
||||||
name: CI Pipeline
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main", "feat/*" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint-and-test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Code auschecken
|
|
||||||
uses: https://github.com/actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Java 21 einrichten
|
|
||||||
uses: https://github.com/actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: 'temurin'
|
|
||||||
java-version: '21'
|
|
||||||
|
|
||||||
- name: Gradle Rechte vergeben
|
|
||||||
run: chmod +x gradlew
|
|
||||||
|
|
||||||
- name: 1. Code-Style (Linter) prüfen
|
|
||||||
run: ./gradlew ktlintCheck --no-daemon
|
|
||||||
|
|
||||||
- name: 2. Tests ausführen
|
|
||||||
run: ./gradlew test --no-daemon
|
|
||||||
|
|
||||||
- name: 3. Backend bauen
|
|
||||||
run: ./gradlew build -x test --max-workers=2 --no-daemon -Dorg.gradle.jvmargs="-Xmx1024m"
|
|
||||||
14
.gitignore
vendored
14
.gitignore
vendored
|
|
@ -1,14 +0,0 @@
|
||||||
# Gradle Build-Ordner
|
|
||||||
build/
|
|
||||||
.gradle/
|
|
||||||
|
|
||||||
# Build-Ordner von Submodulen (falls vorhanden)
|
|
||||||
*/build/
|
|
||||||
|
|
||||||
# IntelliJ / IDE Dateien
|
|
||||||
.idea/
|
|
||||||
*.iml
|
|
||||||
*.iws
|
|
||||||
*.ipr
|
|
||||||
out/
|
|
||||||
build/
|
|
||||||
BIN
.gradle/9.5.1/checksums/checksums.lock
Normal file
BIN
.gradle/9.5.1/checksums/checksums.lock
Normal file
Binary file not shown.
BIN
.gradle/9.5.1/checksums/md5-checksums.bin
Normal file
BIN
.gradle/9.5.1/checksums/md5-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/9.5.1/checksums/sha1-checksums.bin
Normal file
BIN
.gradle/9.5.1/checksums/sha1-checksums.bin
Normal file
Binary file not shown.
BIN
.gradle/9.5.1/executionHistory/executionHistory.bin
Normal file
BIN
.gradle/9.5.1/executionHistory/executionHistory.bin
Normal file
Binary file not shown.
BIN
.gradle/9.5.1/executionHistory/executionHistory.lock
Normal file
BIN
.gradle/9.5.1/executionHistory/executionHistory.lock
Normal file
Binary file not shown.
BIN
.gradle/9.5.1/expanded/expanded.lock
Normal file
BIN
.gradle/9.5.1/expanded/expanded.lock
Normal file
Binary file not shown.
BIN
.gradle/9.5.1/fileChanges/last-build.bin
Normal file
BIN
.gradle/9.5.1/fileChanges/last-build.bin
Normal file
Binary file not shown.
BIN
.gradle/9.5.1/fileHashes/fileHashes.bin
Normal file
BIN
.gradle/9.5.1/fileHashes/fileHashes.bin
Normal file
Binary file not shown.
BIN
.gradle/9.5.1/fileHashes/fileHashes.lock
Normal file
BIN
.gradle/9.5.1/fileHashes/fileHashes.lock
Normal file
Binary file not shown.
BIN
.gradle/9.5.1/fileHashes/resourceHashesCache.bin
Normal file
BIN
.gradle/9.5.1/fileHashes/resourceHashesCache.bin
Normal file
Binary file not shown.
0
.gradle/9.5.1/gc.properties
Normal file
0
.gradle/9.5.1/gc.properties
Normal file
BIN
.gradle/9.5.1/kotlin-dsl-plugin-entries/jar-entries.bin
Normal file
BIN
.gradle/9.5.1/kotlin-dsl-plugin-entries/jar-entries.bin
Normal file
Binary file not shown.
Binary file not shown.
BIN
.gradle/buildOutputCleanup/buildOutputCleanup.lock
Normal file
BIN
.gradle/buildOutputCleanup/buildOutputCleanup.lock
Normal file
Binary file not shown.
2
.gradle/buildOutputCleanup/cache.properties
Normal file
2
.gradle/buildOutputCleanup/cache.properties
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#Wed Sep 02 15:03:29 CEST 2026
|
||||||
|
gradle.version=9.5.1
|
||||||
BIN
.gradle/buildOutputCleanup/outputFiles.bin
Normal file
BIN
.gradle/buildOutputCleanup/outputFiles.bin
Normal file
Binary file not shown.
BIN
.gradle/file-system.probe
Normal file
BIN
.gradle/file-system.probe
Normal file
Binary file not shown.
0
.gradle/vcs-1/gc.properties
Normal file
0
.gradle/vcs-1/gc.properties
Normal file
10
.idea/.gitignore
vendored
Normal file
10
.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Ignored default folder with query files
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
6
.idea/compiler.xml
Normal file
6
.idea/compiler.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="21" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
12
.idea/dataSources.xml
Normal file
12
.idea/dataSources.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="kidio_db@82.165.11.162" uuid="c8ad70de-d5fc-407a-9165-4f24328fa311">
|
||||||
|
<driver-ref>postgresql</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:postgresql://82.165.11.162:5432/kidio_db</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
16
.idea/gradle.xml
Normal file
16
.idea/gradle.xml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/kotlinc.xml
Normal file
7
.idea/kotlinc.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="KotlinJpsPluginSettings">
|
||||||
|
<option name="externalSystemId" value="Gradle" />
|
||||||
|
<option name="version" value="2.4.0" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/misc.xml
Normal file
7
.idea/misc.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="homebrew-26" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
Kidio_backend.iml
Normal file
9
Kidio_backend.iml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
590
README.md
590
README.md
|
|
@ -1,585 +1,33 @@
|
||||||
# Kidio Backend
|
# Kidio_backend
|
||||||
|
|
||||||
Das **Kidio-Backend** ist eine gamifizierte Lösung zur Verwaltung von Bildschirmzeit für Kinder.
|
This project was created using the [Ktor Project Generator](https://start.ktor.io).
|
||||||
|
|
||||||
Die Anwendung basiert auf dem **Ktor-Framework in Kotlin** und kombiniert **PostgreSQL** für die persistente Datenhaltung mit **Redis** als Caching-Layer für schnelle Zugriffszeiten.
|
Here are some useful links to get you started:
|
||||||
|
|
||||||
Kinder können durch das Lösen von Lernaufgaben, beispielsweise Mathematikaufgaben, zusätzliche Bildschirmzeit erwerben. Gleichzeitig synchronisiert das System die verbrauchte Bildschirmzeit und sperrt das Gerät automatisch, sobald keine verfügbare Bildschirmzeit mehr vorhanden ist.
|
* [Ktor Documentation](https://ktor.io/docs/home.html)
|
||||||
|
* [Ktor GitHub page](https://github.com/ktorio/ktor)
|
||||||
---
|
* [Ktor Slack chat](https://app.slack.com/client/T09229ZC6/C0A974TJ9). [Request an invite](https://surveys.jetbrains.com/s3/kotlin-slack-sign-up).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
| Feature | Beschreibung |
|
Here's a list of features included in this project:
|
||||||
| :--------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| **JWT-Authentifizierung** | Sichere API-Endpunkte für authentifizierte Benutzer über JSON Web Tokens (JWT). |
|
|
||||||
| **Lernaufgaben (Tasks)** | Abrufen von interaktiven Lernaufgaben mit vordefinierten Antwortmöglichkeiten, Filterung nach Fach und Alter sowie Belohnungen in Minuten. |
|
|
||||||
| **Antwort-Verifizierung** | Überprüfung der vom Kind ausgewählten Antworten. Bei korrekter Antwort wird die Belohnung direkt auf die verbleibende Bildschirmzeit aufgerechnet. |
|
|
||||||
| **OER / H5P Ingestion Pipeline** | Automatisierter Live-Crawler und Batch-Import von freien Bildungsinhalten (ZUM.de / H5P) inklusive HTML-Bereinigung und Lizenzattributierung (CC-BY). |
|
|
||||||
| **Bildschirmzeit-Synchronisation** | Laufender Datenabgleich der verbrauchten Bildschirmzeit. Sinkt die verbleibende Zeit auf oder unter null, wird der Status automatisch auf `isBlocked = true` gesetzt. |
|
|
||||||
| **Zwei-Ebenen-Speicher** | PostgreSQL als persistente Datenbank, kombiniert mit Redis-Caching für schnelle Screentime-Abfragen. |
|
|
||||||
| **Fallback-Mechanismus** | Ist Redis nicht erreichbar, fällt das System automatisch und transparent auf PostgreSQL zurück. |
|
|
||||||
|
|
||||||
---
|
| Name | Description |
|
||||||
|
|------|-------------|
|
||||||
|
|
||||||
## Technologiestack
|
## Building & Running
|
||||||
|
|
||||||
* **Framework:** Ktor
|
To build or run the project, use one of the following tasks:
|
||||||
* **Programmiersprache:** Kotlin
|
|
||||||
* **Datenbank-ORM:** JetBrains Exposed
|
|
||||||
* **Persistenz:** PostgreSQL
|
|
||||||
* **Caching:** Redis via Jedis
|
|
||||||
* **Authentifizierung:** JWT via Java JWT (Auth0)
|
|
||||||
* **Serialisierung:** Kotlinx Serialization
|
|
||||||
* **Code Quality / Linter:** KtLint
|
|
||||||
* **Testing:** Ktor Server Testing, JUnit
|
|
||||||
* **CI/CD:** Forgejo Actions (Gitea / Act Runner)
|
|
||||||
|
|
||||||
---
|
| Task | Description |
|
||||||
|
|-------------------|-------------------|
|
||||||
|
| `./gradlew test` | Run the tests |
|
||||||
|
| `./gradlew build` | Build the project |
|
||||||
|
| `./gradlew run` | Run the server |
|
||||||
|
|
||||||
## API-Endpunkte
|
If the server starts successfully, you'll see the following output:
|
||||||
|
|
||||||
Alle API-Endpunkte befinden sich unter dem Präfix:
|
|
||||||
|
|
||||||
```text
|
|
||||||
/api/v1
|
|
||||||
```
|
```
|
||||||
|
2024-12-04 14:32:45.584 [main] INFO Application - Application started in 0.303 seconds.
|
||||||
### Öffentliche Endpunkte
|
2024-12-04 14:32:45.682 [main] INFO Application - Responding at http://0.0.0.0:8080
|
||||||
|
|
||||||
#### 1. Health-Check
|
|
||||||
|
|
||||||
**Methode:** `GET`
|
|
||||||
|
|
||||||
**Pfad:**
|
|
||||||
|
|
||||||
```text
|
|
||||||
/api/v1/health
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Beschreibung:**
|
|
||||||
|
|
||||||
Prüft, ob der Backend-Server ordnungsgemäß läuft.
|
|
||||||
|
|
||||||
**Antwort:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"status": "OK",
|
|
||||||
"message": "Kidio Backend läuft!"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 2. Benutzer-Registrierung
|
|
||||||
|
|
||||||
**Methode:** `POST`
|
|
||||||
|
|
||||||
**Pfad:**
|
|
||||||
|
|
||||||
```text
|
|
||||||
/api/v1/auth/register
|
|
||||||
```
|
|
||||||
|
|
||||||
**Request-Body:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"email": "test@family.de",
|
|
||||||
"passwordPlain": "strengGeheim1",
|
|
||||||
"familyName": "Familie Schmidt",
|
|
||||||
"childAge": 8,
|
|
||||||
"parentPin": "4321"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Antwort – `201 Created`:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"token": "eyJhbGciOiJIUzI1NiIsIn...",
|
|
||||||
"userId": "6380776f-88d3-4e9f-a2be-0d9cacae447e",
|
|
||||||
"message": "Registrierung erfolgreich"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 3. Benutzer-Login
|
|
||||||
|
|
||||||
**Methode:** `POST`
|
|
||||||
|
|
||||||
**Pfad:**
|
|
||||||
|
|
||||||
```text
|
|
||||||
/api/v1/auth/login
|
|
||||||
```
|
|
||||||
|
|
||||||
**Request-Body:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"email": "test@family.de",
|
|
||||||
"passwordPlain": "strengGeheim1"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Antwort – `200 OK`:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"token": "eyJhbGciOiJIUzI1NiIsIn...",
|
|
||||||
"userId": "6380776f-88d3-4e9f-a2be-0d9cacae447e",
|
|
||||||
"familyName": "Familie Schmidt",
|
|
||||||
"childAge": 8
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Geschützte Endpunkte
|
|
||||||
|
|
||||||
Die folgenden Endpunkte erfordern ein gültiges JWT im HTTP-Header:
|
|
||||||
|
|
||||||
```http
|
|
||||||
Authorization: Bearer <Token>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 4. Bildschirmzeit abrufen
|
|
||||||
|
|
||||||
**Methode:** `GET`
|
|
||||||
|
|
||||||
**Pfad:**
|
|
||||||
|
|
||||||
```text
|
|
||||||
/api/v1/screentime
|
|
||||||
```
|
|
||||||
|
|
||||||
**Beschreibung:**
|
|
||||||
|
|
||||||
Liefert die aktuell verbleibende Bildschirmzeit sowie den aktuellen Sperrstatus des authentifizierten Benutzers.
|
|
||||||
|
|
||||||
**Antwort – `200 OK`:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"remainingSeconds": 1800,
|
|
||||||
"isBlocked": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 5. Bildschirmzeit synchronisieren
|
|
||||||
|
|
||||||
**Methode:** `POST`
|
|
||||||
|
|
||||||
**Pfad:**
|
|
||||||
|
|
||||||
```text
|
|
||||||
/api/v1/screentime/sync
|
|
||||||
```
|
|
||||||
|
|
||||||
**Beschreibung:**
|
|
||||||
|
|
||||||
Zieht die seit dem letzten Abgleich vom Endgerät verbrauchten Sekunden von der verfügbaren Bildschirmzeit ab.
|
|
||||||
|
|
||||||
**Request-Body:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"usedSecondsSinceLastSync": 30
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Antwort – `200 OK`:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"remainingSeconds": 1770,
|
|
||||||
"isBlocked": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 6. Lernaufgaben abrufen (mit Fach- & Altersfilter)
|
|
||||||
|
|
||||||
**Methode:** `GET`
|
|
||||||
|
|
||||||
**Pfad:**
|
|
||||||
|
|
||||||
```text
|
|
||||||
/api/v1/tasks
|
|
||||||
```
|
|
||||||
|
|
||||||
**Optionale Query-Parameter:**
|
|
||||||
|
|
||||||
| Parameter | Typ | Standardwert | Beschreibung |
|
|
||||||
| :--- | :--- | :--- | :--- |
|
|
||||||
| `subject` | String | `null` | Schulfach (z. B. `Mathematik`, `Deutsch`, `Sachunterricht`, `Englisch`, `Biologie`, `Geographie`, `Geschichte`). |
|
|
||||||
| `age` | Integer | `null` | Zielalter des Kindes (filtert nach `minAge <= age AND maxAge >= age`). |
|
|
||||||
| `limit` | Integer | `10` | Maximale Anzahl an zurückgegebenen Aufgaben. |
|
|
||||||
|
|
||||||
**Beispiel-Anfrage:**
|
|
||||||
|
|
||||||
```text
|
|
||||||
GET /api/v1/tasks?subject=Mathematik&age=8&limit=5
|
|
||||||
```
|
|
||||||
|
|
||||||
**Beschreibung:**
|
|
||||||
|
|
||||||
Liefert kuratierte oder aus OER/H5P importierte Lernaufgaben. Die korrekte Lösung (`correctAnswer`) wird zum Schutz vor Manipulationen serverseitig geheim gehalten und niemals an den Client übertragen.
|
|
||||||
|
|
||||||
**Antwort – `200 OK`:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "zum-math-001",
|
|
||||||
"subject": "Mathematik",
|
|
||||||
"minAge": 8,
|
|
||||||
"maxAge": 9,
|
|
||||||
"question": "Was ist das Ergebnis von 6 × 8?",
|
|
||||||
"options": [
|
|
||||||
"42",
|
|
||||||
"48",
|
|
||||||
"54",
|
|
||||||
"56"
|
|
||||||
],
|
|
||||||
"rewardMinutes": 5,
|
|
||||||
"license": "CC BY-SA 4.0"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 7. Antwort überprüfen und Belohnung verbuchen
|
|
||||||
|
|
||||||
**Methode:** `POST`
|
|
||||||
|
|
||||||
**Pfad:**
|
|
||||||
|
|
||||||
```text
|
|
||||||
/api/v1/tasks/verify
|
|
||||||
```
|
|
||||||
|
|
||||||
**Request-Body:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"taskId": "zum-math-001",
|
|
||||||
"selectedAnswer": "48"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Antwort – korrekte Lösung (`200 OK`):**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"isCorrect": true,
|
|
||||||
"earnedMinutes": 5,
|
|
||||||
"message": "Super gemacht! Du hast 5 Minuten gewonnen."
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Antwort – falsche Lösung (`200 OK`):**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"isCorrect": false,
|
|
||||||
"earnedMinutes": 0,
|
|
||||||
"message": "Schade, das war leider falsch. Versuche es nochmal!"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## H5P / ZUM.de Aufgaben-Import (Standalone CLI)
|
|
||||||
|
|
||||||
Das Backend verfügt über ein integriertes CLI-Tool zum automatisierten Importieren und Crawlen interaktiver Lernaufgaben von **ZUM.de / OER (H5P)**.
|
|
||||||
|
|
||||||
### Batch-Import ausführen
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Alle Fächer importieren (z. B. 100 Aufgaben)
|
|
||||||
./gradlew runIngestion --args="--limit=100"
|
|
||||||
|
|
||||||
# Gezielt ein Schulfach importieren (z. B. Mathematik)
|
|
||||||
./gradlew runIngestion --args="--subject=Mathematik --limit=50"
|
|
||||||
|
|
||||||
# Deutsch für Grundschüler importieren
|
|
||||||
./gradlew runIngestion --args="--subject=Deutsch --limit=30"
|
|
||||||
```
|
|
||||||
|
|
||||||
### CLI-Optionen
|
|
||||||
|
|
||||||
| Argument | Standardwert | Beschreibung |
|
|
||||||
| :--- | :--- | :--- |
|
|
||||||
| `--subject=<Name>` | *(alle Fächer)* | Filtert Aufgaben nach Fach (z. B. `Mathematik`, `Deutsch`, `Englisch`, `Sachunterricht`). |
|
|
||||||
| `--limit=<Zahl>` | `50` | Maximale Anzahl an Aufgaben, die im Durchlauf importiert/aktualisiert werden. |
|
|
||||||
|
|
||||||
### Funktionsweise der Ingestion-Pipeline
|
|
||||||
1. **Live-Crawler & Discovery:** Durchsucht `apps.zum.de` nach interaktiven Inhalten (`H5P.MultiChoice`, `H5P.TrueFalse`, `H5P.QuestionSet`).
|
|
||||||
2. **Textbereinigung:** Entfernt störende HTML-Tags (`<p>`, `<strong>`, ` `) für native mobile Darstellung.
|
|
||||||
3. **Taxonomie- & Alters-Mapping:** Weist Altersgrenzen (`minAge`, `maxAge`) basierend auf Klassenstufen automatisch zu.
|
|
||||||
4. **Idempotenter Upsert:** Speichert Fragen, Optionen, geheime Lösungen und CC-Lizenzen direkt relational in PostgreSQL (`TasksTable`). Duplikate werden vermieden.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Projektkonfiguration und Umgebungsvariablen
|
|
||||||
|
|
||||||
Die Anwendung wird über folgende Konfigurationsdatei konfiguriert:
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/main/resources/application.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
Für eine flexible Konfiguration in lokalen Entwicklungsumgebungen, Docker-Containern oder Deployments können folgende Umgebungsvariablen gesetzt werden:
|
|
||||||
|
|
||||||
| Variable | Beschreibung | Standardwert |
|
|
||||||
| :------------ | :----------------------------------------- | :------------------------------- |
|
|
||||||
| `DB_HOST` | Hostname der PostgreSQL-Datenbank | `localhost` |
|
|
||||||
| `DB_PORT` | Port der PostgreSQL-Datenbank | `5432` |
|
|
||||||
| `DB_NAME` | Name der PostgreSQL-Datenbank | `kidio_db` |
|
|
||||||
| `DB_USER` | PostgreSQL-Benutzername | `kidio_admin` |
|
|
||||||
| `DB_PASSWORD` | PostgreSQL-Passwort | `learning` |
|
|
||||||
| `REDIS_HOST` | Hostname der Redis-Instanz | `localhost` |
|
|
||||||
| `REDIS_PORT` | Port der Redis-Instanz | `6379` |
|
|
||||||
| `JWT_SECRET` | Geheimer Schlüssel zur Signierung der JWTs | `super-geheimes-secret-fuer-dev` |
|
|
||||||
|
|
||||||
> **Hinweis:** Die angegebenen Standardwerte sind für die lokale Entwicklung gedacht. Für produktive Umgebungen sollten insbesondere `DB_PASSWORD` und `JWT_SECRET` über sichere Secrets bzw. Umgebungsvariablen gesetzt werden.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Lokale Entwicklung
|
|
||||||
|
|
||||||
### Voraussetzungen
|
|
||||||
|
|
||||||
Für die lokale Entwicklung werden benötigt:
|
|
||||||
|
|
||||||
* JDK / Java
|
|
||||||
* Kotlin bzw. Gradle Wrapper
|
|
||||||
* PostgreSQL
|
|
||||||
* Redis
|
|
||||||
|
|
||||||
Die konkreten Versionen richten sich nach der im Projekt konfigurierten Gradle- und Kotlin-Version.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Bauen und Ausführen
|
|
||||||
|
|
||||||
Das Projekt verwendet den Gradle Wrapper, sodass Gradle nicht global installiert sein muss.
|
|
||||||
|
|
||||||
### Anwendung starten
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew run
|
|
||||||
```
|
|
||||||
|
|
||||||
Der Ktor-Server ist anschließend standardmäßig unter folgender Adresse erreichbar:
|
|
||||||
|
|
||||||
```text
|
|
||||||
http://localhost:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### Projekt bauen
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tests ausführen
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew test
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Code Quality
|
|
||||||
|
|
||||||
Für die Einhaltung der Kotlin-Coding-Standards wird **KtLint** verwendet.
|
|
||||||
|
|
||||||
### Code automatisch formatieren
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew ktlintFormat
|
|
||||||
```
|
|
||||||
|
|
||||||
### Code Style prüfen
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew ktlintCheck
|
|
||||||
```
|
|
||||||
|
|
||||||
Der KtLint-Check wird ebenfalls innerhalb der CI-Pipeline ausgeführt.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Empfohlener Workflow vor Commits / Pull Requests
|
|
||||||
|
|
||||||
Vor einem Commit oder Pull Request sollte der folgende Workflow ausgeführt werden:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Kotlin-Code automatisch formatieren
|
|
||||||
./gradlew ktlintFormat
|
|
||||||
|
|
||||||
# 2. Code Style überprüfen
|
|
||||||
./gradlew ktlintCheck
|
|
||||||
|
|
||||||
# 3. Tests ausführen
|
|
||||||
./gradlew test
|
|
||||||
|
|
||||||
# 4. Projekt bauen
|
|
||||||
./gradlew build
|
|
||||||
|
|
||||||
# 5. Server lokal starten
|
|
||||||
./gradlew run
|
|
||||||
```
|
|
||||||
|
|
||||||
Der Server ist anschließend unter:
|
|
||||||
|
|
||||||
```text
|
|
||||||
http://localhost:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
erreichbar.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CI/CD Pipeline
|
|
||||||
|
|
||||||
Das Repository verwendet **Forgejo Actions** für Continuous Integration.
|
|
||||||
|
|
||||||
Bei jedem Push oder Pull Request auf den `main`-Branch wird automatisch ein Workflow ausgeführt.
|
|
||||||
|
|
||||||
Die Pipeline führt unter anderem folgende Prüfungen durch:
|
|
||||||
|
|
||||||
1. **Code Style**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew ktlintCheck
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Automatisierte Tests**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./gradlew test
|
|
||||||
```
|
|
||||||
|
|
||||||
Dadurch wird sichergestellt, dass Änderungen den bestehenden Code-Style einhalten und die automatisierten Tests weiterhin erfolgreich ausgeführt werden können.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architektur
|
|
||||||
|
|
||||||
Das Backend besteht im Wesentlichen aus folgenden Komponenten:
|
|
||||||
|
|
||||||
```text
|
|
||||||
┌───────────────────┐
|
|
||||||
│ Client │
|
|
||||||
│ Web / Mobile / │
|
|
||||||
│ Device │
|
|
||||||
└─────────┬─────────┘
|
|
||||||
│
|
|
||||||
│ HTTP / JSON
|
|
||||||
▼
|
|
||||||
┌───────────────────┐
|
|
||||||
│ Ktor │
|
|
||||||
│ REST API │
|
|
||||||
└─────────┬─────────┘
|
|
||||||
│
|
|
||||||
┌────────────┴────────────┐
|
|
||||||
│ │
|
|
||||||
▼ ▼
|
|
||||||
┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ Redis │ │ PostgreSQL │
|
|
||||||
│ Cache │ │ Persistence │
|
|
||||||
└────────┬────────┘ └────────┬────────┘
|
|
||||||
│ │
|
|
||||||
└────────────┬────────────┘
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
┌───────────────────┐
|
|
||||||
│ Exposed ORM │
|
|
||||||
└───────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### Redis-Fallback
|
|
||||||
|
|
||||||
Redis wird für schnelle Zugriffe auf häufig benötigte Daten, insbesondere die verbleibende Bildschirmzeit, verwendet.
|
|
||||||
|
|
||||||
Sollte Redis nicht erreichbar sein, greift das Backend automatisch auf PostgreSQL zurück.
|
|
||||||
|
|
||||||
Dadurch bleibt die Anwendung auch bei einem Ausfall des Caches funktionsfähig.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Bildschirmzeit-Logik
|
|
||||||
|
|
||||||
Die Bildschirmzeit wird intern in Sekunden verwaltet.
|
|
||||||
|
|
||||||
Beispielsweise:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Start:
|
|
||||||
1800 Sekunden
|
|
||||||
= 30 Minuten
|
|
||||||
|
|
||||||
Verbrauch:
|
|
||||||
200 Sekunden
|
|
||||||
|
|
||||||
Verbleibend:
|
|
||||||
1600 Sekunden
|
|
||||||
= 26 Minuten 40 Sekunden
|
|
||||||
```
|
|
||||||
|
|
||||||
Sobald die verbleibende Bildschirmzeit auf `0` oder darunter fällt, wird der Benutzer automatisch gesperrt:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"remainingSeconds": 0,
|
|
||||||
"isBlocked": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Durch das Lösen einer Aufgabe kann zusätzliche Bildschirmzeit erworben werden.
|
|
||||||
|
|
||||||
Beispiel:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Verbleibend: 0 Sekunden
|
|
||||||
Aufgaben-Belohnung: 10 Minuten
|
|
||||||
↓
|
|
||||||
Neue Bildschirmzeit: 600 Sekunden
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Lizenz
|
|
||||||
|
|
||||||
Falls für das Projekt eine bestimmte Lizenz verwendet wird, sollte diese hier ergänzt werden, beispielsweise:
|
|
||||||
|
|
||||||
```text
|
|
||||||
MIT License
|
|
||||||
```
|
|
||||||
|
|
||||||
Eine entsprechende `LICENSE`-Datei sollte sich im Root-Verzeichnis des Repositorys befinden.
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Kidio Ökosystem
|
|
||||||
Für Kinder (Der spielerische Teil - "Kid"):
|
|
||||||
|
|
||||||
* Kidio Learn: Interaktive Lernspiele (Mathe, Sprachen, Logik), die sich dem Alter und Lernfortschritt des Kindes anpassen.
|
|
||||||
|
|
||||||
* Kidio Create: Tools und Plattformen, um eigene Ideen, Kunstwerke oder kleine Projekte zu entwickeln und zu teilen.
|
|
||||||
|
|
||||||
* Kidio Media: Ein sicherer Bereich mit kindgerechten Videos, Hörspielen und Büchern (ähnlich wie YouTube Kids).
|
|
||||||

|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.kotlin.jvm)
|
alias(libs.plugins.kotlin.jvm)
|
||||||
alias(ktorLibs.plugins.ktor)
|
alias(ktorLibs.plugins.ktor)
|
||||||
id("org.jlleitschuh.gradle.ktlint") version "12.1.0"
|
|
||||||
kotlin("plugin.serialization") version "2.0.0"
|
kotlin("plugin.serialization") version "2.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -11,12 +10,6 @@ version = "1.0.0-SNAPSHOT"
|
||||||
application {
|
application {
|
||||||
mainClass = "io.ktor.server.netty.EngineMain"
|
mainClass = "io.ktor.server.netty.EngineMain"
|
||||||
}
|
}
|
||||||
ktor {
|
|
||||||
openApi {
|
|
||||||
enabled = true // Aktiviert die OpenAPI-Code-Generierung
|
|
||||||
codeInferenceEnabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(21)
|
jvmToolchain(21)
|
||||||
|
|
@ -26,37 +19,20 @@ dependencies {
|
||||||
implementation(ktorLibs.server.config.yaml)
|
implementation(ktorLibs.server.config.yaml)
|
||||||
implementation(ktorLibs.server.core)
|
implementation(ktorLibs.server.core)
|
||||||
implementation(ktorLibs.server.netty)
|
implementation(ktorLibs.server.netty)
|
||||||
implementation("io.ktor:ktor-server-openapi")
|
|
||||||
// JWT Authentication
|
|
||||||
implementation("io.ktor:ktor-server-auth")
|
|
||||||
implementation("io.ktor:ktor-server-auth-jwt")
|
|
||||||
|
|
||||||
implementation(libs.logback.classic)
|
implementation(libs.logback.classic)
|
||||||
|
|
||||||
// Bcrypt
|
|
||||||
implementation("org.mindrot:jbcrypt:0.4")
|
|
||||||
|
|
||||||
// JSON-Unterstützung und Serialization
|
// JSON-Unterstützung und Serialization
|
||||||
implementation("io.ktor:ktor-server-content-negotiation-jvm")
|
implementation("io.ktor:ktor-server-content-negotiation-jvm")
|
||||||
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")
|
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")
|
||||||
|
|
||||||
//postgres
|
//postgres
|
||||||
implementation("org.postgresql:postgresql:42.7.3")
|
implementation("org.postgresql:postgresql:42.7.3")
|
||||||
implementation("org.jetbrains.exposed:exposed-core:0.50.0")
|
implementation("org.jetbrains.exposed:exposed-core:0.50.0")
|
||||||
implementation("org.jetbrains.exposed:exposed-dao:0.50.0")
|
implementation("org.jetbrains.exposed:exposed-dao:0.50.0")
|
||||||
implementation("org.jetbrains.exposed:exposed-jdbc:0.50.0")
|
implementation("org.jetbrains.exposed:exposed-jdbc:0.50.0")
|
||||||
|
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
testImplementation(ktorLibs.server.testHost)
|
testImplementation(ktorLibs.server.testHost)
|
||||||
|
|
||||||
//redis
|
//redis
|
||||||
implementation("redis.clients:jedis:5.1.2")
|
implementation("redis.clients:jedis:5.1.2")
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register<JavaExec>("runIngestion") {
|
|
||||||
group = "application"
|
|
||||||
description = "Runs the standalone H5P/ZUM task ingestion CLI tool"
|
|
||||||
mainClass.set("com.oliver.kidio.backend.ingestion.H5PIngestionCli")
|
|
||||||
classpath = sourceSets["main"].runtimeClasspath
|
|
||||||
standardInput = System.`in`
|
|
||||||
}
|
}
|
||||||
Binary file not shown.
BIN
build/classes/kotlin/main/MainKt.class
Normal file
BIN
build/classes/kotlin/main/MainKt.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build/classes/kotlin/main/com/oliver/kidio/backend/Task.class
Normal file
BIN
build/classes/kotlin/main/com/oliver/kidio/backend/Task.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue