nostr images fetching from API

This commit is contained in:
gitea
2025-11-06 00:45:58 +01:00
parent c37dce0222
commit 9650fc78a8
12 changed files with 2694 additions and 233 deletions
+196 -11
View File
@@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:dio/dio.dart';
import '../local/local_storage_service.dart';
import '../local/models/item.dart';
@@ -127,6 +128,9 @@ class ImmichService {
/// Fetches a list of assets from Immich.
///
/// Based on official Immich API documentation: https://api.immich.app/endpoints/search/searchAssets
/// Uses POST /api/search/metadata endpoint with search parameters.
///
/// [limit] - Maximum number of assets to fetch (default: 100).
/// [skip] - Number of assets to skip (for pagination).
///
@@ -139,22 +143,41 @@ class ImmichService {
int skip = 0,
}) async {
try {
final response = await _dio.get(
'/api/asset',
queryParameters: {
// Official endpoint: POST /api/search/metadata
// Response structure: {"assets": {"items": [...], "total": N, "count": N, "nextPage": ...}}
final response = await _dio.post(
'/api/search/metadata',
data: {
'limit': limit,
'skip': skip,
// Empty search to get all assets
},
);
if (response.statusCode != 200) {
if (response.statusCode != 200 && response.statusCode != 201) {
throw ImmichException(
'Failed to fetch assets: ${response.statusMessage}',
response.statusCode,
);
}
final List<dynamic> assetsJson = response.data;
// Parse response structure: {"assets": {"items": [...], "total": N, "count": N}}
final responseData = response.data as Map<String, dynamic>;
if (!responseData.containsKey('assets')) {
throw ImmichException('Unexpected response format: missing "assets" field');
}
final assetsData = responseData['assets'] as Map<String, dynamic>;
if (!assetsData.containsKey('items')) {
throw ImmichException('Unexpected response format: missing "items" field in assets');
}
final assetsJson = assetsData['items'] as List<dynamic>;
final total = assetsData['total'] as int? ?? 0;
final count = assetsData['count'] as int? ?? 0;
final List<ImmichAsset> assets = assetsJson
.map((json) => ImmichAsset.fromJson(json as Map<String, dynamic>))
.toList();
@@ -166,25 +189,45 @@ class ImmichService {
return assets;
} on DioException catch (e) {
final statusCode = e.response?.statusCode;
final errorData = e.response?.data;
String errorMessage;
if (errorData is Map) {
errorMessage = errorData['message']?.toString() ??
errorData.toString();
} else {
errorMessage = errorData?.toString() ??
e.message ??
'Unknown error';
}
throw ImmichException(
'Failed to fetch assets: ${e.message ?? 'Unknown error'}',
e.response?.statusCode,
'Failed to fetch assets: $errorMessage',
statusCode,
);
} catch (e) {
if (e is ImmichException) {
rethrow;
}
throw ImmichException('Failed to fetch assets: $e');
}
}
/// Fetches a single asset by ID.
///
/// [assetId] - The unique identifier of the asset.
/// Based on official Immich API documentation: https://api.immich.app/endpoints/assets
/// Endpoint: GET /api/assets/{id}
///
/// [assetId] - The unique identifier (UUID) of the asset.
///
/// Returns [ImmichAsset] if found.
///
/// Throws [ImmichException] if fetch fails.
Future<ImmichAsset> _getAssetById(String assetId) async {
try {
final response = await _dio.get('/api/asset/$assetId');
// Official endpoint: GET /api/assets/{id}
final response = await _dio.get('/api/assets/$assetId');
if (response.statusCode != 200) {
throw ImmichException(
@@ -195,9 +238,22 @@ class ImmichService {
return ImmichAsset.fromJson(response.data as Map<String, dynamic>);
} on DioException catch (e) {
final statusCode = e.response?.statusCode;
final errorData = e.response?.data;
String errorMessage;
if (errorData is Map) {
errorMessage = errorData['message']?.toString() ??
errorData.toString();
} else {
errorMessage = errorData?.toString() ??
e.message ??
'Unknown error';
}
throw ImmichException(
'Failed to fetch asset: ${e.message ?? 'Unknown error'}',
e.response?.statusCode,
'Failed to fetch asset: $errorMessage',
statusCode,
);
} catch (e) {
throw ImmichException('Failed to fetch asset: $e');
@@ -261,5 +317,134 @@ class ImmichService {
return [];
}
}
/// Gets the thumbnail URL for an asset.
///
/// Uses GET /api/assets/{id}/thumbnail endpoint.
///
/// [assetId] - The unique identifier of the asset.
///
/// Returns the full URL to the thumbnail image.
String getThumbnailUrl(String assetId) {
return '$_baseUrl/api/assets/$assetId/thumbnail';
}
/// Gets the full image URL for an asset.
///
/// Uses GET /api/assets/{id}/original endpoint.
///
/// [assetId] - The unique identifier of the asset.
///
/// Returns the full URL to the original image file.
String getImageUrl(String assetId) {
return '$_baseUrl/api/assets/$assetId/original';
}
/// Gets the base URL for Immich API.
String get baseUrl => _baseUrl;
/// Fetches image bytes for an asset.
///
/// Uses GET /api/assets/{id}/thumbnail for thumbnails or GET /api/assets/{id}/original for full images.
///
/// [assetId] - The unique identifier of the asset (from metadata response).
/// [isThumbnail] - Whether to fetch thumbnail (true) or original image (false). Default: true.
///
/// Returns the image bytes as Uint8List.
///
/// Throws [ImmichException] if fetch fails.
Future<Uint8List> fetchImageBytes(String assetId, {bool isThumbnail = true}) async {
try {
// Use correct endpoint based on thumbnail vs original
final endpoint = isThumbnail
? '/api/assets/$assetId/thumbnail'
: '/api/assets/$assetId/original';
final response = await _dio.get<List<int>>(
endpoint,
options: Options(
responseType: ResponseType.bytes,
),
);
if (response.statusCode != 200) {
throw ImmichException(
'Failed to fetch image: ${response.statusMessage}',
response.statusCode,
);
}
return Uint8List.fromList(response.data ?? []);
} on DioException catch (e) {
final statusCode = e.response?.statusCode;
final errorData = e.response?.data;
String errorMessage;
if (errorData is Map) {
errorMessage = errorData['message']?.toString() ??
errorData.toString();
} else {
errorMessage = errorData?.toString() ??
e.message ??
'Unknown error';
}
throw ImmichException(
'Failed to fetch image: $errorMessage',
statusCode,
);
} catch (e) {
throw ImmichException('Failed to fetch image: $e');
}
}
/// Gets the headers needed for authenticated image requests.
///
/// Returns a map of headers including the API key.
Map<String, String> getImageHeaders() {
return {
'x-api-key': _apiKey,
};
}
/// Tests the connection to Immich server by calling the /api/server/about endpoint.
///
/// Returns server information including version and status.
///
/// Throws [ImmichException] if the request fails.
Future<Map<String, dynamic>> getServerInfo() async {
try {
final response = await _dio.get('/api/server/about');
if (response.statusCode != 200) {
throw ImmichException(
'Failed to get server info: ${response.statusMessage}',
response.statusCode,
);
}
return response.data as Map<String, dynamic>;
} on DioException catch (e) {
final statusCode = e.response?.statusCode;
final errorData = e.response?.data;
String errorMessage;
if (errorData is Map) {
errorMessage = errorData['message']?.toString() ??
errorData.toString();
} else {
errorMessage = errorData?.toString() ??
e.message ??
'Unknown error';
}
throw ImmichException(
'Failed to get server info: $errorMessage',
statusCode,
);
} catch (e) {
throw ImmichException('Failed to get server info: $e');
}
}
}