Skip to content

Add public read-only REST API at /api/v1/ (#1268)#1419

Merged
jonfroehlich merged 1 commit into
masterfrom
1268-rest-api
Jul 18, 2026
Merged

Add public read-only REST API at /api/v1/ (#1268)#1419
jonfroehlich merged 1 commit into
masterfrom
1268-rest-api

Conversation

@jonfroehlich

Copy link
Copy Markdown
Member

Closes #1268.

Adds a versioned, read-only DRF API over already-public content so external sites (Project Sidewalk, Jon's academic page) can treat this website as the source of truth instead of duplicating content. The data was already public on the site; this just makes it machine-readable.

Endpoints (/api/v1/, all GET-only, paginated)

  • /publications/ — filters ?project=, ?author=, ?year=, ?type=, ?ordering= (default newest-first). Powers both drivers: ?author=jonfroehlich&page_size=5 (recent-pubs widget) and ?project=sidewalk.
  • /projects/ (visible only), /grants/, /people/ (lab members only).
  • Project sub-resources: /projects/<short_name>/{publications,grants,people,leadership}.
    • leadership returns all lead roles for all time (current + past), grouped by type, each with an is_active flag. It queries ProjectRole directly rather than reusing get_project_leadership(), which drops a person's past lead roles once they hold any active role.

Design decisions (from #1268 scoping)

  • DRF — was a bundled-but-unused dependency; no new package added.
  • Public, no auth, no throttle — data is already public.
  • Cross-origin enabled on /api/ only via a tiny in-repo ApiCorsMiddleware (no django-cors-headers), so a browser-side widget can fetch it directly.
  • Absolute media/page URLs in every payload.
  • Person.email deliberately not exposed (avoids an email-harvesting surface).
  • Projects gated to is_visible=True; /people/ scoped to actual members (those with a Position).

Code / docs / tests

  • website/api/ (serializers.py, views.py, urls.py, middleware.py), mounted by the root URLconf; REST_FRAMEWORK block in settings.py.
  • Reference: docs/API.md (endpoints, filters, stability contract); pointer in CLAUDE.md.
  • 23 integration tests in website/tests/test_api.py (filters, pagination, visibility gating, 404s, absolute URLs, CORS + preflight, all-time leadership incl. the past-role-of-an-active-person case, email-not-exposed). Full suite: 607 pass.
  • Version bumped to 2.27.0.

Verified against live dev data

  • Project Sidewalk: NSF grant ($2,018,000), PI Jon Froehlich + past Co-PI David Jacobs + active research-scientist lead Mikey Saugstad, 13 publications, keywords, website.
  • ?author=jonfroehlich&page_size=5 → 108 pubs, newest-first.

Deferred (same pattern later): write endpoints, API keys, throttling, Talks/Posters/Videos resources.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y9JS9XZfD86FdDLJiJFSBk

Adds a versioned, read-only DRF API over already-public content so external
sites can treat this website as the source of truth instead of duplicating it.
Two consumers drove the design: Project Sidewalk (grants/people/leadership/
publications for a project) and Jon's academic page (a recent-pubs widget).

Endpoints (all GET-only, paginated with a tunable ?page_size, max 100):
- /publications/ filterable by ?project, ?author, ?year, ?type, ?ordering
  (default newest-first) -- serves both ?author=jonfroehlich&page_size=5 and
  ?project=<short_name>.
- /projects/ (visible only), /grants/, /people/ (members only), plus project
  sub-resources: /projects/<short_name>/{publications,grants,people,leadership}.
- leadership returns ALL lead roles for all time (current + past), grouped by
  type; it queries ProjectRole directly rather than reusing
  get_project_leadership() (which drops a person's past lead roles once they
  hold any active role).

Design decisions (from #1268 scoping): DRF was already a bundled-but-unused
dependency; public data so no auth and no throttle; cross-origin enabled on
/api/ only via a tiny in-repo ApiCorsMiddleware (no django-cors-headers);
absolute media/page URLs in every payload; Person.email deliberately not
exposed. Projects gated to is_visible=True; people scoped to actual members
(those with a Position).

Code in website/api/ (serializers/views/urls/middleware), mounted by the root
URLconf, configured by the REST_FRAMEWORK block in settings. Full reference in
docs/API.md; pointer in CLAUDE.md. 23 integration tests in
website/tests/test_api.py (full suite: 607 pass). Bumps version to 2.27.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y9JS9XZfD86FdDLJiJFSBk
@jonfroehlich
jonfroehlich merged commit 578d643 into master Jul 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create Makeability Lab REST API

1 participant