Fix examples and SDK for Immich v2 API compatibility
Some checks failed
Integration Tests / integration-test (push) Failing after 6s
Some checks failed
Integration Tests / integration-test (push) Failing after 6s
- Fix AssetUploadStatus serialization to use snake_case (created/duplicate/rejected) - Fix AssetOrder serialization to use lowercase (asc/desc) - Add file_created_at/file_modified_at to UploadAssetBuilder - Fix AddAssetsBuilder response type to Vec<Value> instead of AlbumResponse - Fix ServerAbout model - version is String not ServerVersion struct - Update upload_photos.rs to handle duplicate responses correctly - Update server_info.rs to display new ServerAbout fields - Update AGENTS.md with new example list
This commit is contained in:
@@ -30,6 +30,11 @@ All examples should be run using the provided wrapper script:
|
||||
./scripts/run-example.sh upload_photos
|
||||
./scripts/run-example.sh download_asset
|
||||
./scripts/run-example.sh thumbnail
|
||||
./scripts/run-example.sh search_metadata
|
||||
./scripts/run-example.sh album_management
|
||||
./scripts/run-example.sh timeline_browsing
|
||||
./scripts/run-example.sh delete_assets
|
||||
./scripts/run-example.sh server_info
|
||||
```
|
||||
|
||||
The wrapper handles everything automatically:
|
||||
|
||||
@@ -38,6 +38,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let about = client.server().about().await?;
|
||||
println!(" Version: {}", about.version);
|
||||
println!(" Version URL: {}", about.version_url);
|
||||
println!(" Licensed: {}", about.licensed);
|
||||
if let Some(ref build) = about.build {
|
||||
println!(" Build: {}", build);
|
||||
}
|
||||
if let Some(ref repository) = about.repository {
|
||||
println!(" Repository: {}", repository);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
// Create an album and add the uploaded asset
|
||||
// Note: For duplicates, the ID is returned even though status is duplicate
|
||||
if let Some(asset_id) = result.id {
|
||||
let album = client
|
||||
.albums()
|
||||
@@ -68,7 +69,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
client
|
||||
let _add_result = client
|
||||
.albums()
|
||||
.add_assets(album.id)
|
||||
.asset_ids([asset_id])
|
||||
|
||||
@@ -241,13 +241,13 @@ impl AddAssetsBuilder {
|
||||
}
|
||||
|
||||
/// Execute the request
|
||||
pub async fn execute(self) -> Result<AlbumResponse> {
|
||||
pub async fn execute(self) -> Result<Vec<serde_json::Value>> {
|
||||
let path = format!("/albums/{}/assets", self.album_id);
|
||||
let body = serde_json::json!({ "ids": self.asset_ids });
|
||||
let req = self.client.put(&path).json(&body);
|
||||
let response = self.client.execute(req.build()?).await?;
|
||||
let album: AlbumResponse = response.json().await?;
|
||||
Ok(album)
|
||||
let result: Vec<serde_json::Value> = response.json().await?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@ use std::path::Path;
|
||||
use crate::{
|
||||
Client,
|
||||
error::{ImmichError, Result},
|
||||
models::{AssetId, AssetMediaSize, AssetResponse, AssetUploadResponse, DeleteAssetsRequest, MetadataSearchRequest, SearchResponse},
|
||||
models::{
|
||||
AssetId, AssetMediaSize, AssetResponse, AssetUploadResponse, DeleteAssetsRequest,
|
||||
MetadataSearchRequest, SearchResponse,
|
||||
},
|
||||
};
|
||||
|
||||
/// Response from downloading a thumbnail containing image data and metadata
|
||||
@@ -215,6 +218,8 @@ pub struct UploadAssetBuilder {
|
||||
device_asset_id: Option<String>,
|
||||
device_id: Option<String>,
|
||||
is_favorite: bool,
|
||||
file_created_at: Option<String>,
|
||||
file_modified_at: Option<String>,
|
||||
}
|
||||
|
||||
impl UploadAssetBuilder {
|
||||
@@ -226,6 +231,8 @@ impl UploadAssetBuilder {
|
||||
device_asset_id: None,
|
||||
device_id: None,
|
||||
is_favorite: false,
|
||||
file_created_at: None,
|
||||
file_modified_at: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +260,18 @@ impl UploadAssetBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set file creation timestamp (ISO 8601 format)
|
||||
pub fn file_created_at(mut self, timestamp: impl Into<String>) -> Self {
|
||||
self.file_created_at = Some(timestamp.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Set file modification timestamp (ISO 8601 format)
|
||||
pub fn file_modified_at(mut self, timestamp: impl Into<String>) -> Self {
|
||||
self.file_modified_at = Some(timestamp.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Execute the upload
|
||||
pub async fn execute(self) -> Result<AssetUploadResponse> {
|
||||
let file_path = self
|
||||
@@ -282,6 +301,11 @@ impl UploadAssetBuilder {
|
||||
|
||||
form = form.text("isFavorite", self.is_favorite.to_string());
|
||||
|
||||
// Add file timestamps (required by Immich API v2)
|
||||
let now = chrono::Utc::now().to_rfc3339();
|
||||
form = form.text("fileCreatedAt", self.file_created_at.unwrap_or_else(|| now.clone()));
|
||||
form = form.text("fileModifiedAt", self.file_modified_at.unwrap_or(now));
|
||||
|
||||
let req = self.client.post("/assets").multipart(form);
|
||||
let response = self.client.execute(req.build()?).await?;
|
||||
let result: AssetUploadResponse = response.json().await?;
|
||||
|
||||
@@ -186,10 +186,66 @@ pub struct ServerFeatures {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ServerAbout {
|
||||
/// Version information
|
||||
pub version: ServerVersion,
|
||||
/// Version string
|
||||
/// Server version (e.g., "v2.7.5")
|
||||
pub version: String,
|
||||
/// URL to version information
|
||||
pub version_url: String,
|
||||
/// Build identifier
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub build: Option<String>,
|
||||
/// Build URL
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub build_url: Option<String>,
|
||||
/// Build image name
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub build_image: Option<String>,
|
||||
/// Build image URL
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub build_image_url: Option<String>,
|
||||
/// ExifTool version
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub exiftool: Option<String>,
|
||||
/// FFmpeg version
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ffmpeg: Option<String>,
|
||||
/// ImageMagick version
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub imagemagick: Option<String>,
|
||||
/// libvips version
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub libvips: Option<String>,
|
||||
/// Node.js version
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub nodejs: Option<String>,
|
||||
/// Repository name
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub repository: Option<String>,
|
||||
/// Repository URL
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub repository_url: Option<String>,
|
||||
/// Source commit hash
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source_commit: Option<String>,
|
||||
/// Source reference (branch/tag)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source_ref: Option<String>,
|
||||
/// Source URL
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source_url: Option<String>,
|
||||
/// Third-party bug/feature URL
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub third_party_bug_feature_url: Option<String>,
|
||||
/// Third-party documentation URL
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub third_party_documentation_url: Option<String>,
|
||||
/// Third-party source URL
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub third_party_source_url: Option<String>,
|
||||
/// Third-party support URL
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub third_party_support_url: Option<String>,
|
||||
/// Whether the server is licensed
|
||||
pub licensed: bool,
|
||||
}
|
||||
|
||||
/// Create album request
|
||||
@@ -248,7 +304,7 @@ pub struct AssetUploadResponse {
|
||||
|
||||
/// Asset upload status
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AssetUploadStatus {
|
||||
/// Upload created new asset
|
||||
Created,
|
||||
@@ -284,7 +340,7 @@ pub struct ApiKeyResponse {
|
||||
|
||||
/// Asset order enumeration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AssetOrder {
|
||||
/// Oldest first
|
||||
Asc,
|
||||
|
||||
Reference in New Issue
Block a user