# Goondex Frontend API Guide **For Frontend Developers** This guide explains how to interact with the Goondex API using JavaScript. No backend knowledge required - just JavaScript, HTML, CSS, and Bootstrap skills! ## Table of Contents 1. [Getting Started](#getting-started) 2. [Base URL](#base-url) 3. [Data Models](#data-models) 4. [API Endpoints](#api-endpoints) 5. [Common Workflows](#common-workflows) 6. [Error Handling](#error-handling) 7. [Real-time Progress Updates](#real-time-progress-updates) 8. [Complete Examples](#complete-examples) --- ## Getting Started All API endpoints return JSON data. You can use the `fetch` API to make requests from your JavaScript code. ### Basic API Response Format All API responses follow this structure: ```javascript { "success": true, // boolean - whether the operation succeeded "message": "Success text", // string - human-readable message "data": { ... } // object - actual data (optional) } ``` --- ## Base URL The API server runs at: `http://localhost:8080` (by default) All endpoints are prefixed with this URL. --- ## Data Models ### Performer ```javascript { "id": 123, "name": "Performer Name", "aliases": "Alias1, Alias2", // Physical Attributes "gender": "female", // male/female/trans/other "birthday": "1995-03-15", // YYYY-MM-DD "astrology": "Pisces", "birthplace": "Los Angeles, CA", "ethnicity": "Caucasian", "nationality": "US", // ISO country code (US, GB, FR, etc.) "country": "United States", "eye_color": "Blue", "hair_color": "Blonde", "height": 165, // centimeters "weight": 55, // kilograms "measurements": "34C-24-36", "cup_size": "34C", "tattoo_description": "Dragon on left shoulder", "piercing_description": "Nose piercing", "boob_job": "False", // "True" or "False" as string // Career "career": "2015-2023", "career_start_year": 2015, "career_end_year": 2023, "date_of_death": "", // YYYY-MM-DD if applicable "active": true, // Media "image_path": "/path/to/image.jpg", "image_url": "https://example.com/image.jpg", "poster_url": "https://example.com/poster.jpg", "bio": "Biography text...", // Metadata "source": "tpdb", "source_id": "abc-123-def", "source_numeric_id": 456, "created_at": "2024-01-01T12:00:00Z", "updated_at": "2024-01-02T12:00:00Z" } ``` ### Studio ```javascript { "id": 456, "name": "Studio Name", "parent_id": 123, // null if no parent studio "image_path": "/path/to/logo.jpg", "image_url": "https://example.com/logo.jpg", "description": "Studio description...", "source": "tpdb", "source_id": "xyz-789", "created_at": "2024-01-01T12:00:00Z", "updated_at": "2024-01-02T12:00:00Z" } ``` ### Scene ```javascript { "id": 789, "title": "Scene Title", "code": "SCENE-001", // DVD code or scene identifier "date": "2024-01-15", // Release date YYYY-MM-DD "studio_id": 456, "description": "Scene description...", "image_path": "/path/to/thumbnail.jpg", "image_url": "https://example.com/thumbnail.jpg", "director": "Director Name", "url": "https://example.com/scene", "source": "tpdb", "source_id": "scene-123", "created_at": "2024-01-01T12:00:00Z", "updated_at": "2024-01-02T12:00:00Z", // Relationships (when populated) "performers": [ /* array of Performer objects */ ], "tags": [ /* array of Tag objects */ ], "studio": { /* Studio object */ } } ``` ### Movie ```javascript { "id": 321, "title": "Movie Title", "date": "2024-01-01", // Release date "studio_id": 456, "description": "Movie description...", "director": "Director Name", "duration": 120, // Duration in minutes "image_path": "/path/to/cover.jpg", "image_url": "https://example.com/cover.jpg", "back_image_url": "https://example.com/back-cover.jpg", "url": "https://example.com/movie", "source": "tpdb", "source_id": "movie-456", "created_at": "2024-01-01T12:00:00Z", "updated_at": "2024-01-02T12:00:00Z", // Relationships (when populated) "scenes": [ /* array of Scene objects */ ], "performers": [ /* array of Performer objects */ ], "tags": [ /* array of Tag objects */ ], "studio": { /* Studio object */ } } ``` ### Tag ```javascript { "id": 111, "name": "Tag Name", "category_id": 5, "aliases": "Alias1, Alias2", "description": "Tag description...", "source": "tpdb", "source_id": "tag-789", "created_at": "2024-01-01T12:00:00Z", "updated_at": "2024-01-02T12:00:00Z" } ``` --- ## API Endpoints ### Search & Import #### 1. Import Performer by Search **Endpoint:** `POST /api/import/performer` **Description:** Search for a performer and import all matching results from TPDB. **Request Body:** ```javascript { "query": "performer name" } ``` **Example:** ```javascript const response = await fetch('http://localhost:8080/api/import/performer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: 'Jane Doe' }) }); const result = await response.json(); // result = { // "success": true, // "message": "Imported 5 performer(s)", // "data": { "imported": 5, "found": 5 } // } ``` #### 2. Import Studio by Search **Endpoint:** `POST /api/import/studio` **Request Body:** ```javascript { "query": "studio name" } ``` **Example:** ```javascript const response = await fetch('http://localhost:8080/api/import/studio', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: 'Brazzers' }) }); const result = await response.json(); ``` #### 3. Import Scene by Search **Endpoint:** `POST /api/import/scene` **Description:** Search for a scene and import all matching results. This also imports associated performers, studio, and tags. **Request Body:** ```javascript { "query": "scene title" } ``` **Example:** ```javascript const response = await fetch('http://localhost:8080/api/import/scene', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: 'Scene Title' }) }); const result = await response.json(); ``` ### Bulk Import #### 4. Bulk Import All **Endpoint:** `POST /api/import/all` **Description:** Import all performers, studios, and scenes from your local database. This fetches full metadata from TPDB. **No Request Body Required** **Example:** ```javascript const response = await fetch('http://localhost:8080/api/import/all', { method: 'POST' }); const result = await response.json(); // result.data contains import statistics ``` #### 5. Bulk Import All Performers **Endpoint:** `POST /api/import/all-performers` **Example:** ```javascript const response = await fetch('http://localhost:8080/api/import/all-performers', { method: 'POST' }); const result = await response.json(); // result = { // "success": true, // "message": "Imported 150/200 performers", // "data": { // "total": 200, // "imported": 150, // "skipped": 50, // "errors": 0 // } // } ``` #### 6. Bulk Import All Studios **Endpoint:** `POST /api/import/all-studios` **Example:** ```javascript const response = await fetch('http://localhost:8080/api/import/all-studios', { method: 'POST' }); const result = await response.json(); ``` #### 7. Bulk Import All Scenes **Endpoint:** `POST /api/import/all-scenes` **Example:** ```javascript const response = await fetch('http://localhost:8080/api/import/all-scenes', { method: 'POST' }); const result = await response.json(); ``` ### Sync #### 8. Sync All Data **Endpoint:** `POST /api/sync` **Description:** Synchronize all data with TPDB to get the latest updates. **Request Body (Optional):** ```javascript { "force": false // Set to true to force sync even if recently synced } ``` **Example:** ```javascript const response = await fetch('http://localhost:8080/api/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force: true }) }); const result = await response.json(); ``` #### 9. Get Sync Status **Endpoint:** `GET /api/sync/status` **Description:** Get the last sync time for all entities. **Example:** ```javascript const response = await fetch('http://localhost:8080/api/sync/status'); const result = await response.json(); // result.data contains sync status for each entity type ``` ### Global Search #### 10. Search Everything **Endpoint:** `GET /api/search?q=query` **Description:** Search across performers, studios, scenes, and tags simultaneously. **Example:** ```javascript const query = 'search term'; const response = await fetch(`http://localhost:8080/api/search?q=${encodeURIComponent(query)}`); const result = await response.json(); // result.data = { // "performers": [...], // "studios": [...], // "scenes": [...], // "tags": [...], // "total": 25 // } ``` --- ## Real-time Progress Updates For bulk imports, there are special endpoints that provide real-time progress updates using Server-Sent Events (SSE). ### Bulk Import with Progress These endpoints stream progress updates as the import happens: - `GET /api/import/all-performers/progress` - `GET /api/import/all-studios/progress` - `GET /api/import/all-scenes/progress` **Example with EventSource:** ```javascript // Create an EventSource to listen for progress updates const eventSource = new EventSource('http://localhost:8080/api/import/all-performers/progress'); eventSource.onmessage = function(event) { const update = JSON.parse(event.data); if (update.error) { console.error('Import error:', update.error); eventSource.close(); return; } if (update.complete) { console.log('Import complete!', update.result); eventSource.close(); return; } // Progress update console.log(`Progress: ${update.current}/${update.total}`); console.log(`Current item: ${update.name}`); console.log(`Status: ${update.status}`); // Update UI updateProgressBar(update.current, update.total); }; eventSource.onerror = function(error) { console.error('EventSource error:', error); eventSource.close(); }; ``` **Progress Update Format:** ```javascript { "current": 15, // Current item number "total": 100, // Total items to process "name": "Jane Doe", // Name of current item being processed "status": "importing" // Status message } ``` **Completion Format:** ```javascript { "complete": true, "result": { "total": 100, "imported": 95, "skipped": 4, "errors": 1 } } ``` --- ## Common Workflows ### 1. Search and Display Performers ```javascript // Search for performers async function searchPerformers(searchQuery) { const response = await fetch( `http://localhost:8080/api/search?q=${encodeURIComponent(searchQuery)}` ); const result = await response.json(); if (result.success) { return result.data.performers; } throw new Error(result.message); } // Display in HTML async function displayPerformers() { const performers = await searchPerformers('jane'); const container = document.getElementById('performers-list'); container.innerHTML = performers.map(p => `
${p.name}
${p.name}

