//! `space.polymodel.library.*` read endpoint handlers. //! //! All backed by the SQLite projection via [`super::views`]. `getThing`/`getModel` //! hydrate ordered children via the junction tables; feeds/search return a //! `feedView` of `thingViewBasic`. use axum::extract::State; use jacquard::identity::PublicResolver; use jacquard_axum::oauth::OptionalBrowserOAuthSession; use jacquard_axum::{ExtractXrpc, XrpcResponse}; use polymodel_api::space_polymodel::library::{ get_author_things::{GetAuthorThingsOutput, GetAuthorThingsRequest}, get_feed::{GetFeedOutput, GetFeedRequest}, get_model::{GetModelOutput, GetModelRequest}, get_thing::{GetThingOutput, GetThingRequest}, search_things::{SearchThingsOutput, SearchThingsRequest}, }; use super::error::{AppResult, clamp_limit, invalid_request}; use super::state::AppState; use super::views; use super::views::viewer_did; use crate::oauth::SqliteAuthStore; pub(super) async fn get_thing( State(state): State, OptionalBrowserOAuthSession(session): OptionalBrowserOAuthSession< PublicResolver, SqliteAuthStore, >, ExtractXrpc(req): ExtractXrpc, ) -> AppResult> { let viewer_owned = viewer_did(session.as_ref()).await; let viewer = viewer_owned.as_ref().map(|d| d.borrow()); let (thing, models) = views::get_thing(&state, req.uri.as_ref(), viewer).await?; Ok(XrpcResponse(GetThingOutput { thing, models: Some(models), extra_data: None, })) } pub(super) async fn get_model( State(state): State, OptionalBrowserOAuthSession(session): OptionalBrowserOAuthSession< PublicResolver, SqliteAuthStore, >, ExtractXrpc(req): ExtractXrpc, ) -> AppResult> { let viewer_owned = viewer_did(session.as_ref()).await; let viewer = viewer_owned.as_ref().map(|d| d.borrow()); let detail = views::get_model(&state, req.uri.as_ref(), viewer).await?; Ok(XrpcResponse(GetModelOutput { model: detail.model, parts: Some(detail.parts), thing: detail.thing, extra_data: None, })) } pub(super) async fn get_author_things( State(state): State, OptionalBrowserOAuthSession(session): OptionalBrowserOAuthSession< PublicResolver, SqliteAuthStore, >, ExtractXrpc(req): ExtractXrpc, ) -> AppResult> { let viewer_owned = viewer_did(session.as_ref()).await; let viewer = viewer_owned.as_ref().map(|d| d.borrow()); let did = super::actor::resolve_actor(&state, &req.actor).await?; let feed = views::author_things( &state, did.as_ref(), clamp_limit(req.limit), req.cursor.as_deref(), viewer, ) .await?; Ok(XrpcResponse(GetAuthorThingsOutput { value: feed, extra_data: None, })) } pub(super) async fn get_feed( State(state): State, OptionalBrowserOAuthSession(session): OptionalBrowserOAuthSession< PublicResolver, SqliteAuthStore, >, ExtractXrpc(req): ExtractXrpc, ) -> AppResult> { let viewer_owned = viewer_did(session.as_ref()).await; let viewer = viewer_owned.as_ref().map(|d| d.borrow()); let algorithm = req.algorithm.as_deref().unwrap_or("recent"); let limit = clamp_limit(req.limit); let cursor = req.cursor.as_deref(); let feed = match algorithm { "recent" => views::feed_recent(&state, limit, cursor, viewer).await?, "hot" => views::feed_hot(&state, limit, cursor, viewer).await?, "following" => match viewer { Some(viewer) => views::feed_following(&state, viewer, limit, cursor).await?, None => views::empty_feed(), }, other => return Err(invalid_request(format!("unknown algorithm: {other}"))), }; Ok(XrpcResponse(GetFeedOutput { value: feed, extra_data: None, })) } pub(super) async fn search_things( State(state): State, OptionalBrowserOAuthSession(session): OptionalBrowserOAuthSession< PublicResolver, SqliteAuthStore, >, ExtractXrpc(req): ExtractXrpc, ) -> AppResult> { let viewer_owned = viewer_did(session.as_ref()).await; let viewer = viewer_owned.as_ref().map(|d| d.borrow()); let feed = views::search_things( &state, req.q.as_str(), clamp_limit(req.limit), req.cursor.as_deref(), viewer, ) .await?; Ok(XrpcResponse(SearchThingsOutput { value: feed, extra_data: None, })) }