From 6ce3ca84eb074080dc4c867f8322f2e6cb755090 Mon Sep 17 00:00:00 2001 From: Floke Date: Fri, 30 Jan 2026 11:55:37 +0000 Subject: [PATCH] [2f888f42] Container neu bauen und testne Container neu bauen und testne --- .dev_session/SESSION_INFO | 2 +- company-explorer/backend/app.py | 87 ++++++++++++++++++++------------- docker-compose.yml | 15 ++++++ readme.md | 22 +++++++++ 4 files changed, 90 insertions(+), 36 deletions(-) diff --git a/.dev_session/SESSION_INFO b/.dev_session/SESSION_INFO index 6da0f9a0..e73ceab1 100644 --- a/.dev_session/SESSION_INFO +++ b/.dev_session/SESSION_INFO @@ -1 +1 @@ -{"task_id": "2ea88f42-8544-806f-8946-e61ada8cc059", "token": "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", "session_start_time": "2026-01-29T10:55:09.010737"} \ No newline at end of file +{"task_id": "2f888f42-8544-81fc-a92d-f5670a995110", "token": "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", "session_start_time": "2026-01-30T11:54:46.937258"} \ No newline at end of file diff --git a/company-explorer/backend/app.py b/company-explorer/backend/app.py index 881fe9d0..5bdb46f2 100644 --- a/company-explorer/backend/app.py +++ b/company-explorer/backend/app.py @@ -8,6 +8,21 @@ from pydantic import BaseModel from datetime import datetime import os import sys +from fastapi.security import HTTPBasic, HTTPBasicCredentials +import secrets + +security = HTTPBasic() + +async def authenticate_user(credentials: HTTPBasicCredentials = Depends(security)): + correct_username = secrets.compare_digest(credentials.username, os.getenv("API_USER", "default_user")) + correct_password = secrets.compare_digest(credentials.password, os.getenv("API_PASSWORD", "default_password")) + if not (correct_username and correct_password): + raise HTTPException( + status_code=401, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Basic"}, + ) + return credentials.username from .config import settings from .lib.logging_setup import setup_logging @@ -82,18 +97,18 @@ def on_startup(): # --- Routes --- @app.get("/api/health") -def health_check(): +def health_check(username: str = Depends(authenticate_user)): return {"status": "ok", "version": settings.VERSION, "db": settings.DATABASE_URL} @app.get("/api/companies") def list_companies( - skip: int = 0, - limit: int = 50, + skip: int = 0, + limit: int = 50, search: Optional[str] = None, sort_by: Optional[str] = Query("name_asc"), - db: Session = Depends(get_db) -): - try: + db: Session = Depends(get_db), + username: str = Depends(authenticate_user) +): try: query = db.query(Company) if search: query = query.filter(Company.name.ilike(f"%{search}%")) @@ -129,7 +144,7 @@ def list_companies( raise HTTPException(status_code=500, detail=str(e)) @app.get("/api/companies/export") -def export_companies_csv(db: Session = Depends(get_db)): +def export_companies_csv(db: Session = Depends(get_db), username: str = Depends(authenticate_user)): """ Exports a CSV of all companies with their key metrics. """ @@ -171,7 +186,7 @@ def export_companies_csv(db: Session = Depends(get_db)): ) @app.get("/api/companies/{company_id}") -def get_company(company_id: int, db: Session = Depends(get_db)): +def get_company(company_id: int, db: Session = Depends(get_db), username: str = Depends(authenticate_user)): company = db.query(Company).options( joinedload(Company.enrichment_data), joinedload(Company.contacts) @@ -181,7 +196,7 @@ def get_company(company_id: int, db: Session = Depends(get_db)): return company @app.post("/api/companies") -def create_company(company: CompanyCreate, db: Session = Depends(get_db)): +def create_company(company: CompanyCreate, db: Session = Depends(get_db), username: str = Depends(authenticate_user)): db_company = db.query(Company).filter(Company.name == company.name).first() if db_company: raise HTTPException(status_code=400, detail="Company already registered") @@ -199,7 +214,7 @@ def create_company(company: CompanyCreate, db: Session = Depends(get_db)): return new_company @app.post("/api/companies/bulk") -def bulk_import_companies(req: BulkImportRequest, background_tasks: BackgroundTasks, db: Session = Depends(get_db)): +def bulk_import_companies(req: BulkImportRequest, background_tasks: BackgroundTasks, db: Session = Depends(get_db), username: str = Depends(authenticate_user)): imported_count = 0 for name in req.names: name = name.strip() @@ -217,7 +232,7 @@ def bulk_import_companies(req: BulkImportRequest, background_tasks: BackgroundTa return {"status": "success", "imported": imported_count} @app.post("/api/companies/{company_id}/override/wikipedia") -def override_wikipedia(company_id: int, url: str, background_tasks: BackgroundTasks, db: Session = Depends(get_db)): +def override_wikipedia(company_id: int, url: str, background_tasks: BackgroundTasks, db: Session = Depends(get_db), username: str = Depends(authenticate_user)): company = db.query(Company).filter(Company.id == company_id).first() if not company: raise HTTPException(404, detail="Company not found") @@ -253,15 +268,15 @@ def override_wikipedia(company_id: int, url: str, background_tasks: BackgroundTa return {"status": "updated"} @app.get("/api/robotics/categories") -def list_robotics_categories(db: Session = Depends(get_db)): +def list_robotics_categories(db: Session = Depends(get_db), username: str = Depends(authenticate_user)): return db.query(RoboticsCategory).all() @app.get("/api/industries") -def list_industries(db: Session = Depends(get_db)): +def list_industries(db: Session = Depends(get_db), username: str = Depends(authenticate_user)): return db.query(Industry).all() @app.get("/api/job_roles") -def list_job_roles(db: Session = Depends(get_db)): +def list_job_roles(db: Session = Depends(get_db), username: str = Depends(authenticate_user)): return db.query(JobRoleMapping).order_by(JobRoleMapping.pattern.asc()).all() @app.get("/api/mistakes") @@ -270,7 +285,8 @@ def list_reported_mistakes( company_id: Optional[int] = Query(None), skip: int = 0, limit: int = 50, - db: Session = Depends(get_db) + db: Session = Depends(get_db), + username: str = Depends(authenticate_user) ): query = db.query(ReportedMistake).options(joinedload(ReportedMistake.company)) @@ -290,11 +306,11 @@ class MistakeUpdateStatusRequest(BaseModel): @app.put("/api/mistakes/{mistake_id}") def update_reported_mistake_status( - mistake_id: int, - request: MistakeUpdateStatusRequest, - db: Session = Depends(get_db) -): - mistake = db.query(ReportedMistake).filter(ReportedMistake.id == mistake_id).first() + mistake_id: int, + request: MistakeUpdateStatusRequest, + db: Session = Depends(get_db), + username: str = Depends(authenticate_user) +): mistake = db.query(ReportedMistake).filter(ReportedMistake.id == mistake_id).first() if not mistake: raise HTTPException(404, detail="Reported mistake not found") @@ -310,14 +326,14 @@ def update_reported_mistake_status( return {"status": "success", "mistake": mistake} @app.post("/api/enrich/discover") -def discover_company(req: AnalysisRequest, background_tasks: BackgroundTasks, db: Session = Depends(get_db)): +def discover_company(req: AnalysisRequest, background_tasks: BackgroundTasks, db: Session = Depends(get_db), username: str = Depends(authenticate_user)): company = db.query(Company).filter(Company.id == req.company_id).first() if not company: raise HTTPException(404, "Company not found") background_tasks.add_task(run_discovery_task, company.id) return {"status": "queued"} @app.post("/api/enrich/analyze") -def analyze_company(req: AnalysisRequest, background_tasks: BackgroundTasks, db: Session = Depends(get_db)): +def analyze_company(req: AnalysisRequest, background_tasks: BackgroundTasks, db: Session = Depends(get_db), username: str = Depends(authenticate_user)): company = db.query(Company).filter(Company.id == req.company_id).first() if not company: raise HTTPException(404, "Company not found") @@ -329,12 +345,12 @@ def analyze_company(req: AnalysisRequest, background_tasks: BackgroundTasks, db: @app.put("/api/companies/{company_id}/industry") def update_company_industry( - company_id: int, - data: IndustryUpdateModel, + company_id: int, + data: IndustryUpdateModel, background_tasks: BackgroundTasks, - db: Session = Depends(get_db) -): - company = db.query(Company).filter(Company.id == company_id).first() + db: Session = Depends(get_db), + username: str = Depends(authenticate_user) +): company = db.query(Company).filter(Company.id == company_id).first() if not company: raise HTTPException(404, detail="Company not found") @@ -350,7 +366,7 @@ def update_company_industry( @app.post("/api/companies/{company_id}/reevaluate-wikipedia") -def reevaluate_wikipedia(company_id: int, background_tasks: BackgroundTasks, db: Session = Depends(get_db)): +def reevaluate_wikipedia(company_id: int, background_tasks: BackgroundTasks, db: Session = Depends(get_db), username: str = Depends(authenticate_user)): company = db.query(Company).filter(Company.id == company_id).first() if not company: raise HTTPException(404, detail="Company not found") @@ -360,7 +376,7 @@ def reevaluate_wikipedia(company_id: int, background_tasks: BackgroundTasks, db: @app.delete("/api/companies/{company_id}") -def delete_company(company_id: int, db: Session = Depends(get_db)): +def delete_company(company_id: int, db: Session = Depends(get_db), username: str = Depends(authenticate_user)): company = db.query(Company).filter(Company.id == company_id).first() if not company: raise HTTPException(404, detail="Company not found") @@ -375,7 +391,7 @@ def delete_company(company_id: int, db: Session = Depends(get_db)): return {"status": "deleted"} @app.post("/api/companies/{company_id}/override/website") -def override_website(company_id: int, url: str, db: Session = Depends(get_db)): +def override_website(company_id: int, url: str, db: Session = Depends(get_db), username: str = Depends(authenticate_user)): company = db.query(Company).filter(Company.id == company_id).first() if not company: raise HTTPException(404, detail="Company not found") @@ -387,7 +403,7 @@ def override_website(company_id: int, url: str, db: Session = Depends(get_db)): @app.post("/api/companies/{company_id}/override/impressum") -def override_impressum(company_id: int, url: str, background_tasks: BackgroundTasks, db: Session = Depends(get_db)): +def override_impressum(company_id: int, url: str, background_tasks: BackgroundTasks, db: Session = Depends(get_db), username: str = Depends(authenticate_user)): company = db.query(Company).filter(Company.id == company_id).first() @@ -441,14 +457,15 @@ def override_impressum(company_id: int, url: str, background_tasks: BackgroundTa def report_company_mistake( - company_id: int, + company_id: int, - request: ReportMistakeRequest, + request: ReportMistakeRequest, - db: Session = Depends(get_db) + db: Session = Depends(get_db), + + username: str = Depends(authenticate_user) ): - company = db.query(Company).filter(Company.id == company_id).first() if not company: diff --git a/docker-compose.yml b/docker-compose.yml index e5995980..803b7d2c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,5 +13,20 @@ services: volumes: - moltbot_data:/home/node/.clawd + company-explorer: + build: + context: ./company-explorer + container_name: company-explorer + restart: unless-stopped + ports: + - "8000:8000" + environment: + API_USER: "your_api_user" # Placeholder + API_PASSWORD: "your_api_password" # Placeholder + volumes: + - ./company-explorer/backend:/app/backend # Sideloading backend changes + - ./company-explorer/frontend_static:/frontend_static # Sideloading frontend changes if any + - ./companies_v3_fixed_2.db:/app/companies_v3_fixed_2.db # Database persistence, as per MIGRATION_PLAN.md + volumes: moltbot_data: {} diff --git a/readme.md b/readme.md index 2001d38d..016f1c0e 100644 --- a/readme.md +++ b/readme.md @@ -837,6 +837,28 @@ Dank des Volume-Mountings für die Python-Skripte ist die Entwicklung sehr effiz Ein zeitaufwändiger `docker-compose build` ist nur bei Änderungen am Frontend-Code, an den `Dockerfile`n oder an den `requirements.txt`/`package.json`-Dateien notwendig. +### API-Authentifizierung (Company Explorer) + +Der `company-explorer` Dienst erfordert nun eine HTTP Basic Authentication für alle API-Endpunkte (`/api/*`). Dies dient dem internen Zugriffsschutz. + +Um den Dienst zu nutzen, müssen folgende Umgebungsvariablen in der `docker-compose.yml` (oder in einer `.env`-Datei, wenn diese konfiguriert ist) gesetzt werden: + +```yaml +services: + company-explorer: + # ... andere Konfigurationen ... + environment: + API_USER: "IhrBenutzername" # Legen Sie hier Ihren gewünschten Benutzernamen fest + API_PASSWORD: "IhrSicheresPasswort" # Legen Sie hier Ihr gewünschtes sicheres Passwort fest +``` + +Stellen Sie sicher, dass Sie diese Werte vor dem Start des `company-explorer` Dienstes festlegen. Nach dem Aktualisieren der `docker-compose.yml` müssen Sie den Dienst neu starten: + +```bash +docker-compose restart company-explorer +``` + + --- ## 11. Market Intelligence: Funktionsweise v2.2 (Update Dez. 2025)