Add the Read in BookOrbit button and /issues/{date}/read redirect

The signed-in issue page shows a Read in BookOrbit button (only when the
integration is active and the Standard EPUB exists) that hits
/issues/{date}/read. The route redirects to the cached BookOrbit reader
URL, or resolves the ids through OPDS on first click and caches them;
?refresh=1 re-resolves. Not indexed yet is a 503, upstream failures 502.
Includes the implementation plan.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Va5eMEmWEnjMXBsBob5FDW
This commit is contained in:
2026-09-05 05:20:34 +00:00
co-authored by Claude Fable 5.1
parent e370fcaeb2
commit 9aff78ebf1
5 changed files with 453 additions and 17 deletions
+15
View File
@@ -456,6 +456,12 @@ pub enum WebError {
Unauthenticated { next: String },
#[error("bad request: {0}")]
BadRequest(String),
/// BookOrbit has not indexed the requested issue yet.
#[error("service unavailable: {0}")]
ServiceUnavailable(String),
/// A BookOrbit OPDS request failed.
#[error("bad gateway: {0}")]
BadGateway(String),
#[error("request origin did not match this site")]
Csrf,
#[error(transparent)]
@@ -511,6 +517,14 @@ impl IntoResponse for WebError {
Self::BadRequest(ref message) => {
(StatusCode::BAD_REQUEST, "Bad request", message.as_str())
}
Self::ServiceUnavailable(ref message) => (
StatusCode::SERVICE_UNAVAILABLE,
"Not indexed yet",
message.as_str(),
),
Self::BadGateway(ref message) => {
(StatusCode::BAD_GATEWAY, "BookOrbit error", message.as_str())
}
Self::Db(ref error) => {
tracing::error!(%error, "web database request failed");
(
@@ -660,6 +674,7 @@ pub fn router(config: &crate::config::Config) -> axum::Router<crate::server::App
.route("/issues/{date}/articles/{article_id}", get(issue::article))
.route("/issues/{date}/world", get(issue::world))
.route("/issues/{date}/behind", get(issue::behind))
.route("/issues/{date}/read", get(issue::read))
.route_layer(login_required!(
session::Backend,
login_url = "/login",