From d17ce92f815fa145fa56307a7e9b3993e0f4270d Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Wed, 9 Oct 2024 23:46:16 -0400 Subject: Return an instance of the client when opening a channel URL directly. --- src/channel/app.rs | 12 +++++++++++- src/db/mod.rs | 21 +++++++++++++++------ src/ui.rs | 34 ++++++++++++++++++++++++++-------- 3 files changed, 52 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/channel/app.rs b/src/channel/app.rs index cb7ad32..7c0b107 100644 --- a/src/channel/app.rs +++ b/src/channel/app.rs @@ -2,7 +2,7 @@ use chrono::TimeDelta; use itertools::Itertools; use sqlx::sqlite::SqlitePool; -use super::{repo::Provider as _, Channel, Id}; +use super::{repo::Provider as _, Channel, History, Id}; use crate::{ clock::DateTime, db::NotFound, @@ -36,6 +36,16 @@ impl<'a> Channels<'a> { Ok(channel.as_created()) } + // This function is careless with respect to time, and gets you the channel as + // it exists in the specific moment when you call it. + pub async fn get(&self, channel: &Id) -> Result, sqlx::Error> { + let mut tx = self.db.begin().await?; + let channel = tx.channels().by_id(channel).await.optional()?; + tx.commit().await?; + + Ok(channel.iter().flat_map(History::events).collect()) + } + pub async fn delete(&self, channel: &Id, deleted_at: &DateTime) -> Result<(), Error> { let mut tx = self.db.begin().await?; diff --git a/src/db/mod.rs b/src/db/mod.rs index b9c59ef..36d888f 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -97,24 +97,33 @@ pub enum Error { pub trait NotFound { type Ok; + type Error; + fn not_found(self, map: F) -> Result where - E: From, + E: From, F: FnOnce() -> E; + + fn optional(self) -> Result, Self::Error>; } impl NotFound for Result { type Ok = T; + type Error = sqlx::Error; + + fn optional(self) -> Result, sqlx::Error> { + match self { + Ok(value) => Ok(Some(value)), + Err(sqlx::Error::RowNotFound) => Ok(None), + Err(other) => Err(other), + } + } fn not_found(self, map: F) -> Result where E: From, F: FnOnce() -> E, { - match self { - Err(sqlx::Error::RowNotFound) => Err(map()), - Err(other) => Err(other.into()), - Ok(value) => Ok(value), - } + self.optional()?.ok_or_else(map) } } diff --git a/src/ui.rs b/src/ui.rs index 18dd049..9654195 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,24 +1,24 @@ use axum::{ - extract::Path, + extract::{Path, State}, http::{header, StatusCode}, - response::{IntoResponse, Response}, + response::{IntoResponse, Redirect, Response}, routing::get, Router, }; use mime_guess::Mime; use rust_embed::EmbeddedFile; +use crate::{app::App, channel, error::Internal, login::Login}; + #[derive(rust_embed::Embed)] #[folder = "hi-ui/build"] struct Assets; -pub fn router() -> Router -where - S: Clone + Send + Sync + 'static, -{ +pub fn router() -> Router { Router::new() .route("/*path", get(asset)) .route("/", get(root)) + .route("/ch/:channel", get(channel)) } async fn asset(Path(path): Path) -> Result> { @@ -29,8 +29,24 @@ async fn asset(Path(path): Path) -> Result> { .ok_or(NotFound(format!("not found: {path}"))) } -async fn root() -> impl IntoResponse { - asset(Path(String::from("index.html"))).await +async fn root() -> Result { + // "not found" in this case really is an internal error, as it should + // never happen. `index.html` is a known-valid path. + Ok(asset(Path(String::from("index.html"))).await?) +} + +async fn channel( + State(app): State, + login: Option, + Path(channel): Path, +) -> Result { + Ok(if login.is_none() { + Redirect::temporary("/").into_response() + } else if app.channels().get(&channel).await?.is_none() { + NotFound(root().await?).into_response() + } else { + root().await?.into_response() + }) } struct Asset(Mime, EmbeddedFile); @@ -47,6 +63,8 @@ impl IntoResponse for Asset { } } +#[derive(Debug, thiserror::Error)] +#[error("{0}")] struct NotFound(pub E); impl IntoResponse for NotFound -- cgit v1.2.3