Skip to content
Open
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
16 changes: 15 additions & 1 deletion app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from fastapi import FastAPI, HTTPException, Query

from app.models import ReportListResponse, ReportPublic, ReportStatus
from app.reports import query
from app.reports import query, get_report_by_id

app = FastAPI(title="SDD Workshop — Reports API", version="0.1.0")

Expand Down Expand Up @@ -51,3 +51,17 @@ def list_reports(
offset=offset,
limit=limit,
)

@app.get("/reports/{id}", response_model=ReportPublic)
def get_report(id: int) -> ReportPublic:
"""Return one report by ID."""

report = get_report_by_id(id)

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

return ReportPublic.from_internal(report)
11 changes: 11 additions & 0 deletions app/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,14 @@ def query(
rows = (r for r in rows if r.created_at <= date_to)

return sorted(rows, key=lambda r: getattr(r, sort), reverse=descending)

def get_report_by_id(report_id: int) -> Report | None:
"""Return one report by ID or None if missing."""

rows = all_reports()

for report in rows:
if report.id == report_id:
return report
Comment on lines +43 to +50

return None
24 changes: 24 additions & 0 deletions openspec/changes/get-report-by-id/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Proposal

## Motivation

Clients currently fetch all reports and filter client-side to find one record.

Direct lookup reduces payload and improves usability.

## Changes

ADD:

GET /reports/{id}

Behavior:

- Return report if found
- Return 404 if missing

## Non-goals

- Existing routes unchanged
- No authentication changes
- No changes to PublicReport model
29 changes: 29 additions & 0 deletions openspec/changes/get-report-by-id/specs/reports/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Spec Delta

## ADD

### Endpoint

GET /reports/{id}

### Parameters

id:
- type: integer
- required: true

### Responses

200:
Return existing public report model

404:
{
"detail": "Report not found"
}

### Constraints

- Existing `/reports` endpoint behavior remains unchanged
- Existing pagination behavior remains unchanged
- Reuse existing response model
7 changes: 7 additions & 0 deletions openspec/changes/get-report-by-id/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Tasks

- [x] Add helper function in app/reports.py
- [x] Add route in app/main.py
- [x] Add success test
- [x] Add 404 test
- [x] Verify all tests pass
18 changes: 18 additions & 0 deletions openspec/project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Project Context

## Tech Stack
- Python
- FastAPI
- Pydantic
- In-memory dataset

## Conventions
- All routes return JSON
- Existing routes must remain unchanged
- Public models never expose internal fields
- Existing API behavior preserved

## Constraints
- Preserve `/reports` contract
- Preserve pagination behavior
- No breaking changes to existing endpoints
38 changes: 38 additions & 0 deletions tests/test_reports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from fastapi.testclient import TestClient

from app.main import app


client = TestClient(app)


def test_get_report_found():

reports = client.get("/reports").json()["items"]

existing_id = reports[0]["id"]

response = client.get(f"/reports/{existing_id}")

assert response.status_code == 200

data = response.json()

assert data["id"] == existing_id

assert "internal_id" not in data

assert "owner_email" not in data


def test_get_report_missing():

reports = client.get("/reports").json()["items"]

missing_id = max(r["id"] for r in reports) + 1

response = client.get(f"/reports/{missing_id}")

assert response.status_code == 404

assert response.json()["detail"] == "Report not found"