diff --git a/docs/api/README.md b/docs/api/README.md new file mode 100644 index 0000000..00175c7 --- /dev/null +++ b/docs/api/README.md @@ -0,0 +1,11 @@ +# DAM Open API Documentation + +This directory contains documentation for integrating with the DAM Open API. + +## Contents + +- [Overview](overview.md) - General overview of the API +- [Authentication](authentication.md) - Authentication methods +- [Endpoints](endpoints.md) - Available API endpoints +- [Integration Examples](integration-examples.md) - Code examples for integration +- [Best Practices](best-practices.md) - Recommended practices for API usage diff --git a/docs/api/authentication.md b/docs/api/authentication.md new file mode 100644 index 0000000..5c0229e --- /dev/null +++ b/docs/api/authentication.md @@ -0,0 +1,47 @@ +# Authentication + +## Overview + +DAM Open API uses HTTP Basic Authentication for securing API access. Authentication is handled through the `BasicAuthWithExclude` provider which extends Drupal's standard Basic Auth. + +## Authentication Methods + +### Basic Authentication + +The API uses standard HTTP Basic Authentication. You need to include an `Authorization` header with each request. + +``` +Authorization: Basic {base64_encoded_credentials} +``` + +Where `{base64_encoded_credentials}` is the Base64 encoding of `username:password`. + +### Example + +```php +$username = 'api_user'; +$password = 'api_password'; +$auth_header = 'Authorization: Basic ' . base64_encode("$username:$password"); +``` + +## Custom Authentication Provider + +DAM Open extends Drupal's Basic Authentication with a custom provider (`BasicAuthWithExclude`) that: + +1. Applies authentication only to JSON:API routes +2. Handles LDAP integration for user authentication +3. Provides exclusion mechanisms for specific routes + +## Required Permissions + +To access the API endpoints, the authenticated user must have the following permission: + +- `access tml jsonapi resources` + +## Security Best Practices + +1. **Use HTTPS**: Always use HTTPS for API communication to encrypt credentials +2. **Dedicated API User**: Create a dedicated user with minimal permissions for API access +3. **Credential Management**: Store API credentials securely and never hardcode them +4. **Token Rotation**: Regularly change API user passwords +5. **IP Restrictions**: Consider restricting API access to specific IP addresses diff --git a/docs/api/best-practices.md b/docs/api/best-practices.md new file mode 100644 index 0000000..e7164a1 --- /dev/null +++ b/docs/api/best-practices.md @@ -0,0 +1,181 @@ +# Best Practices + +This document outlines recommended practices for using the DAM Open API effectively and securely. + +## Performance Optimization + +### Implement Caching + +Cache API responses to reduce the number of requests and improve performance: + +```php +// Example of simple caching in PHP +function getCachedAssets($base_url, $username, $password, $cache_duration = 3600) { + $cache_file = 'dam_assets_cache.json'; + $cache_time = file_exists($cache_file) ? filemtime($cache_file) : 0; + + if (time() - $cache_time > $cache_duration) { + // Cache expired or doesn't exist, fetch fresh data + $assets = fetch_dam_assets($base_url, $username, $password); + file_put_contents($cache_file, json_encode($assets)); + return $assets; + } else { + // Return cached data + return json_decode(file_get_contents($cache_file), true); + } +} +``` + +### Use Pagination + +When fetching large collections of assets, use pagination to limit the response size: + +``` +GET /jsonapi/media/image?page[limit]=10&page[offset]=0 +``` + +### Request Only Needed Fields + +Specify which fields you need to reduce response size: + +``` +GET /jsonapi/media/image?fields[media--image]=name,thumbnail,field_category +``` + +## Security + +### Secure Credential Storage + +Never hardcode API credentials in your application code. Use environment variables or a secure configuration system: + +```php +// PHP example using environment variables +$username = getenv('DAM_API_USERNAME'); +$password = getenv('DAM_API_PASSWORD'); +``` + +### Implement Rate Limiting + +Implement rate limiting in your application to avoid overwhelming the API: + +```php +// Simple rate limiting example +function rateLimitedRequest($endpoint, $headers, $last_request_time, $min_interval = 1) { + $current_time = microtime(true); + if ($current_time - $last_request_time < $min_interval) { + // Sleep to respect rate limit + usleep(($min_interval - ($current_time - $last_request_time)) * 1000000); + } + + // Make the request + $ch = curl_init($endpoint); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + $response = curl_exec($ch); + curl_close($ch); + + return [ + 'response' => $response, + 'timestamp' => microtime(true) + ]; +} +``` + +### Use HTTPS + +Always use HTTPS for API communication to ensure data security. + +## Error Handling + +### Implement Robust Error Handling + +Handle API errors gracefully: + +```php +function safeApiRequest($endpoint, $headers) { + $ch = curl_init($endpoint); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + + $response = curl_exec($ch); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($http_code >= 400) { + // Handle error + return [ + 'success' => false, + 'error' => 'API request failed with code ' . $http_code, + 'response' => $response + ]; + } + + return [ + 'success' => true, + 'data' => json_decode($response, true) + ]; +} +``` + +### Log API Interactions + +Log API interactions for debugging and monitoring: + +```php +function logApiRequest($endpoint, $method, $status_code, $response_time) { + $log_entry = date('Y-m-d H:i:s') . " | $method | $endpoint | $status_code | {$response_time}ms\n"; + file_put_contents('api_log.txt', $log_entry, FILE_APPEND); +} +``` + +## Integration Patterns + +### Webhook Integration + +Consider implementing webhooks to receive updates when assets change: + +1. Set up an endpoint in your application to receive webhook notifications +2. Register your webhook URL with the DAM Open system +3. Process incoming webhook notifications to update your local cache + +### Batch Processing + +For operations involving multiple assets, use batch processing: + +```php +function processBatchOfAssets($assets, $operation_callback) { + $results = []; + $batch_size = 10; + $total = count($assets); + + for ($i = 0; $i < $total; $i += $batch_size) { + $batch = array_slice($assets, $i, $batch_size); + foreach ($batch as $asset) { + $results[] = $operation_callback($asset); + } + // Add a small delay between batches + usleep(500000); // 500ms + } + + return $results; +} +``` + +## Testing + +### Create a Test Environment + +Set up a separate test environment for API integration development: + +1. Use a separate API user for testing +2. Test with a limited dataset +3. Implement automated tests for your integration + +### Monitor API Usage + +Monitor your API usage to identify patterns and optimize your integration: + +1. Track request frequency +2. Monitor response times +3. Analyze error rates +4. Identify most frequently accessed resources diff --git a/docs/api/endpoints.md b/docs/api/endpoints.md new file mode 100644 index 0000000..e203070 --- /dev/null +++ b/docs/api/endpoints.md @@ -0,0 +1,127 @@ +# API Endpoints + +## Media Assets + +### Get Media Assets + +``` +GET /jsonapi/media/image +``` + +Retrieves a list of media assets of type "image". + +**Parameters:** +- `filter[field_name][value]`: Filter by field value +- `page[limit]`: Number of items per page +- `page[offset]`: Offset for pagination +- `sort`: Field to sort by (e.g., `sort=created`) + +**Response:** +```json +{ + "data": [ + { + "type": "media--image", + "id": "uuid", + "attributes": { + "name": "Image Name", + "created": "2023-01-01T12:00:00+00:00", + "changed": "2023-01-01T12:00:00+00:00", + "thumbnail": { + "alt": "Alt text", + "title": "Title text", + "url": "https://example.com/sites/default/files/styles/thumbnail/private/image.jpg" + }, + "assets": { + "medium": "https://example.com/sites/default/files/styles/medium/private/image.jpg", + "large": "https://example.com/sites/default/files/styles/large/private/image.jpg" + }, + "field_category": ["Category Name"], + "field_keywords": ["Keyword1", "Keyword2"], + "field_gps_gpslatitude": "47.497912", + "field_gps_gpslongitude": "19.040235", + "field_iptc_by_line": "Photographer Name", + "field_iptc_caption": "Image Caption", + "field_iptc_object_name": "Object Name" + } + } + ] +} +``` + +### Get Single Media Asset + +``` +GET /jsonapi/media/image/{uuid} +``` + +Retrieves a single media asset by its UUID. + +**Response:** +Same structure as above but with a single item. + +## File Access + +### Get File + +``` +GET /system/files/{file_path} +``` + +Retrieves a file by its path. + +### Get Private File + +``` +GET /system/private/file/download/{file_path} +``` + +Retrieves a private file by its path. Requires authentication. + +### Get Image Style + +``` +GET /image-style/{style}/private/{file_path} +``` + +Retrieves an image processed with the specified image style. + +**Parameters:** +- `style`: The image style to apply (e.g., `thumbnail`, `medium`, `large`) +- `file_path`: Path to the file + +## Taxonomy + +### Get Categories + +``` +GET /jsonapi/taxonomy_term/category +``` + +Retrieves a list of categories. + +### Get Keywords + +``` +GET /jsonapi/taxonomy_term/keywords +``` + +Retrieves a list of keywords. + +## Media Collections + +### Get Collections + +``` +GET /jsonapi/media_collection/media_collection +``` + +Retrieves a list of media collections. + +### Get Collection Items + +``` +GET /jsonapi/media_collection_item/media_collection_item +``` + +Retrieves a list of media collection items. diff --git a/docs/api/integration-examples.md b/docs/api/integration-examples.md new file mode 100644 index 0000000..3c2ac79 --- /dev/null +++ b/docs/api/integration-examples.md @@ -0,0 +1,233 @@ +# Integration Examples + +This document provides code examples for integrating with the DAM Open API using different programming languages and frameworks. + +## PHP Examples + +### Basic API Request + +```php + [ + 'header' => 'Authorization: Basic ' . base64_encode("$username:$password") + ] + ]); + + // Return image tag with authenticated URL + return 'DAM Image'; +} +``` + +### Using Guzzle HTTP Client + +```php +request('GET', $base_url . '/jsonapi/media/image', [ + 'headers' => [ + 'Accept' => 'application/vnd.api+json', + 'Authorization' => 'Basic ' . base64_encode("$username:$password"), + ], + ]); + + return json_decode($response->getBody(), TRUE); +} +``` + +## JavaScript Examples + +### Fetch API + +```javascript +/** + * Fetch media assets from DAM Open + */ +async function fetchDamAssets(baseUrl, username, password) { + const endpoint = `${baseUrl}/jsonapi/media/image`; + const response = await fetch(endpoint, { + headers: { + 'Accept': 'application/vnd.api+json', + 'Authorization': 'Basic ' + btoa(`${username}:${password}`) + } + }); + + return await response.json(); +} + +/** + * Display an image from DAM Open + */ +function displayDamImage(imageUri, style = 'medium', baseUrl, username, password) { + const imageUrl = `${baseUrl}/image-style/${style}/private/${imageUri}`; + + // Create image element with authenticated URL + const img = document.createElement('img'); + img.src = imageUrl; + img.alt = 'DAM Image'; + + // Add authentication headers when the image is requested + img.crossOrigin = 'use-credentials'; + + return img; +} +``` + +### Axios Library + +```javascript +// Using Axios library +import axios from 'axios'; + +/** + * Fetch media assets using Axios + */ +async function fetchDamAssetsAxios(baseUrl, username, password) { + const endpoint = `${baseUrl}/jsonapi/media/image`; + + const response = await axios.get(endpoint, { + headers: { + 'Accept': 'application/vnd.api+json', + 'Authorization': 'Basic ' + btoa(`${username}:${password}`) + } + }); + + return response.data; +} +``` + +## Drupal Module Integration + +### Custom Module Structure + +``` +my_dam_integration/ + ├── my_dam_integration.info.yml + ├── my_dam_integration.module + ├── my_dam_integration.services.yml + └── src/ + ├── DamOpenClient.php + ├── Form/ + │ └── DamOpenSettingsForm.php + └── Plugin/ + └── Field/ + └── DamOpenMediaField.php +``` + +### Service Configuration + +```yaml +# my_dam_integration.services.yml +services: + my_dam_integration.client: + class: Drupal\my_dam_integration\DamOpenClient + arguments: + - '@config.factory' + - '@http_client' +``` + +### Client Implementation + +```php +configFactory = $configFactory; + $this->httpClient = $httpClient; + } + + /** + * Get media assets from DAM Open. + */ + public function getMediaAssets() { + $config = $this->configFactory->get('my_dam_integration.settings'); + $baseUrl = $config->get('base_url'); + $username = $config->get('username'); + $password = $config->get('password'); + + $response = $this->httpClient->request('GET', $baseUrl . '/jsonapi/media/image', [ + 'headers' => [ + 'Accept' => 'application/vnd.api+json', + 'Authorization' => 'Basic ' . base64_encode("$username:$password"), + ], + ]); + + return json_decode($response->getBody(), TRUE); + } + + /** + * Get image URL with style. + */ + public function getImageUrl($imageUri, $style = 'medium') { + $config = $this->configFactory->get('my_dam_integration.settings'); + $baseUrl = $config->get('base_url'); + + return $baseUrl . '/image-style/' . $style . '/private/' . $imageUri; + } +} +``` diff --git a/docs/api/overview.md b/docs/api/overview.md new file mode 100644 index 0000000..6947629 --- /dev/null +++ b/docs/api/overview.md @@ -0,0 +1,39 @@ +# DAM Open API Overview + +## Introduction + +DAM Open provides a RESTful API for integrating with Drupal-based systems. The API is built on top of Drupal's JSON:API and REST modules, allowing for standardized access to digital assets stored in the DAM system. + +## Architecture + +The DAM Open API follows REST principles and uses JSON:API specification for structured responses. It provides access to media assets, taxonomies, and file operations through standardized endpoints. + +## Key Features + +- **Media Asset Access**: Retrieve and manage digital assets +- **Taxonomy Integration**: Access categories and keywords +- **Image Style Processing**: Generate different image styles on demand +- **Authentication**: Secure access through Basic Authentication +- **Standardized Responses**: Consistent JSON:API formatted responses + +## Prerequisites + +To integrate with DAM Open, your Drupal installation needs: + +- Drupal 9.1+ or Drupal 10.0+ +- JSON:API module enabled +- REST module enabled +- Basic Auth module enabled + +## Module Dependencies + +The API functionality is provided by the following modules: +- `damopen_assets_api`: Core API functionality +- `damopen_common`: Common utilities and services +- `jsonapi_extras`: Enhanced JSON:API functionality + +## Getting Started + +1. Ensure you have proper authentication credentials +2. Explore the available endpoints +3. Test API calls using the examples provided in the [Integration Examples](integration-examples.md) document diff --git a/modules/damopen_common/damopen_common.module b/modules/damopen_common/damopen_common.module index 60f9164..3e244ff 100644 --- a/modules/damopen_common/damopen_common.module +++ b/modules/damopen_common/damopen_common.module @@ -1,5 +1,7 @@ id() !== 'media') { + return []; + } + + // Check if user has permission to view unpublished media + if ($account->hasPermission('view any unpublished image media') || + $account->hasPermission('administer media')) { + return [ + JSONAPI_FILTER_AMONG_ALL => AccessResult::allowed(), + ]; + } + + // Check if user has permission to view own unpublished media + if ($account->hasPermission('view own unpublished media')) { + return [ + JSONAPI_FILTER_AMONG_OWN => AccessResult::allowed(), + ]; + } + + return []; +} + +/** + * Implements hook_jsonapi_entity_access(). + * + * Grant access to unpublished media entities for authorized users. + */ +function damopen_common_jsonapi_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account) { + // Only handle media entities + if ($entity->getEntityTypeId() !== 'media') { + return \Drupal\Core\Access\AccessResult::neutral(); + } + + // Only handle view operations + if ($operation !== 'view') { + return \Drupal\Core\Access\AccessResult::neutral(); + } + + // Check if media is unpublished + if ($entity->isPublished()) { + return \Drupal\Core\Access\AccessResult::neutral(); + } + + // Check permissions for unpublished media + if ($account->hasPermission('view any unpublished image media') || + $account->hasPermission('administer media')) { + return \Drupal\Core\Access\AccessResult::allowed() + ->cachePerPermissions() + ->addCacheableDependency($entity); + } + + // Check if user owns the media + if ($entity->hasField('uid') && + $entity->get('uid')->target_id == $account->id() && + $account->hasPermission('view own unpublished media')) { + return \Drupal\Core\Access\AccessResult::allowed() + ->cachePerPermissions() + ->cachePerUser() + ->addCacheableDependency($entity); + } + + return \Drupal\Core\Access\AccessResult::neutral(); +} diff --git a/modules/damopen_common/src/Plugin/Block/HeaderMenuBlock.php b/modules/damopen_common/src/Plugin/Block/HeaderMenuBlock.php index de905ae..73b285c 100644 --- a/modules/damopen_common/src/Plugin/Block/HeaderMenuBlock.php +++ b/modules/damopen_common/src/Plugin/Block/HeaderMenuBlock.php @@ -64,7 +64,7 @@ public function build() { $count = $query->count()->execute(); $menu['manage_assets'] = [ 'title' => new TranslatableMarkup('My Assets waiting for approval'), - 'url' => Url::fromRoute('view.unpublished_assets.user_unpublished_assets')->toString(), + 'url' => Url::fromRoute('damopen_wfa.assets')->toString(), 'class' => '', 'count' => $count, ]; diff --git a/modules/damopen_upload/damopen_upload.routing.yml b/modules/damopen_upload/damopen_upload.routing.yml index 896a538..4f309e2 100644 --- a/modules/damopen_upload/damopen_upload.routing.yml +++ b/modules/damopen_upload/damopen_upload.routing.yml @@ -5,6 +5,7 @@ damopen_upload.upload: _title: 'Image upload' requirements: _permission: 'access content' + damopen_upload.image_form: path: '/upload-image-form' defaults: @@ -13,3 +14,11 @@ damopen_upload.image_form: requirements: _permission: 'access content' _access: 'TRUE' + +damopen_wfa.assets: + path: '/waiting-for-approval' + defaults: + _controller: '\Drupal\damopen_upload\Controller\DamopenWfaController::waitingForApproval' + _title: 'Assets Waiting for Approval' + requirements: + _permission: 'access media asset library' diff --git a/modules/damopen_upload/src/Controller/DamopenWfaController.php b/modules/damopen_upload/src/Controller/DamopenWfaController.php new file mode 100644 index 0000000..69b355c --- /dev/null +++ b/modules/damopen_upload/src/Controller/DamopenWfaController.php @@ -0,0 +1,32 @@ + '
', + '#cache' => [ + 'max-age' => 0, + ], + '#attached' => [ + 'library' => [ + 'damo_theme/damopen_wfa', + ], + ], + ]; + } + +} diff --git a/modules/media_collection/src/Service/FileSizeCalculator.php b/modules/media_collection/src/Service/FileSizeCalculator.php index 24521da..ba96a42 100644 --- a/modules/media_collection/src/Service/FileSizeCalculator.php +++ b/modules/media_collection/src/Service/FileSizeCalculator.php @@ -129,8 +129,9 @@ private function collectionItemsSize(MediaCollectionInterface $collection): ?int if ($media->bundle() === 'image') { /** @var \Drupal\image\ImageStyleInterface $style */ $style = $item->get('style')->entity ?? NULL; - // @todo: Check for the existence of style. If not there, that's an inconsistent state. - $size += $this->calculateImageMediaSize($media, $style); + // Calculate size of the styled image. + $size += is_a($style, 'Drupal\image\ImageStyleInterface') ? + $this->calculateImageMediaSize($media, $style) : 0; continue; }