From 64b828c46e28d6b1308b1819db6bbbfa17fcc16b Mon Sep 17 00:00:00 2001 From: jp170na Date: Thu, 4 Jun 2026 19:15:20 +0200 Subject: [PATCH] Update README with webhook status --- README.md | 178 +++++++++++---------------------------------- app/main.py | 75 ++++++++++++++++++- docker-compose.yml | 1 + 3 files changed, 115 insertions(+), 139 deletions(-) diff --git a/README.md b/README.md index af4e2b4..50c09b2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Agent pre manažment záverečných prác nad repozitárom `zpwiki`. -Projekt rieši základnú službu pre indexovanie a vyhľadávanie v Markdown súboroch zo školského repozitára záverečných prác. Cieľom je vytvoriť samostatné API, ktoré vie načítať obsah `zpwiki`, spracovať metadata, rozdeliť dokumenty na chunky, vyhľadávať v nich a neskôr sa napojiť na OpenWebUI, RAG, znalostný graf a Gitea webhook. +Projekt zatiaľ rieši základnú časť systému pre vyhľadávanie v Markdown súboroch zo školského repozitára záverečných prác. Cieľom je vytvoriť samostatnú službu, ktorá vie indexovať obsah `zpwiki`, vyhľadávať v ňom a neskôr sa napojí na OpenWebUI, RAG, znalostný graf a webhook synchronizáciu. ## Aktuálny stav @@ -17,16 +17,8 @@ Zatiaľ je implementované: 7. rozlíšenie režimu vyhľadávania: 1. `person` pre mená osôb, napríklad `jan ptak`, 2. `topic` pre tematické dopyty, napríklad `rag agent` alebo `knowledge graph`, -8. FastAPI backend s endpointmi: - 1. `/health`, - 2. `/search`, - 3. `/sync`, -9. automatická Swagger dokumentácia API, -10. Dockerfile pre zostavenie API kontajnera, -11. `docker-compose.yml` pre spustenie služby, -12. mount repozitára `zpwiki` do kontajnera, -13. environment premenná `ZPWIKI_ROOT`, -14. reindexovanie dát cez endpoint `/sync`. +8. FastAPI backend s endpointmi `/health` a `/search`, +9. automatická Swagger dokumentácia API. ## Štruktúra projektu @@ -39,14 +31,10 @@ dp-zp-agent/ │ ├── scan_zpwiki.py │ ├── build_chunks.py │ ├── build_sqlite_index.py -│ ├── rebuild_index.py │ ├── search_chunks.py │ └── search_db.py ├── data/ -├── Dockerfile -├── docker-compose.yml ├── requirements.txt -├── .dockerignore ├── .gitignore └── README.md ``` @@ -91,18 +79,6 @@ Nakoniec sa vytvorí SQLite index: python scripts/build_sqlite_index.py ``` -Alebo sa dá celý index obnoviť jedným príkazom: - -```bash -python scripts/rebuild_index.py -``` - -S voliteľným `git pull` pred reindexovaním: - -```bash -python scripts/rebuild_index.py --pull -``` - ## Testovanie vyhľadávania v termináli Vyhľadávanie podľa osoby: @@ -123,7 +99,7 @@ Vyhľadávanie podľa znalostného grafu: python scripts/search_db.py "knowledge graph" ``` -## Spustenie API lokálne +## Spustenie API FastAPI server sa spustí príkazom: @@ -145,24 +121,6 @@ curl -X POST http://127.0.0.1:8000/search \ -d '{"query":"jan ptak","limit":5}' ``` -Reindexovanie cez API: - -```bash -curl -X POST http://127.0.0.1:8000/sync \ - -H "Content-Type: application/json" \ - -d '{"pull_git":false}' -``` - -Reindexovanie aj s `git pull`: - -```bash -curl -X POST http://127.0.0.1:8000/sync \ - -H "Content-Type: application/json" \ - -d '{"pull_git":true}' -``` - -Poznámka: pri Docker verzii môže `pull_git:true` vyžadovať vyriešenie SSH prístupu ku Gitu. Zatiaľ je bezpečné používať hlavne `pull_git:false`. - ## Swagger UI FastAPI automaticky generuje Swagger dokumentáciu API. @@ -173,68 +131,26 @@ Po spustení servera je dostupná na adrese: http://127.0.0.1:8000/docs ``` -V Swagger UI je možné testovať endpointy `/health`, `/search` a `/sync` priamo z prehliadača. +V Swagger UI je možné testovať endpointy `/health` a `/search` priamo z prehliadača. -## Spustenie cez Docker +## Čo ešte treba dorobiť -Služba sa dá spustiť cez Docker Compose: +### 1. Dockerizácia aplikácie + +Treba vytvoriť: + +1. `Dockerfile`, +2. `docker-compose.yml`, +3. jednoduchý návod na spustenie cez Docker, +4. volume alebo mount pre dáta a SQLite databázu. + +Cieľ je, aby sa služba dala spustiť jedným príkazom: ```bash docker compose up --build ``` -Po spustení je API dostupné na: - -```text -http://127.0.0.1:8000 -``` - -Swagger UI: - -```text -http://127.0.0.1:8000/docs -``` - -Health check: - -```bash -curl http://127.0.0.1:8000/health -``` - -Vyhľadávanie: - -```bash -curl -X POST http://127.0.0.1:8000/search \ - -H "Content-Type: application/json" \ - -d '{"query":"knowledge graph","limit":5}' -``` - -Reindexovanie: - -```bash -curl -X POST http://127.0.0.1:8000/sync \ - -H "Content-Type: application/json" \ - -d '{"pull_git":false}' -``` - -V `docker-compose.yml` je repozitár `zpwiki` pripojený ako volume: - -```yaml -volumes: - - ./data:/app/data - - ../zpwiki:/zpwiki -``` - -A cesta k nemu je nastavená cez environment premennú: - -```yaml -environment: - - ZPWIKI_ROOT=/zpwiki -``` - -## Čo ešte treba dorobiť - -### 1. Upratanie kódu do modulov +### 2. Upratanie kódu do modulov Aktuálne je veľká časť logiky priamo v `app/main.py`. Neskôr treba kód rozdeliť napríklad takto: @@ -250,20 +166,19 @@ app/ Cieľ je, aby API, vyhľadávanie, databáza a synchronizácia neboli v jednom veľkom súbore. -### 2. Vylepšenie synchronizácie so `zpwiki` +### 3. Synchronizácia so `zpwiki` -Aktuálne endpoint `/sync` vie obnoviť celý index. Neskôr treba pridať efektívnejšiu synchronizáciu. +Treba pridať mechanizmus, ktorý bude vedieť aktualizovať dáta zo školského repozitára. -Treba dorobiť: +Plánované časti: -1. zistenie aktuálneho commitu, -2. uloženie posledného spracovaného commitu, -3. detekciu zmenených Markdown súborov, -4. reindexovanie iba zmenených dokumentov, -5. uloženie stavu synchronizácie do databázy, -6. logovanie výsledkov synchronizácie. +1. skript pre `git pull`, +2. zistenie aktuálneho commitu, +3. detekcia zmenených Markdown súborov, +4. reindexovanie zmenených dokumentov, +5. uloženie stavu synchronizácie do databázy. -### 3. Webhook endpoint pre Gitea +### 4. Webhook endpoint pre Gitea Treba vytvoriť endpoint napríklad: @@ -275,12 +190,11 @@ Tento endpoint má: 1. prijať webhook z Gitea, 2. overiť secret alebo podpis webhooku, -3. spracovať push event, -4. spustiť synchronizáciu repozitára, -5. spustiť reindexovanie, -6. vrátiť výsledok synchronizácie. +3. spustiť synchronizáciu repozitára, +4. spustiť reindexovanie zmenených súborov, +5. zapísať výsledok do logu alebo tabuľky synchronizácie. -### 4. OpenWebUI integrácia +### 5. OpenWebUI integrácia Treba napojiť API na OpenWebUI. @@ -293,7 +207,7 @@ Možné riešenia: Cieľ je, aby používateľ mohol v OpenWebUI položiť otázku a agent použil vyhľadávanie nad `zpwiki`. -### 5. Embeddingy a vektorové vyhľadávanie +### 6. Embeddingy a vektorové vyhľadávanie Aktuálne vyhľadávanie je fulltextové a skórovacie. Ďalší krok je pridať embeddingy. @@ -305,14 +219,14 @@ Treba dorobiť: 4. vektorové vyhľadávanie, 5. porovnanie fulltextového a vektorového vyhľadávania. -Možné databázy alebo nástroje: +Možné databázy: 1. PostgreSQL plus pgvector, 2. Qdrant, 3. ChromaDB, 4. FAISS ako jednoduchý lokálny prototyp. -### 6. RAG odpovede s citáciami +### 7. RAG odpovede s citáciami Treba doplniť generovanie odpovede pomocou jazykového modelu. @@ -326,7 +240,7 @@ Postup: Cieľ je, aby agent nehalucinoval a vedel ukázať, z ktorých dokumentov odpovedal. -### 7. Znalostný graf +### 8. Znalostný graf Treba vytvoriť štruktúrovaný graf nad dátami zo `zpwiki`. @@ -348,7 +262,7 @@ Základné vzťahy: 5. práca je podobná inej práci, 6. práca patrí do roka alebo obdobia. -### 8. GraphRAG +### 9. GraphRAG Treba prepojiť RAG a znalostný graf. @@ -360,7 +274,7 @@ GraphRAG časť má umožniť: 4. analýzu tém podľa tagov, rokov a kategórií, 5. kombináciu textového, vektorového a grafového vyhľadávania. -### 9. Vyhodnotenie systému +### 10. Vyhodnotenie systému Treba pripraviť testovaciu sadu otázok a porovnať viacero prístupov. @@ -389,7 +303,7 @@ Sledované vlastnosti: 5. čas odpovede, 6. čas reindexovania po zmene v Gite. -### 10. Dokumentácia do diplomovej práce +### 11. Dokumentácia do diplomovej práce Treba priebežne písať: @@ -400,20 +314,10 @@ Treba priebežne písať: 5. ako funguje `zpwiki`, 6. návrh architektúry systému, 7. návrh databázy a indexu, -8. návrh synchronizácie, -9. návrh webhook integrácie, -10. návrh integrácie s OpenWebUI, -11. popis experimentov a vyhodnotenia. +8. návrh webhook synchronizácie, +9. návrh integrácie s OpenWebUI, +10. popis experimentov a vyhodnotenia. ## Najbližší praktický krok -Najbližšie treba spraviť Gitea webhook endpoint. - -Poradie najbližších krokov: - -1. commitnúť aktuálne zmeny, -2. pushnúť projekt na KEMT Git, -3. vytvoriť endpoint `POST /webhook/gitea`, -4. pridať overenie secretu alebo podpisu webhooku, -5. napojiť webhook na `/sync`, -6. otestovať webhook lokálne alebo cez verejne dostupný tunel. +Najbližšie treba spraviť Docker nasadenie aktuálneho FastAPI prototypu. diff --git a/app/main.py b/app/main.py index 634725a..19e1ef5 100644 --- a/app/main.py +++ b/app/main.py @@ -1,4 +1,7 @@ from pathlib import Path +import hashlib +import hmac +import json import os import re import sqlite3 @@ -8,12 +11,13 @@ import time import unicodedata from collections import Counter -from fastapi import FastAPI, HTTPException +from fastapi import FastAPI, Header, HTTPException, Request from pydantic import BaseModel, Field DB_FILE = Path("data/zp_index.sqlite") ZPWIKI_ROOT = Path(os.getenv("ZPWIKI_ROOT", "../zpwiki")) +WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET", "dev-secret") TECHNICAL_TERMS = { @@ -51,7 +55,7 @@ TECHNICAL_TERMS = { app = FastAPI( title="ZP Agent API", description="API pre vyhľadávanie v repozitári záverečných prác zpwiki.", - version="0.2.0", + version="0.3.0", ) @@ -338,6 +342,31 @@ def rebuild_index(pull_git: bool = False) -> dict: } +def verify_gitea_signature(raw_body: bytes, signature: str | None) -> bool: + if not signature: + return False + + expected = hmac.new( + WEBHOOK_SECRET.encode("utf-8"), + raw_body, + hashlib.sha256, + ).hexdigest() + + signature = signature.strip() + + if signature.startswith("sha256="): + signature = signature.replace("sha256=", "", 1) + + return hmac.compare_digest(expected, signature) + + +def verify_simple_token(token: str | None) -> bool: + if not token: + return False + + return hmac.compare_digest(token, WEBHOOK_SECRET) + + @app.get("/health") def health(): return { @@ -346,6 +375,7 @@ def health(): "database_path": str(DB_FILE), "zpwiki_root": str(ZPWIKI_ROOT), "zpwiki_exists": ZPWIKI_ROOT.exists(), + "webhook_secret_configured": bool(WEBHOOK_SECRET), } @@ -377,3 +407,44 @@ def sync(request: SyncRequest): "duration_seconds": result["duration_seconds"], "counts": result["counts"], } + + +@app.post("/webhook/gitea") +async def gitea_webhook( + request: Request, + x_gitea_event: str | None = Header(default=None, alias="X-Gitea-Event"), + x_gitea_signature: str | None = Header(default=None, alias="X-Gitea-Signature"), + x_gitea_token: str | None = Header(default=None, alias="X-Gitea-Token"), +): + raw_body = await request.body() + + signature_ok = verify_gitea_signature(raw_body, x_gitea_signature) + token_ok = verify_simple_token(x_gitea_token) + + if not signature_ok and not token_ok: + raise HTTPException( + status_code=401, + detail="Invalid webhook signature or token", + ) + + try: + payload = json.loads(raw_body.decode("utf-8")) if raw_body else {} + except json.JSONDecodeError: + payload = {} + + repository = payload.get("repository", {}) + repository_name = repository.get("full_name") or repository.get("name") + + try: + result = rebuild_index(pull_git=False) + except RuntimeError as error: + raise HTTPException(status_code=500, detail=str(error)) from error + + return { + "status": "ok", + "event": x_gitea_event or "unknown", + "repository": repository_name, + "verified_by": "signature" if signature_ok else "token", + "duration_seconds": result["duration_seconds"], + "counts": result["counts"], + } diff --git a/docker-compose.yml b/docker-compose.yml index dfcdbb6..a83890f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,7 @@ services: - "8000:8000" environment: - ZPWIKI_ROOT=/zpwiki + - WEBHOOK_SECRET=dev-secret volumes: - ./data:/app/data - ../zpwiki:/zpwiki