${p.nationality ? getFlagEmoji(p.nationality) : ''} ${p.gender || 'Unknown'}

View Details
`).join(''); } // Helper: Convert country code to flag emoji function getFlagEmoji(countryCode) { const codePoints = countryCode .toUpperCase() .split('') .map(char => 127397 + char.charCodeAt()); return String.fromCodePoint(...codePoints); } ``` ### 2. Import Data with Progress Bar ```javascript async function importPerformersWithProgress() { const progressBar = document.getElementById('progress-bar'); const statusText = document.getElementById('status-text'); const eventSource = new EventSource( 'http://localhost:8080/api/import/all-performers/progress' ); eventSource.onmessage = function(event) { const update = JSON.parse(event.data); if (update.error) { statusText.textContent = `Error: ${update.error}`; progressBar.style.width = '100%'; progressBar.classList.add('bg-danger'); eventSource.close(); return; } if (update.complete) { statusText.textContent = `Complete! Imported ${update.result.imported}/${update.result.total}`; progressBar.style.width = '100%'; progressBar.classList.add('bg-success'); eventSource.close(); return; } // Update progress const percent = (update.current / update.total) * 100; progressBar.style.width = `${percent}%`; statusText.textContent = `Importing ${update.name} (${update.current}/${update.total})`; }; eventSource.onerror = function(error) { statusText.textContent = 'Connection error'; progressBar.classList.add('bg-danger'); eventSource.close(); }; } ``` ### 3. Sync Data ```javascript async function syncAllData(forceSync = false) { const response = await fetch('http://localhost:8080/api/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force: forceSync }) }); const result = await response.json(); if (result.success) { console.log('Sync completed:', result.data); return result.data; } throw new Error(result.message); } // Check last sync status async function checkSyncStatus() { const response = await fetch('http://localhost:8080/api/sync/status'); const result = await response.json(); if (result.success) { console.log('Last sync times:', result.data); return result.data; } throw new Error(result.message); } ``` ### 4. Search and Import New Performer ```javascript async function importNewPerformer(performerName) { const response = await fetch('http://localhost:8080/api/import/performer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: performerName }) }); const result = await response.json(); if (result.success) { alert(`Successfully imported ${result.data.imported} performer(s)`); return result.data; } alert(`Failed: ${result.message}`); throw new Error(result.message); } // Usage with a form document.getElementById('import-form').addEventListener('submit', async (e) => { e.preventDefault(); const performerName = document.getElementById('performer-name').value; await importNewPerformer(performerName); // Refresh the performer list location.reload(); }); ``` --- ## Error Handling ### Best Practices Always check the `success` field in the response: ```javascript async function safeApiCall(url, options = {}) { try { const response = await fetch(url, options); // Check HTTP status if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); // Check API success field if (!result.success) { throw new Error(result.message); } return result.data; } catch (error) { console.error('API Error:', error); throw error; } } // Usage try { const performers = await safeApiCall('http://localhost:8080/api/search?q=jane'); console.log('Performers:', performers); } catch (error) { alert(`Error: ${error.message}`); } ``` ### Common Error Responses **API Key Not Configured:** ```javascript { "success": false, "message": "TPDB_API_KEY not configured" } ``` **No Results Found:** ```javascript { "success": false, "message": "No performers found" } ``` **Search Failed:** ```javascript { "success": false, "message": "Search failed: connection timeout" } ``` --- ## Complete Examples ### Example 1: Search Form with Results ```html Goondex Search

