diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e366af56..a7b16aeb 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -41,7 +41,7 @@ pip install --upgrade pip && pip install -r requirements.txt **Dev server**: `npm run dev` at -**Environment** (optional): `NEXT_PUBLIC_SEARCH_INDEX_URL=https://hadiscover.com/data/search-index.json` for alternate static data; `NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1` only enables the local dev re-index button +**Environment** (optional): `NEXT_PUBLIC_SEARCH_INDEX_URL=https://hadiscover.ravensberg.org/data/search-index.json` for alternate static data; `NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1` only enables the local dev re-index button **Code Style**: Biome formatter/linter (configured in `/biome.json`). TypeScript strict mode enabled. Use functional components with hooks. @@ -50,7 +50,7 @@ pip install --upgrade pip && pip install -r requirements.txt ```bash # Backend: docker build -t test ./backend && docker run -d -p 8000:8000 -e ENVIRONMENT=development test # Wait 5s, then: curl http://localhost:8000/api/v1/health -# Frontend: docker build --build-arg NEXT_PUBLIC_SEARCH_INDEX_URL=https://hadiscover.com/data/search-index.json -t test ./frontend +# Frontend: docker build --build-arg NEXT_PUBLIC_SEARCH_INDEX_URL=https://hadiscover.ravensberg.org/data/search-index.json -t test ./frontend # docker run -d -p 8080:80 test && sleep 3 && curl http://localhost:8080 ``` @@ -98,7 +98,7 @@ Root: README.md, ARCHITECTURE.md, docker-compose.yml ## Key Implementation Notes -- **CORS**: Backend allows localhost:8080, hadiscover.com. Update `app/main.py` if forking. +- **CORS**: Backend allows localhost:8080, hadiscover.ravensberg.org. Update `app/main.py` if forking. - **Rate limit**: /api/v1/index limited to once per 10min (in-memory). - **Indexing**: Runs in background thread; returns immediately. - **Parsing**: Best-effort YAML parser; gracefully handles errors. Supports both old (`trigger`/`action`/`platform`) and new (`triggers`/`actions`/`trigger`) Home Assistant YAML formats. Automatically deduplicates trigger types. diff --git a/.github/scripts/create-github-release.sh b/.github/scripts/create-github-release.sh index 0f37e190..4ed4b28d 100755 --- a/.github/scripts/create-github-release.sh +++ b/.github/scripts/create-github-release.sh @@ -27,7 +27,7 @@ cat >"${RELEASE_NOTES_FILE}" < None: + """Allow the local and production frontend origins.""" + client = TestClient(app) + response = client.get("/api/v1/health", headers={"Origin": origin}) + + assert response.status_code == 200 + assert response.headers["access-control-allow-origin"] == origin + assert response.headers["access-control-allow-credentials"] == "true" + + +@pytest.mark.parametrize( + "origin", ["http://localhost:8080", "https://hadiscover.ravensberg.org"] +) +@pytest.mark.parametrize("method", ["GET", "POST"]) +def test_cors_preflight_allowed_origin(origin: str, method: str) -> None: + """Allow preflight requests for supported frontend methods and headers.""" + client = TestClient(app) + response = client.options( + "/api/v1/health", + headers={ + "Origin": origin, + "Access-Control-Request-Method": method, + "Access-Control-Request-Headers": "Content-Type, Authorization", + }, + ) + + assert response.status_code == 200 + assert response.headers["access-control-allow-origin"] == origin + assert response.headers["access-control-allow-credentials"] == "true" + + +def test_cors_disallowed_origin() -> None: + """Do not grant cross-origin access to unrelated sites.""" + client = TestClient(app) + response = client.get("/api/v1/health", headers={"Origin": "https://example.com"}) + + assert response.status_code == 200 + assert "access-control-allow-origin" not in response.headers + + preflight = client.options( + "/api/v1/health", + headers={ + "Origin": "https://example.com", + "Access-Control-Request-Method": "GET", + }, + ) + + assert preflight.status_code == 400 + assert "access-control-allow-origin" not in preflight.headers + + def test_root_endpoint(): """Test root endpoint.""" client = TestClient(app) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 33f9a731..7849abde 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,5 +1,5 @@ # Production docker-compose configuration for the static frontend only. -# The public hadiscover.com deployment is served by GitHub Pages and does not +# The public hadiscover.ravensberg.org deployment is served by GitHub Pages and does not # require a hadiscover-backend/API container. # Usage: docker-compose -f docker-compose.prod.yml up -d @@ -8,7 +8,7 @@ services: build: context: ./frontend args: - NEXT_PUBLIC_SEARCH_INDEX_URL: ${NEXT_PUBLIC_SEARCH_INDEX_URL:-https://hadiscover.com/data/search-index.json} + NEXT_PUBLIC_SEARCH_INDEX_URL: ${NEXT_PUBLIC_SEARCH_INDEX_URL:-https://hadiscover.ravensberg.org/data/search-index.json} container_name: web ports: - "8080:80" diff --git a/docker-compose.yml b/docker-compose.yml index 365ce154..dd7c2a70 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,7 +40,7 @@ services: build: context: ./frontend args: - NEXT_PUBLIC_SEARCH_INDEX_URL: ${NEXT_PUBLIC_SEARCH_INDEX_URL:-https://hadiscover.com/data/search-index.json} + NEXT_PUBLIC_SEARCH_INDEX_URL: ${NEXT_PUBLIC_SEARCH_INDEX_URL:-https://hadiscover.ravensberg.org/data/search-index.json} container_name: web ports: diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt index 2307e60d..02fb761c 100644 --- a/frontend/public/robots.txt +++ b/frontend/public/robots.txt @@ -8,4 +8,4 @@ Allow: /icon.png Allow: /apple-icon.png # Sitemap location (if you add one in the future) -# Sitemap: https://hadiscover.com/sitemap.xml +# Sitemap: https://hadiscover.ravensberg.org/sitemap.xml