Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ jobs:
env:
NEVUS_DATABASE_URL: postgresql+psycopg://nevus:nevus@localhost:5432/nevus
run: uv run pytest
- name: OpenAPI snapshot is current
run: |
uv run nevus openapi > /tmp/openapi.json
diff -q /tmp/openapi.json ../frontend/openapi.json || { echo "frontend/openapi.json is stale: run 'just openapi'"; exit 1; }
- name: Migrations on SQLite
env:
NEVUS_DATA_DIR: ${{ runner.temp }}/sqlite
Expand Down Expand Up @@ -91,6 +95,10 @@ jobs:
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml
- run: pnpm install --frozen-lockfile
- name: Generated API types are current
run: |
pnpm openapi
git diff --exit-code -- src/lib/api/schema.d.ts || { echo "src/lib/api/schema.d.ts is stale: run 'just openapi'"; exit 1; }
- run: pnpm typecheck
- run: pnpm lint
- run: pnpm format:check
Expand Down
17 changes: 17 additions & 0 deletions backend/src/nevus/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,29 @@ def main(argv: list[str] | None = None) -> int:
parser.add_argument("--version", action="version", version=f"nevus {__version__}")
sub = parser.add_subparsers(dest="command", required=True)
sub.add_parser("serve", help="run the web application")
sub.add_parser("openapi", help="print the OpenAPI schema as JSON")
args = parser.parse_args(argv)
if args.command == "serve":
return _serve()
if args.command == "openapi":
return _openapi()
return 2


def _openapi() -> int:
import json
import tempfile
from pathlib import Path

from nevus.app import create_app
from nevus.config import Settings

with tempfile.TemporaryDirectory() as tmp:
app = create_app(Settings(data_dir=Path(tmp), log_level="warning"))
print(json.dumps(app.openapi(), indent=2, sort_keys=True))
return 0


def _serve() -> int:
import uvicorn

Expand Down
9 changes: 6 additions & 3 deletions backend/src/nevus/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import secrets
from functools import lru_cache
from pathlib import Path
from typing import Literal
from typing import Annotated, Literal

from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict


class Settings(BaseSettings):
Expand All @@ -19,7 +19,10 @@ class Settings(BaseSettings):
data_dir: Path = Field(default=Path("data"), description="Holds the database, blobs, backups and secrets")
database_url: str | None = Field(default=None, description="SQLAlchemy URL; defaults to SQLite inside data_dir")
static_dir: Path | None = Field(default=None, description="Built frontend; a placeholder is served when missing")
allowed_hosts: list[str] = Field(default_factory=list, description="Hostnames answered beyond private addresses")
# NoDecode keeps pydantic-settings from parsing the variable as JSON, so a plain "a.example, b.example" works.
allowed_hosts: Annotated[list[str], NoDecode] = Field(
default_factory=list, description="Hostnames answered beyond private addresses; comma-separated"
)
secret_key_file: Path | None = Field(default=None, description="Server secret file; generated when missing")
log_level: Literal["debug", "info", "warning", "error"] = "info"
role: Literal["all", "web", "worker"] = "all"
Expand Down
8 changes: 8 additions & 0 deletions backend/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,11 @@ def test_secret_key_is_generated_once_with_restrictive_permissions(tmp_path: Pat
assert first == second
assert len(first) == 64
assert oct((tmp_path / "secret.key").stat().st_mode & 0o777) == "0o600"


def test_allowed_hosts_read_from_the_environment_as_a_plain_list(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("NEVUS_ALLOWED_HOSTS", "nevus.example.com, Photos.Example.ORG ,")
settings = Settings(data_dir=tmp_path)
assert settings.allowed_hosts == ["nevus.example.com", "photos.example.org"]
3 changes: 3 additions & 0 deletions frontend/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ dist
dev-dist
node_modules
pnpm-lock.yaml
# Generated from the backend schema; kept byte-identical to the generator output so CI can diff it.
openapi.json
src/lib/api/schema.d.ts
Loading
Loading