fairu_sdk_dart 0.1.0
fairu_sdk_dart: ^0.1.0 copied to clipboard
Official Dart/Flutter SDK for the Fairu Digital Asset Management API. Provides type-safe GraphQL queries, mutations, uploads, and image transformations.
Fairu SDK for Dart/Flutter #
Official Dart/Flutter SDK for the Fairu Digital Asset Management API.
Features #
- Asset Management - Query, update, delete, block/unblock files
- Folder Management - Create, update, delete, move folders
- Gallery Management - Create and manage galleries
- Copyright & License Management - Full CRUD operations
- File Uploads - Simple and multipart uploads with progress tracking
- FileProxy URL Builder - Fluent API for image transformations (resize, crop, format conversion)
- Type-safe - Full type definitions for all operations
- Error Handling - Structured error classes for validation, authentication, and GraphQL errors
Installation #
Add this to your pubspec.yaml:
dependencies:
fairu_sdk_dart:
git:
url: https://github.com/fairu-media/fairu-sdk-dart.git
Or, if published to pub.dev:
dependencies:
fairu_sdk_dart: ^0.1.0
Then run:
dart pub get
Quick Start #
import 'package:fairu_sdk_dart/fairu_sdk_dart.dart';
void main() async {
// Initialize the SDK
final fairu = FairuSdk(
config: FairuClientConfig(
token: 'your-api-token',
),
);
// Fetch an asset
final asset = await fairu.assets.find('asset-id');
print('Asset: ${asset?['name']}');
// List assets
final assets = await fairu.assets.list(page: 1, perPage: 10);
print('Total assets: ${assets.paginatorInfo.total}');
}
Configuration #
final config = FairuClientConfig(
// GraphQL API URL (default: https://fairu.app/graphql)
url: 'https://fairu.app/graphql',
// Static API token
token: 'your-api-token',
// Or use a dynamic token provider
getToken: () async => await getTokenFromSecureStorage(),
// FileProxy base URL (default: https://files.fairu.app)
fileProxyUrl: 'https://files.fairu.app',
// Request timeout in milliseconds (default: 30000)
timeout: 30000,
);
final fairu = FairuSdk(config: config);
Assets (Files) #
Find Asset #
// By ID
final asset = await fairu.assets.find('asset-id');
// By path
final asset = await fairu.assets.findByPath('/marketing/hero.jpg');
List Assets #
final result = await fairu.assets.list(
page: 1,
perPage: 20,
folderId: 'folder-id', // optional
);
print('Total: ${result.paginatorInfo.total}');
for (final asset in result.data) {
print('- ${asset['name']}');
}
Search Assets #
final result = await fairu.assets.search(
searchTerm: 'hero image',
page: 1,
perPage: 20,
orderBy: 'created_at',
orderDirection: 'DESC',
);
Update Asset #
final updated = await fairu.assets.update(
id: 'asset-id',
alt: 'New alt text',
caption: 'New caption',
description: 'New description',
focalPoint: '50-50-1',
copyrightIds: ['copyright-id'],
licenseIds: ['license-id'],
);
Delete Asset #
final success = await fairu.assets.delete('asset-id');
Block/Unblock Asset #
await fairu.assets.block('asset-id');
await fairu.assets.unblock('asset-id');
Rename/Move Asset #
// Rename
await fairu.assets.rename('asset-id', 'new-name');
// Move to different folder
await fairu.assets.move('asset-id', parentId: 'folder-id');
// Move to root
await fairu.assets.move('asset-id');
Folders #
List Folder Contents #
final result = await fairu.folders.getContent(
folderId: 'folder-id', // null for root
page: 1,
perPage: 20,
search: 'optional search term',
orderBy: 'name',
orderDirection: 'ASC',
);
Find Folder by Path #
final folder = await fairu.folders.findByPath('/marketing/2024');
Create Folder #
final folder = await fairu.folders.create(
name: 'New Folder',
parentId: 'parent-folder-id', // optional
autoAssignCopyright: true, // optional
copyrightIds: ['copyright-id'], // optional
);
Update/Delete Folder #
await fairu.folders.update(id: 'folder-id', name: 'Renamed Folder');
await fairu.folders.delete('folder-id');
File Uploads #
Simple Upload #
import 'dart:io';
final file = File('/path/to/image.jpg');
final result = await fairu.upload.simple(
file: file,
filename: 'image.jpg',
folderId: 'folder-id',
alt: 'Alt text',
caption: 'Caption',
description: 'Description',
onProgress: (progress) {
print('Upload: ${progress.percentage}%');
},
onStatusChange: (status) {
print('Status: $status'); // preparing, uploading, syncing, completed
},
);
print('Uploaded file ID: ${result.id}');
Multipart Upload (Large Files) #
import 'dart:io';
final file = File('/path/to/large-video.mp4');
final fileSize = await file.length();
// Initialize multipart upload
final session = await fairu.upload.initMultipart(
filename: 'large-video.mp4',
fileSize: fileSize,
folderId: 'folder-id',
contentType: 'video/mp4',
);
// Upload parts
final fileBytes = await file.readAsBytes();
final partSize = session.partSize;
int offset = 0;
int partNumber = 1;
while (offset < fileBytes.length) {
final end = (offset + partSize < fileBytes.length)
? offset + partSize
: fileBytes.length;
final partData = fileBytes.sublist(offset, end);
// Get presigned URL for this part (if not provided upfront)
final part = session.parts.isNotEmpty
? session.parts[partNumber - 1]
: await session.getPartUrl(partNumber);
// Upload the part
await session.uploadPart(part, partData);
offset = end;
partNumber++;
}
// Complete the upload
final result = await session.complete();
print('Upload complete: ${result.id}');
FileProxy URL Builder #
Build optimized image URLs with transformations:
// Basic usage
final url = fileProxy('asset-id', 'image.jpg')
.width(800)
.height(600)
.quality(85)
.format(ImageFormat.webp)
.build();
// Result: https://files.fairu.app/asset-id/image.jpg?width=800&height=600&quality=85&format=webp
Available Transformations #
final url = fileProxy('asset-id', 'image.jpg')
// Dimensions (1-6000 pixels)
.width(800)
.height(600)
.dimensions(800, 600) // shorthand
// Quality (1-100, default: 95)
.quality(85)
// Format
.format(ImageFormat.webp) // jpg, png, webp
// Fit mode
.fit(FitMode.cover) // cover, contain
// Focal point for smart cropping
.focal(50, 30, zoom: 1.5) // x, y, zoom
.focalFromString('50-30-1.5') // from string
// Return original file
.raw(true)
// Convert SVG to raster
.processSvg(true)
// Video frame extraction
.timestamp('00:05:30')
.timestampFromSeconds(330)
// Video version
.version(VideoVersion.high) // low, medium, high
// Signed URL for protected files
.sign('your-secret-key')
.withSignature('pre-computed-sig', '1234567890')
.build();
Utility Functions #
// Get image metadata URL
final metaUrl = FileProxyUtils.meta('asset-id');
// HLS video streaming
final hlsUrl = FileProxyUtils.hls('tenant-id', 'asset-id', 'playlist.m3u8');
// HLS decryption key
final keyUrl = FileProxyUtils.hlsKey('asset-id', VideoVersion.high, 'key');
Galleries #
// Find gallery
final gallery = await fairu.galleries.find('gallery-id');
// List galleries
final result = await fairu.galleries.list(
tenantIds: ['tenant-id'],
page: 1,
perPage: 20,
);
// Create gallery
final gallery = await fairu.galleries.create(
name: 'Summer 2024',
folderId: 'folder-id',
description: 'Photos from summer vacation',
location: 'Beach',
date: DateTime(2024, 7, 15),
active: true,
);
// Create share link
final shareUrl = await fairu.galleries.createShareLink('gallery-id');
Copyrights & Licenses #
Copyrights #
// List copyrights
final result = await fairu.copyrights.list(page: 1, perPage: 20);
// Create copyright
final copyright = await fairu.copyrights.create(
name: 'Photographer Name',
email: 'photo@example.com',
website: 'https://example.com',
);
// Update copyright
await fairu.copyrights.update(id: 'id', name: 'Updated Name');
// Delete copyright
await fairu.copyrights.delete('id', deleteAssets: false, deleteLicenses: false);
Licenses #
// List licenses
final result = await fairu.licenses.list(page: 1, perPage: 20);
// Create license
final license = await fairu.licenses.create(
name: 'Standard License',
copyrightId: 'copyright-id',
type: 'STANDARD', // or 'PERIOD'
active: true,
);
Tenant Operations #
// Get tenant info
final tenant = await fairu.tenant.get();
// Update tenant settings
await fairu.tenant.update(
useAi: true,
forceFileAlt: true,
);
// Health check
final health = await fairu.tenant.healthCheck();
print('API Version: ${health?['version']}');
print('Healthy: ${health?['healthy']}');
Error Handling #
The SDK provides structured error classes:
try {
await fairu.assets.find('non-existent-id');
} on ValidationError catch (e) {
// Handle validation errors
print('Validation errors:');
for (final field in e.fields) {
print(' $field: ${e.getFieldErrors(field)}');
}
} on AuthenticationError catch (e) {
// Handle auth errors
if (e.isTokenExpired) {
// Refresh token
}
} on NetworkError catch (e) {
// Handle network errors
print('Network error: ${e.message}');
} on FairuError catch (e) {
// Handle other errors
print('Error: ${e.message}');
}
Raw GraphQL Queries #
For operations not covered by the SDK:
// Query
final result = await fairu.query('''
query CustomQuery(\$id: ID!) {
fairuFile(id: \$id) {
id
name
customField
}
}
''', variables: {'id': 'asset-id'});
// Mutation
final result = await fairu.mutate('''
mutation CustomMutation(\$id: ID!) {
someCustomMutation(id: \$id) {
success
}
}
''', variables: {'id': 'some-id'});
License #
MIT License - see LICENSE file for details.