feat(api): implement notebook management CRUD endpoints

Implement Sprint 1: Notebook Management CRUD

- Add NotebookService with full CRUD operations
- Add POST /api/v1/notebooks (create notebook)
- Add GET /api/v1/notebooks (list with pagination)
- Add GET /api/v1/notebooks/{id} (get by ID)
- Add PATCH /api/v1/notebooks/{id} (partial update)
- Add DELETE /api/v1/notebooks/{id} (delete)
- Add Pydantic models for requests/responses
- Add custom exceptions (ValidationError, NotFoundError, NotebookLMError)
- Add comprehensive unit tests (31 tests, 97% coverage)
- Add API integration tests (26 tests)
- Fix router prefix duplication
- Fix JSON serialization in error responses

BREAKING CHANGE: None
This commit is contained in:
Luca Sacchi Ricciardi
2026-04-06 01:13:13 +02:00
commit 4b7a419a98
65 changed files with 10507 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
"""Core configuration for NotebookLM Agent API."""
from functools import lru_cache
from typing import Any
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
extra="ignore",
)
# API Configuration
api_key: str = Field(default="", alias="NOTEBOOKLM_AGENT_API_KEY")
webhook_secret: str = Field(default="", alias="NOTEBOOKLM_AGENT_WEBHOOK_SECRET")
port: int = Field(default=8000, alias="NOTEBOOKLM_AGENT_PORT")
host: str = Field(default="0.0.0.0", alias="NOTEBOOKLM_AGENT_HOST")
reload: bool = Field(default=False, alias="NOTEBOOKLM_AGENT_RELOAD")
# NotebookLM Configuration
notebooklm_home: str = Field(default="~/.notebooklm", alias="NOTEBOOKLM_HOME")
notebooklm_profile: str = Field(default="default", alias="NOTEBOOKLM_PROFILE")
# Redis Configuration
redis_url: str = Field(default="redis://localhost:6379/0", alias="REDIS_URL")
# Logging
log_level: str = Field(default="INFO", alias="LOG_LEVEL")
log_format: str = Field(default="json", alias="LOG_FORMAT")
# Development
debug: bool = Field(default=False, alias="DEBUG")
testing: bool = Field(default=False, alias="TESTING")
# Security
cors_origins: list[str] = Field(default_factory=list, alias="CORS_ORIGINS")
@field_validator("cors_origins", mode="before")
@classmethod
def parse_cors_origins(cls, v: Any) -> list[str]:
"""Parse CORS origins from string or list."""
if isinstance(v, str):
return [origin.strip() for origin in v.split(",") if origin.strip()]
return v if v else []
@field_validator("log_level")
@classmethod
def validate_log_level(cls, v: str) -> str:
"""Validate log level is one of the allowed values."""
allowed = {"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
v_upper = v.upper()
if v_upper not in allowed:
raise ValueError(f"log_level must be one of {allowed}")
return v_upper
@property
def is_production(self) -> bool:
"""Check if running in production mode."""
return not self.debug and not self.testing
@lru_cache()
def get_settings() -> Settings:
"""Get cached settings instance.
Returns:
Settings instance loaded from environment.
"""
return Settings()