Better video ID handling + Instance::new improvements

This commit is contained in:
roaming97 2025-04-13 13:21:07 -06:00
parent 3fc3eb8d68
commit 461ccaa4b9
6 changed files with 32 additions and 34 deletions

2
Cargo.lock generated
View File

@ -33,7 +33,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "almond" name = "almond-api"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"axum", "axum",

View File

@ -1,5 +1,5 @@
[package] [package]
name = "almond" name = "almond-api"
version = "0.2.0" version = "0.2.0"
edition = "2024" edition = "2024"

View File

@ -4,13 +4,11 @@ use serde::{Deserialize, Serialize};
use sqlx::SqlitePool; use sqlx::SqlitePool;
use thiserror::Error; use thiserror::Error;
use crate::video::Video;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config { pub struct Config {
pub host: String, pub host: String,
pub port: u16, pub port: u16,
pub password: Option<String>, pub password: String,
pub videos_per_page: usize, pub videos_per_page: usize,
} }
@ -19,7 +17,7 @@ impl Default for Config {
Self { Self {
host: "0.0.0.0".into(), host: "0.0.0.0".into(),
port: 3000, port: 3000,
password: None, password: "123456".into(),
videos_per_page: 10, videos_per_page: 10,
} }
} }
@ -36,10 +34,10 @@ pub struct Instance {
pub enum NewInstanceError { pub enum NewInstanceError {
#[error("Failed to open TOML configuration file")] #[error("Failed to open TOML configuration file")]
ConfigLoad(#[from] io::Error), ConfigLoad(#[from] io::Error),
#[error("Could not parse TOML configuration: {0}")] #[error("Could not parse TOML configuration")]
ConfigParse(#[from] toml::de::Error), ConfigParse(#[from] toml::de::Error),
#[error("Failed to fetch entries from database: {0}")] #[error("Failed to create connection pool")]
Populate(#[from] sqlx::Error), PoolConnect(#[from] sqlx::Error),
} }
impl Instance { impl Instance {
@ -52,10 +50,4 @@ impl Instance {
Ok(Self { config, pool }) Ok(Self { config, pool })
} }
pub async fn fetch_videos(&self) -> Result<Vec<Video>, sqlx::Error> {
sqlx::query_as!(Video, "SELECT * FROM video")
.fetch_all(&self.pool)
.await
}
} }

View File

@ -3,7 +3,7 @@ use axum::{
routing::{get, post}, routing::{get, post},
}; };
use instance::Instance; use instance::Instance;
use routes::{list_entries, upload_video}; use routes::{list_videos, upload_video};
use tokio::signal; use tokio::signal;
use tracing::info; use tracing::info;
@ -30,7 +30,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let address = format!("{}:{}", instance.config.host, instance.config.port); let address = format!("{}:{}", instance.config.host, instance.config.port);
let almond = Router::new() let almond = Router::new()
.route("/", get(list_entries)) .route("/", get(list_videos))
.route("/upload", post(upload_video)) .route("/upload", post(upload_video))
.with_state(instance); .with_state(instance);

View File

@ -1,5 +1,5 @@
use axum::{ use axum::{
Json, debug_handler, Json,
extract::{Query, State}, extract::{Query, State},
http::StatusCode, http::StatusCode,
}; };
@ -12,12 +12,12 @@ use crate::{
}; };
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct ListEntriesQuery { pub struct ListVideosQuery {
page: Option<usize>, page: Option<usize>,
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct ListEntriesResponse { pub struct ListVideosResponse {
videos: Vec<Video>, videos: Vec<Video>,
page: usize, page: usize,
per_page: usize, per_page: usize,
@ -26,12 +26,14 @@ pub struct ListEntriesResponse {
} }
/// Retrieve video list as JSON (paged) /// Retrieve video list as JSON (paged)
#[debug_handler] pub async fn list_videos(
pub async fn list_entries(
State(state): State<Instance>, State(state): State<Instance>,
Query(query): Query<ListEntriesQuery>, Query(query): Query<ListVideosQuery>,
) -> Result<Json<ListEntriesResponse>, StatusCode> { ) -> Result<Json<ListVideosResponse>, StatusCode> {
let Ok(videos) = state.fetch_videos().await else { let Ok(videos) = sqlx::query_as!(Video, "SELECT * FROM video")
.fetch_all(&state.pool)
.await
else {
error!("Could not fetch videos from database!"); error!("Could not fetch videos from database!");
return Err(StatusCode::INTERNAL_SERVER_ERROR); return Err(StatusCode::INTERNAL_SERVER_ERROR);
}; };
@ -50,7 +52,7 @@ pub async fn list_entries(
vec![] vec![]
}; };
Ok(Json(ListEntriesResponse { Ok(Json(ListVideosResponse {
videos, videos,
page, page,
per_page, per_page,
@ -70,12 +72,18 @@ pub async fn upload_video(
State(state): State<Instance>, State(state): State<Instance>,
Query(query): Query<UploadVideoQuery>, Query(query): Query<UploadVideoQuery>,
) -> StatusCode { ) -> StatusCode {
let Ok(videos) = state.fetch_videos().await else { let id = match sqlx::query_scalar!("SELECT MAX(id) FROM video")
error!("Could not fetch videos from database!"); .fetch_one(&state.pool)
return StatusCode::INTERNAL_SERVER_ERROR; .await
{
Ok(Some(max_id)) => max_id + 1,
Ok(None) => 0,
Err(_) => {
return StatusCode::INTERNAL_SERVER_ERROR;
}
}; };
let new_video = Video::from_url(&query.url, videos.len(), query.cookie.as_deref()) let new_video = Video::from_url(&query.url, id, query.cookie.as_deref())
.await .await
.map_err(|e| match e { .map_err(|e| match e {
VideoError::InvalidUrl | VideoError::UrlParse(_) => StatusCode::BAD_REQUEST, VideoError::InvalidUrl | VideoError::UrlParse(_) => StatusCode::BAD_REQUEST,

View File

@ -95,8 +95,7 @@ impl Video {
Some(query_v.1.to_string()) Some(query_v.1.to_string())
} }
#[allow(clippy::cast_possible_wrap)] pub async fn from_url(url: &str, id: i64, cookie: Option<&str>) -> Result<Self, VideoError> {
pub async fn from_url(url: &str, id: usize, cookie: Option<&str>) -> Result<Self, VideoError> {
let url = Url::parse(url)?; let url = Url::parse(url)?;
info!("Parsed argument as URL"); info!("Parsed argument as URL");
@ -141,8 +140,6 @@ impl Video {
.unwrap() .unwrap()
}; };
let id = id as i64;
let url = url.to_string(); let url = url.to_string();
let description = format!("{file_stem}.description"); let description = format!("{file_stem}.description");
let title = get_info_value("title").to_string(); let title = get_info_value("title").to_string();
@ -175,6 +172,7 @@ impl Video {
}; };
let sha256 = format!("{:x}", Sha3_256::digest(&buffer)); let sha256 = format!("{:x}", Sha3_256::digest(&buffer));
#[allow(clippy::cast_possible_wrap)]
let file_size = buffer.len() as i64; let file_size = buffer.len() as i64;
let author_id = get_info_value("channel_id").to_string(); let author_id = get_info_value("channel_id").to_string();
let author_url = get_info_value("channel_url").to_string(); let author_url = get_info_value("channel_url").to_string();