Search Goondex

``` ### Example 2: Import Progress with Bootstrap ```html Import Performers

Import All Performers

``` --- ## Tips for Frontend Developers ### 1. Always Use `encodeURIComponent` for Query Parameters ```javascript // Good const query = 'Jane Doe & Associates'; fetch(`/api/search?q=${encodeURIComponent(query)}`); // Bad - will break with special characters fetch(`/api/search?q=${query}`); ``` ### 2. Handle Image Loading Errors ```javascript ${performer.name} ``` ### 3. Use Bootstrap Classes for Quick Styling ```html
Success!
Error!
Warning!
Loading...
75%
``` ### 4. Debounce Search Input ```javascript function debounce(func, wait) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } // Usage const searchInput = document.getElementById('search'); const debouncedSearch = debounce(performSearch, 300); searchInput.addEventListener('input', (e) => debouncedSearch(e.target.value)); ``` ### 5. Format Dates for Display ```javascript function formatDate(dateString) { if (!dateString) return 'Unknown'; const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); } // Usage console.log(formatDate('2024-01-15')); // "January 15, 2024" ``` ### 6. Calculate Age from Birthday ```javascript function calculateAge(birthday) { if (!birthday) return null; const birthDate = new Date(birthday); const today = new Date(); let age = today.getFullYear() - birthDate.getFullYear(); const monthDiff = today.getMonth() - birthDate.getMonth(); if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { age--; } return age; } // Usage const age = calculateAge('1995-03-15'); console.log(`Age: ${age}`); ``` --- ## Need Help? If you have questions about the API or need clarification: 1. Check the data model structures above 2. Look at the complete examples 3. Test endpoints using browser DevTools Network tab 4. Consult your backend developer if you need custom endpoints Happy coding!