pinakes-tui: cover more API routes in the TUI crate
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Id14b6f82d3b9f3c27bee9c214a1bdedc6a6a6964
This commit is contained in:
parent
c18edc9146
commit
8129c5a6e7
8 changed files with 1866 additions and 67 deletions
|
|
@ -186,6 +186,62 @@ pub struct AuthorSummary {
|
|||
pub count: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PlaylistResponse {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CommentResponse {
|
||||
pub text: String,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct TranscodeSessionResponse {
|
||||
pub id: String,
|
||||
pub profile: String,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct SubtitleEntry {
|
||||
pub language: Option<String>,
|
||||
pub format: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct SubtitleListResponse {
|
||||
pub subtitles: Vec<SubtitleEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct DeviceResponse {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub device_type: Option<String>,
|
||||
pub last_seen: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct WebhookInfo {
|
||||
#[serde(default)]
|
||||
pub id: String,
|
||||
pub url: String,
|
||||
pub events: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct UserResponse {
|
||||
pub id: String,
|
||||
pub username: String,
|
||||
pub role: String,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
impl ApiClient {
|
||||
pub fn new(base_url: &str, api_key: Option<&str>) -> Self {
|
||||
let client = api_key.map_or_else(Client::new, |key| {
|
||||
|
|
@ -198,7 +254,13 @@ impl ApiClient {
|
|||
Client::builder()
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.unwrap_or_default()
|
||||
.unwrap_or_else(|e| {
|
||||
tracing::warn!(
|
||||
"failed to build authenticated HTTP client: {e}; falling back to \
|
||||
unauthenticated client"
|
||||
);
|
||||
Client::new()
|
||||
})
|
||||
});
|
||||
Self {
|
||||
client,
|
||||
|
|
@ -627,4 +689,303 @@ impl ApiClient {
|
|||
.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_playlists(&self) -> Result<Vec<PlaylistResponse>> {
|
||||
let resp = self
|
||||
.client
|
||||
.get(self.url("/playlists"))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn create_playlist(
|
||||
&self,
|
||||
name: &str,
|
||||
description: Option<&str>,
|
||||
) -> Result<PlaylistResponse> {
|
||||
let mut body = serde_json::json!({"name": name});
|
||||
if let Some(desc) = description {
|
||||
body["description"] = serde_json::Value::String(desc.to_string());
|
||||
}
|
||||
let resp = self
|
||||
.client
|
||||
.post(self.url("/playlists"))
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn delete_playlist(&self, id: &str) -> Result<()> {
|
||||
self
|
||||
.client
|
||||
.delete(self.url(&format!("/playlists/{id}")))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_playlist_items(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<Vec<MediaResponse>> {
|
||||
let resp = self
|
||||
.client
|
||||
.get(self.url(&format!("/playlists/{id}/items")))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn remove_from_playlist(
|
||||
&self,
|
||||
playlist_id: &str,
|
||||
media_id: &str,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.client
|
||||
.delete(self.url(&format!("/playlists/{playlist_id}/items/{media_id}")))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn shuffle_playlist(&self, id: &str) -> Result<()> {
|
||||
self
|
||||
.client
|
||||
.post(self.url(&format!("/playlists/{id}/shuffle")))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn rate_media(&self, media_id: &str, stars: u8) -> Result<()> {
|
||||
self
|
||||
.client
|
||||
.post(self.url(&format!("/media/{media_id}/ratings")))
|
||||
.json(&serde_json::json!({"stars": stars}))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_comment(
|
||||
&self,
|
||||
media_id: &str,
|
||||
text: &str,
|
||||
) -> Result<CommentResponse> {
|
||||
let resp = self
|
||||
.client
|
||||
.post(self.url(&format!("/media/{media_id}/comments")))
|
||||
.json(&serde_json::json!({"text": text}))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn list_comments(
|
||||
&self,
|
||||
media_id: &str,
|
||||
) -> Result<Vec<CommentResponse>> {
|
||||
let resp = self
|
||||
.client
|
||||
.get(self.url(&format!("/media/{media_id}/comments")))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn toggle_favorite(&self, media_id: &str) -> Result<()> {
|
||||
// Try POST to add; if it fails with conflict, DELETE to remove
|
||||
let post_resp = self
|
||||
.client
|
||||
.post(self.url("/favorites"))
|
||||
.json(&serde_json::json!({"media_id": media_id}))
|
||||
.send()
|
||||
.await?;
|
||||
if post_resp.status() == reqwest::StatusCode::CONFLICT
|
||||
|| post_resp.status() == reqwest::StatusCode::UNPROCESSABLE_ENTITY
|
||||
{
|
||||
// Already a favorite: remove it
|
||||
self
|
||||
.client
|
||||
.delete(self.url(&format!("/favorites/{media_id}")))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
} else {
|
||||
post_resp.error_for_status()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn enrich_media(&self, media_id: &str) -> Result<()> {
|
||||
self
|
||||
.client
|
||||
.post(self.url(&format!("/media/{media_id}/enrich")))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start_transcode(
|
||||
&self,
|
||||
media_id: &str,
|
||||
profile: &str,
|
||||
) -> Result<TranscodeSessionResponse> {
|
||||
let resp = self
|
||||
.client
|
||||
.post(self.url(&format!("/media/{media_id}/transcode")))
|
||||
.json(&serde_json::json!({"profile": profile}))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn list_transcodes(&self) -> Result<Vec<TranscodeSessionResponse>> {
|
||||
let resp = self
|
||||
.client
|
||||
.get(self.url("/transcode"))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn cancel_transcode(&self, id: &str) -> Result<()> {
|
||||
self
|
||||
.client
|
||||
.delete(self.url(&format!("/transcode/{id}")))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_subtitles(
|
||||
&self,
|
||||
media_id: &str,
|
||||
) -> Result<SubtitleListResponse> {
|
||||
let resp = self
|
||||
.client
|
||||
.get(self.url(&format!("/media/{media_id}/subtitles")))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn list_sync_devices(&self) -> Result<Vec<DeviceResponse>> {
|
||||
let resp = self
|
||||
.client
|
||||
.get(self.url("/sync/devices"))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn delete_sync_device(&self, id: &str) -> Result<()> {
|
||||
self
|
||||
.client
|
||||
.delete(self.url(&format!("/sync/devices/{id}")))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_webhooks(&self) -> Result<Vec<WebhookInfo>> {
|
||||
let resp = self
|
||||
.client
|
||||
.get(self.url("/webhooks"))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn test_webhook(&self, id: &str) -> Result<()> {
|
||||
self
|
||||
.client
|
||||
.post(self.url("/webhooks/test"))
|
||||
.json(&serde_json::json!({"id": id}))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_users(&self) -> Result<Vec<UserResponse>> {
|
||||
let resp = self
|
||||
.client
|
||||
.get(self.url("/users"))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn delete_user(&self, id: &str) -> Result<()> {
|
||||
self
|
||||
.client
|
||||
.delete(self.url(&format!("/users/{id}")))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_to_playlist(
|
||||
&self,
|
||||
playlist_id: &str,
|
||||
media_id: &str,
|
||||
) -> Result<()> {
|
||||
let body = serde_json::json!({"media_id": media_id});
|
||||
let resp = self
|
||||
.client
|
||||
.post(self.url(&format!("/playlists/{playlist_id}/items")))
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?;
|
||||
if resp.status().is_success() {
|
||||
Ok(())
|
||||
} else {
|
||||
anyhow::bail!("add to playlist failed: {}", resp.status())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue