Initial commit: Meta File Server Query Application

- Flask web application for querying Oliver metafile server
- Replicates Make.com workflow for job data retrieval
- Two-stage process: XML client extraction → JSON data retrieval
- Clean HTML interface with responsive design
- Comprehensive error handling and SSL fixes
- Complete documentation and installation guides

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
DJP 2025-09-09 15:26:40 -04:00
commit dd77b199e0
9 changed files with 2571 additions and 0 deletions

62
.gitignore vendored Normal file
View file

@ -0,0 +1,62 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Virtual Environment
meta-server-venv/
venv/
env/
ENV/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Logs
*.log
# Flask
instance/
.webassets-cache
# Environment variables
.env
.env.local
.env.production
# Temporary files
*.tmp
*.temp

File diff suppressed because it is too large Load diff

285
INSTALL.md Normal file
View file

@ -0,0 +1,285 @@
# Installation Guide - Meta File Server Query Application
This guide will help you install and run the Meta File Server Query application on different systems.
## Prerequisites
### Required Software
- **Python 3.7 or higher** - [Download Python](https://www.python.org/downloads/)
- **Git** (optional) - For cloning the repository
- **Modern web browser** - Chrome, Firefox, Safari, or Edge
### System Requirements
- **RAM**: 512MB minimum, 1GB recommended
- **Storage**: 100MB free space
- **Network**: Internet access to reach metafile.oliver.solutions
## Installation Methods
### Method 1: Quick Install (Recommended)
1. **Download/Copy the application folder** to your desired location
2. **Open Terminal/Command Prompt** and navigate to the folder:
```bash
cd /path/to/META-FILE-SERVER-QUERY
```
3. **Run the startup script**:
```bash
./start.sh
```
This will automatically:
- Create the virtual environment
- Install all dependencies
- Start the application
### Method 2: Manual Installation
1. **Navigate to the application directory**:
```bash
cd /path/to/META-FILE-SERVER-QUERY
```
2. **Create Python virtual environment**:
```bash
python3 -m venv meta-server-venv
```
3. **Activate the virtual environment**:
**On macOS/Linux**:
```bash
source meta-server-venv/bin/activate
```
**On Windows**:
```cmd
meta-server-venv\Scripts\activate
```
4. **Install dependencies**:
```bash
pip install -r requirements.txt
```
5. **Run the application**:
```bash
python run.py
```
## Platform-Specific Instructions
### macOS
1. **Install Python 3** (if not already installed):
```bash
# Using Homebrew (recommended)
brew install python3
# Or download from python.org
```
2. **Make startup script executable**:
```bash
chmod +x start.sh
```
3. **Run the application**:
```bash
./start.sh
```
### Windows
1. **Install Python 3** from [python.org](https://www.python.org/downloads/windows/)
- ✅ Check "Add Python to PATH" during installation
2. **Open Command Prompt or PowerShell**:
```cmd
cd C:\path\to\META-FILE-SERVER-QUERY
```
3. **Create virtual environment**:
```cmd
python -m venv meta-server-venv
```
4. **Activate virtual environment**:
```cmd
meta-server-venv\Scripts\activate
```
5. **Install dependencies**:
```cmd
pip install -r requirements.txt
```
6. **Run application**:
```cmd
python run.py
```
### Linux (Ubuntu/Debian)
1. **Install Python 3 and pip**:
```bash
sudo apt update
sudo apt install python3 python3-pip python3-venv
```
2. **Navigate and setup**:
```bash
cd /path/to/META-FILE-SERVER-QUERY
chmod +x start.sh
./start.sh
```
### Linux (CentOS/RHEL/Fedora)
1. **Install Python 3**:
```bash
# CentOS/RHEL
sudo yum install python3 python3-pip
# Fedora
sudo dnf install python3 python3-pip
```
2. **Run setup**:
```bash
cd /path/to/META-FILE-SERVER-QUERY
chmod +x start.sh
./start.sh
```
## Docker Installation (Optional)
If you prefer using Docker:
1. **Create Dockerfile**:
```dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY . /app
RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python", "run.py"]
```
2. **Build and run**:
```bash
docker build -t meta-file-server .
docker run -p 5000:5000 meta-file-server
```
## Verification
After installation, verify the application is working:
1. **Check the startup output** - you should see:
```
Meta File Server Query Application
====================================
Starting server on http://localhost:5000
Press Ctrl+C to stop the server
```
2. **Open your web browser** and go to: `http://localhost:5000`
3. **Test with a job number** (e.g., `6046034`)
4. **Check the API directly**: `http://localhost:5000/api/health`
## Troubleshooting Installation
### Python Not Found
**Error**: `python3: command not found`
**Solution**:
- Install Python 3 from python.org
- On Windows, try `python` instead of `python3`
- Make sure Python is added to your system PATH
### Permission Denied
**Error**: `Permission denied: ./start.sh`
**Solution**:
```bash
chmod +x start.sh
```
### Module Not Found
**Error**: `ModuleNotFoundError: No module named 'flask'`
**Solution**:
- Make sure you activated the virtual environment
- Run: `pip install -r requirements.txt`
### Port Already in Use
**Error**: `OSError: [Errno 48] Address already in use`
**Solution**:
- Stop any other applications using port 5000
- Or modify `run.py` to use a different port:
```python
app.run(debug=True, host='0.0.0.0', port=5001) # Change port
```
### SSL Certificate Warnings
**Warning**: `InsecureRequestWarning: Unverified HTTPS request`
**This is expected** - The application disables SSL verification for the metafile server due to certificate issues. This is safe for this specific use case.
## Uninstallation
To remove the application:
1. **Stop the application** (Ctrl+C)
2. **Delete the entire folder**:
```bash
rm -rf /path/to/META-FILE-SERVER-QUERY
```
The application is self-contained in its directory and doesn't install anything system-wide.
## Moving to Another Machine
To transfer the application to another computer:
1. **Copy the entire META-FILE-SERVER-QUERY folder**
2. **Install Python 3** on the target machine
3. **Run the installation** using Method 1 or 2 above
The `meta-server-venv` folder can be deleted before copying - it will be recreated during installation.
## Production Deployment
For production use on a server:
1. **Install using Method 2** (manual installation)
2. **Install a production WSGI server**:
```bash
pip install gunicorn
```
3. **Run with Gunicorn**:
```bash
gunicorn -w 4 -b 0.0.0.0:5000 app:app
```
4. **Set up reverse proxy** (nginx/Apache) and SSL certificate
5. **Configure firewall** to allow traffic on your chosen port
## Support
If you encounter issues during installation:
1. Check the **Troubleshooting** section above
2. Verify your Python version: `python3 --version`
3. Check that all required files are present in the application directory
4. Ensure you have internet connectivity to download dependencies
---
**Need help?** The application includes comprehensive error messages and logging to help diagnose issues.

151
README.md Normal file
View file

@ -0,0 +1,151 @@
# Meta File Server Query Application
A Python Flask web application that replicates the Make.com workflow for querying the Oliver metafile server to retrieve job information based on deliverable numbers.
## Features
- **Two-stage workflow**: First fetches XML to extract client information, then retrieves JSON data
- **Secure authentication**: Uses MD5 checksum authentication matching the original blueprint
- **Clean web interface**: Simple HTML form with responsive design
- **Error handling**: Comprehensive error handling for network issues, SSL problems, and parsing errors
- **Portable**: Self-contained with virtual environment for easy deployment
## Quick Start
### Option 1: Use the startup script
```bash
./start.sh
```
### Option 2: Manual setup
```bash
# Create and activate virtual environment
python3 -m venv meta-server-venv
source meta-server-venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Run the application
python run.py
```
Then open your browser to: `http://localhost:5000`
## Usage
1. Enter a job/deliverable number in the web form
2. Click "Query Job Information"
3. The application will:
- Fetch the XML file from the metafile server
- Extract the client name from the jobpath
- Retrieve the corresponding JSON data using the client information
- Display the results in a formatted view
## API Endpoints
- `GET /` - Main web interface
- `GET /api/job/<job_number>` - Retrieve job information as JSON
- `GET /api/health` - Health check endpoint
## Example API Response
```json
{
"success": true,
"job_number": "6046034",
"client": "ADIDAS",
"data": {
"JobSpecification": {
// ... job data ...
}
}
}
```
## Configuration
The application uses these default settings:
- **Metafile Server**: `https://metafile.oliver.solutions/getFile`
- **API Key**: `$14W0TF~8FL` (from original blueprint)
- **Port**: 5000
- **SSL Verification**: Disabled (due to certificate issues)
## Project Structure
```
META-FILE-SERVER-QUERY/
├── app.py # Main Flask application
├── run.py # Application runner
├── start.sh # Startup script
├── requirements.txt # Python dependencies
├── templates/
│ └── index.html # Web interface
├── static/ # Static assets (empty)
└── meta-server-venv/ # Python virtual environment
```
## Technical Details
### Workflow Process
1. **XML Retrieval**:
- Constructs URL: `{base_url}?root=XML&path={job_number}.xml&keyid=1&cs={checksum}`
- Checksum: `MD5(api_key + "XML/" + filename)`
2. **Client Extraction**:
- Parses XML for `Set-Property` with `Property="jobpath"`
- Extracts client name using regex: `([A-Z][A-Z0-9_]*?)(?=/CAMPAIGNS/)`
3. **JSON Retrieval**:
- Constructs URL: `{base_url}?root=JSON_STORE&sub={client}&path={job_number}.json&keyid=1&cs={checksum}`
- Checksum: `MD5(api_key + "JSON_STORE/" + client + "/" + filename)`
### Error Handling
- SSL certificate verification disabled for metafile.oliver.solutions
- Network timeout protection (30 seconds)
- XML parsing error handling
- JSON parsing error handling
- HTTP status code validation
## Moving to Production
To deploy this application:
1. **Copy the entire directory** to your target server
2. **Install Python 3.7+** on the target system
3. **Run the startup script**: `./start.sh`
4. **For production**: Consider using a WSGI server like Gunicorn:
```bash
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:5000 app:app
```
## Troubleshooting
### SSL Certificate Issues
The application automatically handles SSL certificate verification issues by disabling verification. This is necessary because the metafile server has certificate problems.
### Connection Timeouts
The application has a 30-second timeout for requests. If you experience frequent timeouts, check your network connection to the metafile server.
### Missing Job Data
If a job number returns an error, verify:
1. The job number exists in the XML system
2. The corresponding JSON file exists in the JSON_STORE
3. The client name extraction is working correctly
## Development
To modify or extend the application:
1. Activate the virtual environment: `source meta-server-venv/bin/activate`
2. Make your changes to `app.py` or `templates/index.html`
3. Test your changes: `python run.py`
4. The server will automatically reload in debug mode
---
*Converted from Make.com blueprint: "AI COMPANION Metafile Server Grabber"*

197
app.py Normal file
View file

@ -0,0 +1,197 @@
import hashlib
import re
import requests
import xml.etree.ElementTree as ET
from flask import Flask, render_template, jsonify, request
from urllib.parse import quote
import urllib3
# Disable SSL warnings when using verify=False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
app = Flask(__name__)
# Configuration
METAFILE_BASE_URL = "https://metafile.oliver.solutions/getFile"
METAFILE_KEY = "$14W0TF~8FL"
class MetafileClient:
def __init__(self, base_url, api_key):
self.base_url = base_url
self.api_key = api_key
def _generate_checksum(self, root, path, sub=None):
"""Generate MD5 checksum for metafile API authentication"""
if sub:
checksum_string = f"{self.api_key}{root}/{sub}/{path}"
else:
checksum_string = f"{self.api_key}{root}/{path}"
return hashlib.md5(checksum_string.encode('utf-8')).hexdigest()
def get_file(self, root, path, keyid=1, sub=None):
"""Make request to metafile server"""
checksum = self._generate_checksum(root, path, sub)
params = {
'root': root,
'path': path,
'keyid': keyid,
'cs': checksum
}
if sub:
params['sub'] = sub
try:
response = requests.get(self.base_url, params=params, timeout=30, verify=False)
response.raise_for_status()
return {
'success': True,
'data': response.text,
'status_code': response.status_code
}
except requests.exceptions.RequestException as e:
return {
'success': False,
'error': str(e),
'status_code': getattr(e.response, 'status_code', None) if hasattr(e, 'response') else None
}
def get_client_from_xml(self, deliverable_number):
"""Get client information from XML file"""
xml_filename = f"{deliverable_number}.xml"
result = self.get_file(root="XML", path=xml_filename)
if not result['success']:
return result
try:
# Parse XML
root_elem = ET.fromstring(result['data'])
# Look for Set-Property elements with Property="jobpath"
jobpath_elements = root_elem.findall(".//Set-Property[@Property='jobpath']")
for prop in jobpath_elements:
jobpath_value = prop.get('Value', '')
if jobpath_value and '/CAMPAIGNS/' in jobpath_value:
# Extract client name from jobpath like "/ADIDAS_CAM_OLIVER_KUALA_LUMPUR/ADIDAS/CAMPAIGNS/..."
# Use regex to extract client name before /CAMPAIGNS
client_pattern = r'([A-Z][A-Z0-9_]*?)(?=/CAMPAIGNS/)'
match = re.search(client_pattern, jobpath_value)
if match:
return {
'success': True,
'client': match.group(1)
}
# Fallback: look for any Value containing /CAMPAIGNS/
set_properties = root_elem.findall(".//Set-Property[@Value]")
for prop in set_properties:
value = prop.get('Value', '')
if '/CAMPAIGNS/' in value:
client_pattern = r'([A-Z][A-Z0-9_]*?)(?=/CAMPAIGNS/)'
match = re.search(client_pattern, value)
if match:
return {
'success': True,
'client': match.group(1)
}
return {
'success': False,
'error': 'Could not extract client information from XML'
}
except ET.ParseError as e:
return {
'success': False,
'error': f'XML parsing error: {str(e)}'
}
except Exception as e:
return {
'success': False,
'error': f'Error processing XML: {str(e)}'
}
def get_job_data(self, deliverable_number):
"""Get complete job data (XML + JSON workflow)"""
# Step 1: Get client from XML
client_result = self.get_client_from_xml(deliverable_number)
if not client_result['success']:
return client_result
client = client_result['client']
# Step 2: Get JSON data using client info
json_filename = f"{deliverable_number}.json"
json_result = self.get_file(root="JSON_STORE", path=json_filename, sub=client)
if not json_result['success']:
return json_result
# Check if response is 200 OK
if json_result['status_code'] != 200:
return {
'success': False,
'error': f'JSON file not found or inaccessible (HTTP {json_result["status_code"]})'
}
try:
import json
json_data = json.loads(json_result['data'])
return {
'success': True,
'client': client,
'data': json_data
}
except json.JSONDecodeError as e:
return {
'success': False,
'error': f'JSON parsing error: {str(e)}'
}
# Initialize metafile client
metafile_client = MetafileClient(METAFILE_BASE_URL, METAFILE_KEY)
@app.route('/')
def index():
"""Serve the main HTML page"""
return render_template('index.html')
@app.route('/api/job/<job_number>')
def get_job_info(job_number):
"""API endpoint to get job information"""
if not job_number:
return jsonify({'error': 'Job number is required'}), 400
# Sanitize job number (remove any non-alphanumeric characters except hyphens and underscores)
job_number = re.sub(r'[^a-zA-Z0-9\-_]', '', job_number)
if not job_number:
return jsonify({'error': 'Invalid job number format'}), 400
result = metafile_client.get_job_data(job_number)
if result['success']:
return jsonify({
'success': True,
'job_number': job_number,
'client': result['client'],
'data': result['data']
})
else:
return jsonify({
'success': False,
'job_number': job_number,
'error': result['error']
}), 400
@app.route('/api/health')
def health_check():
"""Health check endpoint"""
return jsonify({'status': 'healthy', 'service': 'Meta File Server Query'})
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)

13
requirements.txt Normal file
View file

@ -0,0 +1,13 @@
blinker==1.9.0
certifi==2025.8.3
charset-normalizer==3.4.3
click==8.2.1
Flask==3.1.2
idna==3.10
itsdangerous==2.2.0
Jinja2==3.1.6
lxml==6.0.1
MarkupSafe==3.0.2
requests==2.32.5
urllib3==2.5.0
Werkzeug==3.1.3

34
run.py Normal file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env python3
"""
Meta File Server Query Application
A simple Flask application that replicates the Make.com workflow for querying
the Oliver metafile server to retrieve job information based on deliverable numbers.
Usage:
python run.py
The server will start on http://localhost:5000
"""
import os
import sys
# Add the current directory to Python path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from app import app
if __name__ == '__main__':
print("=" * 60)
print("Meta File Server Query Application")
print("=" * 60)
print("Starting server on http://localhost:5000")
print("Press Ctrl+C to stop the server")
print("=" * 60)
try:
app.run(debug=True, host='0.0.0.0', port=5000)
except KeyboardInterrupt:
print("\n\nServer stopped by user.")
sys.exit(0)

19
start.sh Executable file
View file

@ -0,0 +1,19 @@
#!/bin/bash
# Meta File Server Query - Start Script
echo "Starting Meta File Server Query Application..."
# Check if virtual environment exists
if [ ! -d "meta-server-venv" ]; then
echo "Creating virtual environment..."
python3 -m venv meta-server-venv
echo "Installing dependencies..."
source meta-server-venv/bin/activate
pip install -r requirements.txt
else
echo "Activating virtual environment..."
source meta-server-venv/bin/activate
fi
echo "Starting Flask application..."
python run.py

272
templates/index.html Normal file
View file

@ -0,0 +1,272 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meta File Server Query</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
border-bottom: 3px solid #007bff;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
input[type="text"] {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
}
input[type="text"]:focus {
border-color: #007bff;
outline: none;
}
button {
background-color: #007bff;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
margin-right: 10px;
}
button:hover {
background-color: #0056b3;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.loading {
display: none;
margin: 20px 0;
text-align: center;
color: #666;
}
.result {
margin-top: 20px;
padding: 20px;
border-radius: 4px;
display: none;
}
.result.success {
background-color: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.result.error {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
.json-data {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 4px;
padding: 15px;
margin-top: 10px;
font-family: 'Courier New', monospace;
white-space: pre-wrap;
overflow-x: auto;
max-height: 400px;
overflow-y: auto;
}
.client-info {
background-color: #e7f3ff;
border: 1px solid #b8daff;
border-radius: 4px;
padding: 10px;
margin: 10px 0;
color: #004085;
}
.buttons {
display: flex;
gap: 10px;
}
@media (max-width: 768px) {
body {
padding: 10px;
}
.container {
padding: 20px;
}
.buttons {
flex-direction: column;
}
button {
margin-right: 0;
margin-bottom: 10px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>Meta File Server Query</h1>
<form id="queryForm">
<div class="form-group">
<label for="jobNumber">Job Number / Deliverable Number:</label>
<input type="text" id="jobNumber" name="jobNumber" placeholder="Enter job number (e.g., 12345)" required>
</div>
<div class="buttons">
<button type="submit">Query Job Information</button>
<button type="button" onclick="clearResults()">Clear</button>
</div>
</form>
<div class="loading" id="loading">
<p>🔍 Querying meta file server...</p>
</div>
<div class="result" id="result">
<div id="resultContent"></div>
</div>
</div>
<script>
const form = document.getElementById('queryForm');
const loading = document.getElementById('loading');
const result = document.getElementById('result');
const resultContent = document.getElementById('resultContent');
const jobNumberInput = document.getElementById('jobNumber');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const jobNumber = jobNumberInput.value.trim();
if (!jobNumber) {
showError('Please enter a job number');
return;
}
// Show loading state
loading.style.display = 'block';
result.style.display = 'none';
try {
const response = await fetch(`/api/job/${encodeURIComponent(jobNumber)}`);
const data = await response.json();
loading.style.display = 'none';
if (data.success) {
showSuccess(data);
} else {
showError(data.error || 'Unknown error occurred');
}
} catch (error) {
loading.style.display = 'none';
showError('Network error: ' + error.message);
}
});
function showSuccess(data) {
result.className = 'result success';
result.style.display = 'block';
let html = `
<h3>✅ Job Information Retrieved</h3>
<p><strong>Job Number:</strong> ${escapeHtml(data.job_number)}</p>
`;
if (data.client) {
html += `
<div class="client-info">
<strong>Client:</strong> ${escapeHtml(data.client)}
</div>
`;
}
if (data.data) {
html += `
<p><strong>Data:</strong></p>
<div class="json-data">${escapeHtml(JSON.stringify(data.data, null, 2))}</div>
`;
}
resultContent.innerHTML = html;
}
function showError(error) {
result.className = 'result error';
result.style.display = 'block';
resultContent.innerHTML = `
<h3>❌ Error</h3>
<p>${escapeHtml(error)}</p>
`;
}
function clearResults() {
jobNumberInput.value = '';
result.style.display = 'none';
loading.style.display = 'none';
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Auto-focus on job number input
jobNumberInput.focus();
// Allow Enter key to submit
jobNumberInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
form.dispatchEvent(new Event('submit'));
}
});
</script>
</body>
</html>