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
13 changes: 12 additions & 1 deletion src/squishmark/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@

from typing import Annotated

from fastapi import Depends
from fastapi import Depends, Request

from squishmark.config import Settings, get_settings

# Type alias for settings dependency
SettingsDep = Annotated[Settings, Depends(get_settings)]


def is_admin(request: Request) -> bool:
"""Check if the current user is an admin without requiring auth."""
settings = get_settings()
if settings.debug and settings.dev_skip_auth:
return True
user = request.session.get("user") if hasattr(request, "session") else None
if user is None:
return False
return user.get("login") in settings.admin_users_list
13 changes: 10 additions & 3 deletions src/squishmark/routers/pages.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
"""Routes for static pages."""

from fastapi import APIRouter, HTTPException, Request
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import HTMLResponse
from sqlalchemy.ext.asyncio import AsyncSession

from squishmark.dependencies import is_admin
from squishmark.models.content import Config
from squishmark.models.db import get_db_session
from squishmark.services.content import get_all_posts, get_featured_posts
from squishmark.services.github import get_github_service
from squishmark.services.markdown import get_markdown_service
from squishmark.services.notes import NotesService
from squishmark.services.theme import get_theme_engine

router = APIRouter(tags=["pages"])
Expand All @@ -16,6 +20,7 @@
async def get_page(
request: Request,
slug: str,
db: AsyncSession = Depends(get_db_session),
) -> HTMLResponse:
"""
Get a static page by slug.
Expand Down Expand Up @@ -46,8 +51,10 @@ async def get_page(
if page.visibility == "hidden":
raise HTTPException(status_code=404, detail="Page not found")

# TODO: Get notes for this page from database
notes: list = []
# Fetch notes (private notes only visible to admins)
admin = is_admin(request)
notes_service = NotesService(db)
notes = await notes_service.get_for_path(f"/{slug}", include_private=admin)

# Featured posts for template context
all_posts = await get_all_posts(github_service, markdown_service)
Expand Down
28 changes: 11 additions & 17 deletions src/squishmark/routers/posts.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
"""Routes for blog posts."""

from fastapi import APIRouter, HTTPException, Query, Request
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import HTMLResponse
from sqlalchemy.ext.asyncio import AsyncSession

from squishmark.config import get_settings
from squishmark.dependencies import is_admin
from squishmark.models.content import Config, Pagination
from squishmark.models.db import get_db_session
from squishmark.services.content import get_all_posts, get_featured_posts
from squishmark.services.github import get_github_service
from squishmark.services.markdown import get_markdown_service
from squishmark.services.notes import NotesService
from squishmark.services.theme import get_theme_engine

router = APIRouter(prefix="/posts", tags=["posts"])


def _is_admin(request: Request) -> bool:
"""Check if the current user is an admin without requiring auth."""
settings = get_settings()
if settings.debug and settings.dev_skip_auth:
return True
user = request.session.get("user") if hasattr(request, "session") else None
if user is None:
return False
return user.get("login") in settings.admin_users_list


@router.get("", response_class=HTMLResponse)
async def list_posts(
request: Request,
Expand All @@ -40,7 +32,7 @@ async def list_posts(
markdown_service = get_markdown_service(config)

# Get all posts (admins can see drafts)
include_drafts = _is_admin(request)
include_drafts = is_admin(request)
all_posts = await get_all_posts(github_service, markdown_service, include_drafts=include_drafts)

# Paginate
Expand Down Expand Up @@ -76,6 +68,7 @@ async def list_posts(
async def get_post(
request: Request,
slug: str,
db: AsyncSession = Depends(get_db_session),
) -> HTMLResponse:
"""Get a single post by slug."""
github_service = get_github_service()
Expand All @@ -88,16 +81,17 @@ async def get_post(
markdown_service = get_markdown_service(config)

# Get all posts and find the matching one (admins can see drafts)
include_drafts = _is_admin(request)
include_drafts = is_admin(request)
all_posts = await get_all_posts(github_service, markdown_service, include_drafts=include_drafts)

post = next((p for p in all_posts if p.slug == slug), None)

if post is None:
raise HTTPException(status_code=404, detail="Post not found")

# TODO: Get notes for this post from database
notes: list = []
# Fetch notes (private notes only visible to admins)
notes_service = NotesService(db)
notes = await notes_service.get_for_path(f"/posts/{slug}", include_private=include_drafts)

# Featured posts for template context
featured = get_featured_posts(all_posts, config.site)
Expand Down
8 changes: 6 additions & 2 deletions tests/test_nav_pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,16 @@ async def test_hidden_page_returns_404(self):
with (
patch("squishmark.routers.pages.get_github_service") as mock_get_github,
patch("squishmark.routers.pages.get_theme_engine"),
patch("squishmark.routers.pages.NotesService") as mock_notes_cls,
):
mock_github = AsyncMock()
mock_get_github.return_value = mock_github
mock_github.get_config.return_value = None
mock_github.get_file.return_value = MagicMock(content=hidden_content)
mock_notes_cls.return_value = AsyncMock()

with pytest.raises(HTTPException) as exc_info:
await get_page(mock_request, "secret")
await get_page(mock_request, "secret", db=AsyncMock())

assert exc_info.value.status_code == 404

Expand All @@ -102,18 +104,20 @@ async def test_unlisted_page_does_not_404(self):
with (
patch("squishmark.routers.pages.get_github_service") as mock_get_github,
patch("squishmark.routers.pages.get_theme_engine") as mock_get_engine,
patch("squishmark.routers.pages.NotesService") as mock_notes_cls,
):
mock_github = AsyncMock()
mock_get_github.return_value = mock_github
mock_github.get_config.return_value = None
mock_github.get_file.return_value = MagicMock(content=unlisted_content)
mock_notes_cls.return_value = AsyncMock()

mock_engine = AsyncMock()
mock_get_engine.return_value = mock_engine
mock_engine.render_page.return_value = "<html>Unlisted</html>"

mock_request = MagicMock()
response = await get_page(mock_request, "unlisted")
response = await get_page(mock_request, "unlisted", db=AsyncMock())

assert response.status_code == 200

Expand Down
Loading
Loading