···77from litestar import Request, Router, get
88from litestar.enums import MediaType
991010-from app.atproto import get_cursor, store_cursor
1010+from app.atproto import ATProtoClient, SessionStore, get_cursor, store_cursor
1111from app.auth import optional_auth, require_auth
1212-from app.convert import convert_feed_item
1313-from app.state import client
1212+from app.convert import convert_feed_item, convert_status
141315141615# ── GET /api/v1/timelines/home ─────────────────────────────────────────────
···1918@get("/api/v1/timelines/home", media_type=MediaType.JSON)
2019async def home_timeline(
2120 request: Request,
2121+ sessions: SessionStore,
2222+ client: ATProtoClient,
2223 max_id: str | None = None,
2324 since_id: str | None = None,
2425 min_id: str | None = None,
2526 limit: int = 20,
2627) -> list[dict[str, Any]]:
2727- session = require_auth(request)
2828+ session = require_auth(request, sessions)
2829 params: dict[str, Any] = {"limit": min(limit, 50)}
29303031 # Try to map Mastodon pagination to Bluesky cursor
···5354@get("/api/v1/timelines/public", media_type=MediaType.JSON)
5455async def public_timeline(
5556 request: Request,
5757+ sessions: SessionStore,
5858+ client: ATProtoClient,
5659 local: bool = False,
5760 remote: bool = False,
5861 only_media: bool = False,
···6366) -> list[dict[str, Any]]:
6467 """Bluesky doesn't have a true public timeline, so we use the
6568 ``discover`` feed (What's Hot) as a reasonable substitute."""
6666- session = optional_auth(request)
6969+ session = optional_auth(request, sessions)
6770 params: dict[str, Any] = {"limit": min(limit, 50)}
68716972 if max_id:
···99102async def hashtag_timeline(
100103 request: Request,
101104 hashtag: str,
105105+ sessions: SessionStore,
106106+ client: ATProtoClient,
102107 max_id: str | None = None,
103108 limit: int = 20,
104109) -> list[dict[str, Any]]:
···107112 Note: searchPosts requires auth on the public API, so unauthenticated
108113 requests return an empty list.
109114 """
110110- session = optional_auth(request)
115115+ session = optional_auth(request, sessions)
111116 if not session:
112117 return [] # searchPosts requires auth
113118···120125121126 data = await client.get(session, "app.bsky.feed.searchPosts", **params)
122127123123- from app.convert import convert_status
124124-125128 statuses = []
126129 for post in data.get("posts", []):
127130 statuses.append(convert_status(post, session=session))
···153156@get("/api/v1/timelines/bubble", media_type=MediaType.JSON)
154157async def bubble_timeline(
155158 request: Request,
159159+ sessions: SessionStore,
160160+ client: ATProtoClient,
156161 max_id: str | None = None,
157162 since_id: str | None = None,
158163 min_id: str | None = None,
···163168164169 Since we're a Bluesky bridge, we treat this the same as the public timeline.
165170 """
166166- session = optional_auth(request)
171171+ session = optional_auth(request, sessions)
167172 params: dict[str, Any] = {"limit": min(limit, 50)}
168173169174 if max_id:
+10-68
app/state.py
···2424 client: ATProtoClient
252526262727-# Global instance for backward compatibility during migration
2727+# Global instance — lazily initialized on first access
2828_app_state: AppState | None = None
292930303131-def create_state() -> AppState:
3232- """Create and initialize the application state.
3333-3434- This function is called once during application startup.
3535- """
3131+def _get_or_create_state() -> AppState:
3232+ """Return the global AppState, creating it on first access."""
3633 global _app_state
3737- sessions = SessionStore()
3838- client = ATProtoClient(sessions)
3939- _app_state = AppState(sessions=sessions, client=client)
4040- return _app_state
4141-4242-4343-def provide_state() -> AppState:
4444- """Dependency provider for AppState.
4545-4646- Returns the global AppState instance.
4747- """
4834 if _app_state is None:
4949- return create_state()
3535+ sessions = SessionStore()
3636+ client = ATProtoClient(sessions)
3737+ _app_state = AppState(sessions=sessions, client=client)
5038 return _app_state
513952405341def provide_sessions() -> SessionStore:
5454- """Dependency provider for SessionStore.
5555-5656- Returns the session store from the global AppState.
5757- """
5858- return provide_state().sessions
4242+ """Dependency provider for SessionStore."""
4343+ return _get_or_create_state().sessions
594460456146def provide_client() -> ATProtoClient:
6262- """Dependency provider for ATProtoClient.
6363-6464- Returns the client from the global AppState.
6565- """
6666- return provide_state().client
4747+ """Dependency provider for ATProtoClient."""
4848+ return _get_or_create_state().client
674968506951# Dependency providers for Litestar DI
7070-# Note: "state" is a reserved keyword in Litestar, so we use "app_state" instead
7152dependencies = {
7272- "app_state": Provide(provide_state, sync_to_thread=False),
7353 "sessions": Provide(provide_sessions, sync_to_thread=False),
7454 "client": Provide(provide_client, sync_to_thread=False),
7555}
7676-7777-7878-# Backward compatibility: expose global singletons for legacy code
7979-# These will be removed once all routes are migrated to use DI
8080-def _get_sessions() -> SessionStore:
8181- """Get the global session store (backward compatibility)."""
8282- return provide_state().sessions
8383-8484-8585-def _get_client() -> ATProtoClient:
8686- """Get the global client (backward compatibility)."""
8787- return provide_state().client
8888-8989-9090-# Module-level singletons for backward compatibility
9191-# These are lazily initialized on first access
9292-class _SessionsProxy:
9393- """Proxy that delegates to the global SessionStore."""
9494-9595- def __getattr__(self, name):
9696- return getattr(provide_sessions(), name)
9797-9898- def __repr__(self) -> str:
9999- return repr(provide_sessions())
100100-101101-102102-class _ClientProxy:
103103- """Proxy that delegates to the global ATProtoClient."""
104104-105105- def __getattr__(self, name):
106106- return getattr(provide_client(), name)
107107-108108- def __repr__(self) -> str:
109109- return repr(provide_client())
110110-111111-112112-sessions = _SessionsProxy()
113113-client = _ClientProxy()