remdms Claude Opus 4.6 commited on
Commit
de2956c
·
1 Parent(s): 61a9b93

feat: /api/stories endpoint for landing mosaic

Browse files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

src/mediastorm/api.py CHANGED
@@ -1,8 +1,62 @@
 
 
 
 
 
1
  from fastapi import FastAPI
2
 
3
- app = FastAPI(title="MediaStorm Archive Explorer")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
 
6
  @app.get("/api/health")
7
  async def health():
8
  return {"status": "ok"}
 
 
 
 
 
 
1
+ import json
2
+ from contextlib import asynccontextmanager
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
  from fastapi import FastAPI
7
 
8
+ from mediastorm.config import INGEST_CACHE_DIR
9
+
10
+ _landing_stories: list[dict[str, Any]] = []
11
+
12
+
13
+ def _load_stories_for_landing() -> list[dict[str, Any]]:
14
+ stories = []
15
+ cache_dir = Path(INGEST_CACHE_DIR)
16
+ if not cache_dir.exists():
17
+ return stories
18
+ for json_file in sorted(cache_dir.glob("*.json")):
19
+ try:
20
+ data = json.loads(json_file.read_text())
21
+ except (json.JSONDecodeError, OSError):
22
+ continue
23
+ name = data.get("name") or ""
24
+ slug = data.get("slug") or ""
25
+ if not name or not slug:
26
+ continue
27
+ poster_images = data.get("poster_images") or []
28
+ poster = ""
29
+ if poster_images:
30
+ last = poster_images[-1]
31
+ poster = f"https://www.mediastorm.com{last}" if last.startswith("/") else last
32
+ awards = data.get("awards") or []
33
+ stories.append({
34
+ "uid": data.get("uid", ""),
35
+ "name": name,
36
+ "slug": slug,
37
+ "poster": poster,
38
+ "year": data.get("published_year"),
39
+ "media_type": data.get("content_type", ""),
40
+ "awards_count": len(awards),
41
+ })
42
+ return stories
43
+
44
+
45
+ @asynccontextmanager
46
+ async def lifespan(app: FastAPI):
47
+ global _landing_stories
48
+ _landing_stories = _load_stories_for_landing()
49
+ yield
50
+
51
+
52
+ app = FastAPI(title="MediaStorm Archive Explorer", lifespan=lifespan)
53
 
54
 
55
  @app.get("/api/health")
56
  async def health():
57
  return {"status": "ok"}
58
+
59
+
60
+ @app.get("/api/stories")
61
+ async def stories():
62
+ return {"stories": _landing_stories}
src/mediastorm/config.py CHANGED
@@ -21,3 +21,5 @@ GEMINI_FLASH_MODEL = "gemini-2.5-flash"
21
  GEMINI_PRO_MODEL = "gemini-2.5-pro"
22
 
23
  BM25_INDEX_PATH = Path(os.environ.get("BM25_INDEX_PATH", "./data/bm25_index.pkl"))
 
 
 
21
  GEMINI_PRO_MODEL = "gemini-2.5-pro"
22
 
23
  BM25_INDEX_PATH = Path(os.environ.get("BM25_INDEX_PATH", "./data/bm25_index.pkl"))
24
+
25
+ INGEST_CACHE_DIR = Path(os.environ.get("INGEST_CACHE_DIR", "./data/ingest_cache"))
tests/test_api.py CHANGED
@@ -12,3 +12,20 @@ async def test_health():
12
  data = resp.json()
13
  assert "status" in data
14
  assert data["status"] == "ok"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  data = resp.json()
13
  assert "status" in data
14
  assert data["status"] == "ok"
15
+
16
+
17
+ @pytest.mark.asyncio
18
+ async def test_stories_returns_list():
19
+ transport = ASGITransport(app=app)
20
+ async with AsyncClient(transport=transport, base_url="http://test") as client:
21
+ resp = await client.get("/api/stories")
22
+ assert resp.status_code == 200
23
+ data = resp.json()
24
+ assert "stories" in data
25
+ assert isinstance(data["stories"], list)
26
+ if data["stories"]:
27
+ story = data["stories"][0]
28
+ assert "uid" in story
29
+ assert "name" in story
30
+ assert "slug" in story
31
+ assert "poster" in story