chore: initialiser le projet imagod-web (Rust)
- Structure du projet Cargo avec dépendances (clap, axum, reqwest, rusqlite, arboard, tray-icon) - CLI avec sous-commandes start, serve, stop, status, config, install, uninstall, logs - Module config: parsing TOML, chemins Windows/Linux - Module dedup: dédoublonnage SHA-256 avec cache SQLite - Module imago_client: client HTTP pour API Imago (upload, search, get) - Module watcher: boucle de polling stub - Module server: serveur web axum avec routes API REST stub - Platform-specific: clipboard, tray, service, window_info (Windows) - Frontend placeholder HTML - Config par défaut (imagod.toml) - README.md, LICENSE (MIT), .gitignore
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
//! Client HTTP pour l'API Imago
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use reqwest::{Client, multipart};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UploadResponse {
|
||||
pub id: String,
|
||||
pub filename: String,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ImageInfo {
|
||||
pub id: String,
|
||||
pub filename: String,
|
||||
pub tags: Vec<String>,
|
||||
pub description: Option<String>,
|
||||
pub ocr_text: Option<String>,
|
||||
pub created_at: String,
|
||||
pub width: Option<u32>,
|
||||
pub height: Option<u32>,
|
||||
pub size_bytes: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SearchResponse {
|
||||
pub results: Vec<ImageInfo>,
|
||||
pub total: usize,
|
||||
}
|
||||
|
||||
/// Client pour l'API Imago
|
||||
pub struct ImagoClient {
|
||||
client: Client,
|
||||
base_url: String,
|
||||
api_key: String,
|
||||
}
|
||||
|
||||
impl ImagoClient {
|
||||
pub fn new(base_url: String, api_key: String) -> Result<Self> {
|
||||
let client = Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.build()?;
|
||||
Ok(Self { client, base_url, api_key })
|
||||
}
|
||||
|
||||
/// Upload une image vers Imago
|
||||
pub async fn upload_image(
|
||||
&self,
|
||||
data: Vec<u8>,
|
||||
filename: &str,
|
||||
window_title: Option<&str>,
|
||||
) -> Result<UploadResponse> {
|
||||
let part = multipart::Part::bytes(data)
|
||||
.file_name(filename.to_string())
|
||||
.mime_str("image/png")?;
|
||||
|
||||
let mut form = multipart::Form::new()
|
||||
.part("file", part);
|
||||
|
||||
if let Some(title) = window_title {
|
||||
form = form.text("window_title", title.to_string());
|
||||
}
|
||||
|
||||
let response = self.client
|
||||
.post(format!("{}/images/upload", self.base_url))
|
||||
.header("Authorization", format!("Bearer {}", self.api_key))
|
||||
.multipart(form)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let upload: UploadResponse = response
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
Ok(upload)
|
||||
}
|
||||
|
||||
/// Recherche des images
|
||||
pub async fn search(&self, query: &str, limit: usize) -> Result<SearchResponse> {
|
||||
let response = self.client
|
||||
.get(format!("{}/images/search", self.base_url))
|
||||
.header("Authorization", format!("Bearer {}", self.api_key))
|
||||
.query(&[("q", query), ("limit", &limit.to_string())])
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let result: SearchResponse = response
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Récupère les infos d'une image
|
||||
pub async fn get_image(&self, id: &str) -> Result<ImageInfo> {
|
||||
let response = self.client
|
||||
.get(format!("{}/images/{}", self.base_url, id))
|
||||
.header("Authorization", format!("Bearer {}", self.api_key))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let info: ImageInfo = response
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user