diff --git a/UPLOAD_TROUBLESHOOTING.md b/UPLOAD_TROUBLESHOOTING.md new file mode 100644 index 0000000..dba38ab --- /dev/null +++ b/UPLOAD_TROUBLESHOOTING.md @@ -0,0 +1,364 @@ +# Ferrero OpenText DAM - Upload Troubleshooting Document + +**Date:** October 24, 2025 +**Environment:** Production (https://ppr.dam.ferrero.com) +**Application:** Content Scaling Workflow V3 +**Issue:** File uploads blocked with "restricted file extension" error + +--- + +## Executive Summary + +The Content Scaling workflow application successfully performs campaign management, asset downloads, and status updates. However, **all file upload attempts via the OpenText Media Management API are being rejected** with file extension restriction errors, regardless of file type. + +This document provides detailed technical information for Ferrero's OpenText DAM support team to diagnose and resolve the upload restriction. + +--- + +## Current Status + +### ✅ Working Features + +1. **Campaign Discovery** + - Search campaigns by Content Scaling Status (A1, A2, A5, etc.) + - Filter by status using `CONTENT.SCALING.STATUS` metadata field + - Display campaign details, brands, markets + +2. **Asset Downloads** + - Download master assets from campaigns + - Bulk download functionality + - Multiple file format support (.tif, .jpg, .png, etc.) + +3. **Status Management** + - Update campaign status: A1→A2, A2→A3, A5→A6 + - Uses PATCH `/v6/folders/{id}?lock_strategy=optimistic` + - All status transitions working correctly + +4. **OAuth2 Authentication** + - Client credentials flow working + - Token refresh functioning + - Authorization headers properly included in all requests + +### ❌ Blocked Feature + +**File Uploads to DAM** +- ALL file uploads rejected +- Multiple file types tested +- Various API approaches attempted +- Consistent error across all attempts + +--- + +## Upload Error Details + +### Error Message +```json +{ + "exception_body": { + "http_response_code": 500, + "message": "Cannot import asset having restricted file extension.", + "debug_message": "Asset creation failed due to restricted file extension", + "error_code": 1000 + } +} +``` + +### API Endpoint Used +``` +POST https://ppr.dam.ferrero.com/otmmapi/v6/assets +``` + +### Authentication +- **Method:** OAuth2 Bearer Token +- **Token Status:** Valid and active +- **User:** scpi.user1 (from Creds.txt) +- **Permissions:** Claimed to have "full access" + +--- + +## File Types Tested + +All of the following file extensions were rejected with the same error: + +| File Extension | MIME Type | Test Result | +|----------------|-----------|-------------| +| `.tif` | image/tiff | ❌ Rejected | +| `.jpg` | image/jpeg | ❌ Rejected | +| `.png` | image/png | ❌ Rejected | +| `.pdf` | application/pdf | ❌ Rejected | + +**Conclusion:** The restriction appears to be **account-level or API-level**, not file-type-specific. + +--- + +## Upload Approaches Tested + +### Attempt 1: Full Metadata from Master Asset +**Payload:** +- `asset_representation` with complete metadata structure from existing master asset +- `metadata_model_id`: ECOMMERCE (from master asset) +- `security_policy_list`: From master asset +- `manifest`: Upload manifest with filename and MIME type +- `parent_folder_id`: Final Assets folder ID +- `files`: File uploaded via CURLFile + +**Result:** HTTP 500 - "restricted file extension" + +--- + +### Attempt 2: Minimal Metadata with Model ID +**Payload:** +- `asset_representation` with only: + - `name`: Filename + - `metadata_model_id`: ECOMMERCE + - `security_policy_list`: [{"id": 1594}] +- `manifest`: Upload manifest +- `parent_folder_id`: Final Assets folder ID +- `files`: File + +**Result:** HTTP 500 - "restricted file extension" + +--- + +### Attempt 3: Import Template Approach +**Payload:** +- `import_template_id`: MARKETING.ASSET +- `manifest`: Upload manifest +- `parent_folder_id`: Final Assets folder ID +- `files`: File + +**Result:** HTTP 400 - "No permissable import template found" + +--- + +### Attempt 4: Absolute Minimum (No Metadata) +**Payload:** +- `manifest`: Upload manifest only +- `parent_folder_id`: Final Assets folder ID +- `files`: File + +**Result:** HTTP 500 - "restricted file extension" + +--- + +### Attempt 5: Without Metadata Model ID +**Payload:** +- `asset_representation`: + - `name`: Filename + - `security_policy_list`: [{"id": 1594}] +- `manifest`: Upload manifest +- `parent_folder_id`: Final Assets folder ID +- `files`: File + +**Result:** HTTP 500 - "restricted file extension" + +--- + +## Technical Details + +### Request Structure (Current Implementation) + +**Endpoint:** +``` +POST /v6/assets +``` + +**Headers:** +``` +Accept: application/json +Authorization: Bearer {oauth_token} +Content-Type: multipart/form-data +``` + +**Form Data Fields:** +``` +asset_representation: { + "asset_resource": { + "asset": { + "name": "filename.jpg", + "metadata_model_id": "ECOMMERCE", + "security_policy_list": [{"id": 1594}] + } + } +} + +manifest: { + "upload_manifest": { + "master_files": [{ + "file": { + "file_name": "filename.jpg", + "file_type": "image/jpeg" + } + }] + } +} + +parent_folder_id: "ea0dbf86e13e3634895746d3a986558ec2eb8be1" + +files: [binary file data via CURLFile] +``` + +### Upload Target Details + +**Campaign:** Local adaptation test 2 (C000000078) +**Campaign Asset ID:** 7e2f7c97b003f91f8b2a162b9f62ccab51586fa9 +**Target Folder:** Final Assets (ID: ea0dbf86e13e3634895746d3a986558ec2eb8be1) +**Content Scaling Status:** A2 (Assets Sent to Agency) + +**Master Assets Available:** +- 8000500247167_8.tif (image/tiff, ECOMMERCE model) +- [Additional assets in Master Assets folder] + +--- + +## Comparison: Downloads vs Uploads + +### Downloads (Working ✅) +- **Endpoint:** `GET /v6/assets/{id}/contents` +- **Authentication:** Same OAuth token +- **File Types:** .tif, .jpg, .png all download successfully +- **Permissions:** Read access confirmed working + +### Uploads (Blocked ❌) +- **Endpoint:** `POST /v6/assets` +- **Authentication:** Same OAuth token +- **File Types:** ALL rejected +- **Permissions:** Upload/write access appears restricted + +**Analysis:** The same OAuth credentials work for downloads but not uploads, suggesting **write permissions may not be enabled** for the API user account. + +--- + +## Questions for Ferrero DAM Support + +### 1. API Upload Permissions +- Is the user account `scpi.user1` authorized to upload files via API? +- Are there separate permissions for read vs. write operations? +- Is there a different user account or API key required for uploads? + +### 2. File Extension Whitelist +- What file extensions are permitted for uploads via `/v6/assets` endpoint? +- Is there a server-side whitelist configuration? +- Can this be configured per user or globally? + +### 3. Alternative Upload Methods +- Is there a different API endpoint for uploads? (e.g., `/assets` without `/v6/`?) +- Are there import templates we should be using instead of direct uploads? +- What method does the web interface use for uploads? + +### 4. Metadata Requirements +- What is the minimum required metadata structure for uploads? +- Are specific metadata fields mandatory for the ECOMMERCE model? +- Can uploads inherit metadata from the parent folder? + +### 5. Environment Configuration +- Are API uploads enabled in the production environment? +- Are there rate limits or quota restrictions? +- Are uploads restricted to certain folders or campaigns? + +--- + +## Additional Context + +### Master Asset Metadata Size Issue + +Master assets contain extensive metadata structures (brands, markets, campaigns, compliance data, etc.) that are **too large to transmit** in upload requests: +- Causes PHP memory issues when stored in session +- JSON payload becomes several MB in size +- Server returns HTTP 502 (Bad Gateway) when full metadata sent + +**Current Approach:** Only transmit essential fields (model ID, security policies) and rely on folder inheritance for other metadata. + +--- + +## Working Example from Postman Collection + +The V3 Postman collection includes a "Create Assets and Set Metadata" request with example payload. However: + +1. The example uses `import_template_id: 'MARKETING.ASSET'` which returns "No permissable import template found" +2. The example includes extensive metadata fields +3. We cannot test this request in Postman as we don't have the Postman environment + +--- + +## Recommended Next Steps + +### For Ferrero Support Team: + +1. **Verify API upload permissions** for user `scpi.user1` in production environment +2. **Check file extension whitelist** configuration for `/v6/assets` endpoint +3. **Provide list of allowed file extensions** for API uploads +4. **Confirm correct upload endpoint** and required parameters +5. **Share working example** of successful upload via API (if available) + +### For Development Team (Once Resolved): + +1. Update AssetUploader with correct endpoint and parameters +2. Implement proper metadata copying from master assets +3. Add support for metadata field updates (language, market, etc.) +4. Test upload workflow end-to-end (A2→A3) +5. Implement rework upload workflow (A5→A6) + +--- + +## Code Repository + +**Bitbucket:** bitbucket.org:zlalani/ferrero-opentext.git +**Branch:** main +**Latest Commit:** 0ebdf5c - "Disable master assets fetching in upload" + +**Key Files:** +- `workflow_v3.php` - Main workflow interface +- `src/AssetUploader.php` - Upload implementation +- `src/StatusManager.php` - Status updates (working) +- `config_v3.php` - Configuration + +--- + +## Contact Information + +**Developer:** Working through Claude Code +**Client:** [Your contact information] +**Issue Tracker:** GitHub Issues (if applicable) + +--- + +## Appendix: Full Error Log + +### Sample Upload Request (Simplified) + +**URL:** +``` +POST https://ppr.dam.ferrero.com/otmmapi/v6/assets +``` + +**Headers:** +``` +Accept: application/json +Authorization: Bearer [valid_oauth_token] +Content-Type: multipart/form-data; boundary=----WebKitFormBoundary... +``` + +**Form Fields:** +``` +asset_representation: {"asset_resource":{"asset":{"name":"test.jpg","security_policy_list":[{"id":1594}]}}} +manifest: {"upload_manifest":{"master_files":[{"file":{"file_name":"test.jpg","file_type":"image/jpeg"}}]}} +parent_folder_id: ea0dbf86e13e3634895746d3a986558ec2eb8be1 +files: [binary file data] +``` + +**Response:** +```json +{ + "exception_body": { + "http_response_code": 500, + "message": "Cannot import asset having restricted file extension.", + "debug_message": "Asset creation failed due to restricted file extension", + "error_code": 1000 + } +} +``` + +--- + +**End of Document** diff --git a/workflow_v3.php b/workflow_v3.php index 6ba9977..7f58d56 100644 --- a/workflow_v3.php +++ b/workflow_v3.php @@ -263,10 +263,53 @@ if ($_POST && $testRunner) { if ($uploadFolderId) { $results['upload_folder_id'] = $uploadFolderId; - $success = "Found upload target folder"; - // TODO: Master assets fetching disabled - causes PHP crashes due to metadata size - // Will re-enable once upload file extension issue is resolved + // Get Master Assets to copy their metadata + try { + $masterFolderId = findMasterAssetsFolder($testRunner, $campaignId, $configV3); + if ($masterFolderId) { + $fullMasterAssets = getAssetsFromFolder($testRunner, $masterFolderId); + + // Store lightweight version - only IDs and basic info + $results['master_assets_for_upload'] = []; + if (!empty($fullMasterAssets)) { + foreach ($fullMasterAssets as $asset) { + // Only store minimal fields - no huge metadata structure + $lightAsset = [ + 'asset_id' => $asset['asset_id'] ?? null, + 'name' => $asset['name'] ?? 'Unknown', + 'mime_type' => $asset['mime_type'] ?? null + ]; + + // Safely extract metadata_model_id if it exists + if (isset($asset['metadata_model_id'])) { + $lightAsset['metadata_model_id'] = $asset['metadata_model_id']; + } + + // Safely extract security policy IDs only + if (isset($asset['security_policy_list']) && is_array($asset['security_policy_list'])) { + $policyIds = []; + foreach ($asset['security_policy_list'] as $policy) { + if (isset($policy['id'])) { + $policyIds[] = ['id' => $policy['id']]; + } + } + if (!empty($policyIds)) { + $lightAsset['security_policy_list'] = $policyIds; + } + } + + $results['master_assets_for_upload'][] = $lightAsset; + } + } + + $success = "Found upload folder and " . count($results['master_assets_for_upload']) . " master assets for reference"; + } else { + $success = "Found upload target folder (no master assets folder found)"; + } + } catch (Exception $e) { + $success = "Found upload target folder (master assets error: " . $e->getMessage() . ")"; + } } else { $error = "Upload folder not found in campaign"; }