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:
2026-06-22 16:44:47 -04:00
commit a3b853de04
19 changed files with 923 additions and 0 deletions
+113
View File
@@ -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)
}
}