From 744e5d55c647d014308dd192084e05cd5f492d42 Mon Sep 17 00:00:00 2001 From: "IGNY8 VPS (Salman)" Date: Fri, 21 Nov 2025 13:46:31 +0000 Subject: [PATCH] wp api --- backend/celerybeat-schedule | Bin 16384 -> 16384 bytes .../migrations/0002_add_wp_api_key_to_site.py | 19 + backend/igny8_core/auth/models.py | 1 + backend/igny8_core/auth/serializers.py | 2 +- .../sites/WordPressIntegrationForm.tsx | 543 ++++++++++-------- frontend/src/pages/Sites/Settings.tsx | 2 + sample compoeents/sample-api-keys table.html | 1 + 7 files changed, 319 insertions(+), 249 deletions(-) create mode 100644 backend/igny8_core/auth/migrations/0002_add_wp_api_key_to_site.py create mode 100644 sample compoeents/sample-api-keys table.html diff --git a/backend/celerybeat-schedule b/backend/celerybeat-schedule index a040cce0b1fe1e29138af96d3e3aa67adf5eb697..0e4c837fb628186047398f4abab285e0f692ed3a 100644 GIT binary patch delta 35 rcmZo@U~Fh$++b=Zz^lQ)z-B%rL$qy5&=lV!=E(*o%9}IHW^e)kwa*G~ delta 35 rcmZo@U~Fh$++b=Zz-+|8z|cA+L$qy5&=lV{Op^^vls9LX&ENz8!9xo` diff --git a/backend/igny8_core/auth/migrations/0002_add_wp_api_key_to_site.py b/backend/igny8_core/auth/migrations/0002_add_wp_api_key_to_site.py new file mode 100644 index 00000000..103191f4 --- /dev/null +++ b/backend/igny8_core/auth/migrations/0002_add_wp_api_key_to_site.py @@ -0,0 +1,19 @@ +# Generated manually for adding wp_api_key to Site model + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('igny8_core_auth', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='site', + name='wp_api_key', + field=models.CharField(blank=True, help_text='API key for WordPress integration via IGNY8 WP Bridge plugin', max_length=255, null=True), + ), + ] + diff --git a/backend/igny8_core/auth/models.py b/backend/igny8_core/auth/models.py index 8681240d..0f8cfef6 100644 --- a/backend/igny8_core/auth/models.py +++ b/backend/igny8_core/auth/models.py @@ -217,6 +217,7 @@ class Site(AccountBaseModel): wp_url = models.URLField(blank=True, null=True, help_text="WordPress site URL (legacy - use SiteIntegration)") wp_username = models.CharField(max_length=255, blank=True, null=True) wp_app_password = models.CharField(max_length=255, blank=True, null=True) + wp_api_key = models.CharField(max_length=255, blank=True, null=True, help_text="API key for WordPress integration via IGNY8 WP Bridge plugin") # Site type and hosting (Phase 6) SITE_TYPE_CHOICES = [ diff --git a/backend/igny8_core/auth/serializers.py b/backend/igny8_core/auth/serializers.py index 2567ba95..23a1c35a 100644 --- a/backend/igny8_core/auth/serializers.py +++ b/backend/igny8_core/auth/serializers.py @@ -68,7 +68,7 @@ class SiteSerializer(serializers.ModelSerializer): fields = [ 'id', 'name', 'slug', 'domain', 'description', 'industry', 'industry_name', 'industry_slug', - 'is_active', 'status', 'wp_url', 'wp_username', + 'is_active', 'status', 'wp_url', 'wp_username', 'wp_api_key', 'site_type', 'hosting_type', 'seo_metadata', 'sectors_count', 'active_sectors_count', 'selected_sectors', 'can_add_sectors', diff --git a/frontend/src/components/sites/WordPressIntegrationForm.tsx b/frontend/src/components/sites/WordPressIntegrationForm.tsx index aef3d126..7fb64734 100644 --- a/frontend/src/components/sites/WordPressIntegrationForm.tsx +++ b/frontend/src/components/sites/WordPressIntegrationForm.tsx @@ -6,31 +6,33 @@ import React, { useState, useEffect } from 'react'; import { Card } from '../ui/card'; import Button from '../ui/button/Button'; import Label from '../form/Label'; -import Input from '../form/input/Input'; +import Input from '../form/input/InputField'; import Checkbox from '../form/input/Checkbox'; -import Alert from '../ui/alert/Alert'; import { useToast } from '../ui/toast/ToastContainer'; import { integrationApi, SiteIntegration } from '../../services/integration.api'; +import { fetchAPI } from '../../services/api'; import { - Globe, - Key, - Download, - Copy, - CheckCircle, - AlertCircle, - ExternalLink, - RefreshCw -} from 'lucide-react'; + CheckCircleIcon, + AlertIcon, + DownloadIcon, + PlusIcon, + CopyIcon +} from '../../icons'; +import { Globe, Key, RefreshCw } from 'lucide-react'; interface WordPressIntegrationFormProps { siteId: number; integration: SiteIntegration | null; + siteName?: string; + siteUrl?: string; onIntegrationUpdate?: (integration: SiteIntegration) => void; } export default function WordPressIntegrationForm({ siteId, integration, + siteName, + siteUrl, onIntegrationUpdate, }: WordPressIntegrationFormProps) { const toast = useToast(); @@ -38,41 +40,53 @@ export default function WordPressIntegrationForm({ const [generatingKey, setGeneratingKey] = useState(false); const [apiKey, setApiKey] = useState(''); const [apiKeyVisible, setApiKeyVisible] = useState(false); - const [formData, setFormData] = useState({ - url: integration?.config_json?.site_url || '', - username: integration?.credentials_json?.username || '', - app_password: '', - is_active: integration?.is_active ?? true, - sync_enabled: integration?.sync_enabled ?? true, - }); + const [isActive, setIsActive] = useState(integration?.is_active ?? true); + const [syncEnabled, setSyncEnabled] = useState(integration?.sync_enabled ?? true); useEffect(() => { if (integration) { - setFormData({ - url: integration.config_json?.site_url || '', - username: integration.credentials_json?.username || '', - app_password: '', - is_active: integration.is_active ?? true, - sync_enabled: integration.sync_enabled ?? true, - }); - // Load API key if it exists - if (integration.credentials_json?.api_key) { - setApiKey(integration.credentials_json.api_key); - } + setIsActive(integration.is_active ?? true); + setSyncEnabled(integration.sync_enabled ?? true); } }, [integration]); + // Load API key from site settings on mount + useEffect(() => { + loadApiKeyFromSite(); + }, [siteId]); + + const loadApiKeyFromSite = async () => { + try { + const siteData = await fetchAPI(`/v1/auth/sites/${siteId}/`); + if (siteData?.wp_api_key) { + setApiKey(siteData.wp_api_key); + } else { + // Clear API key if it doesn't exist in the backend + setApiKey(''); + } + } catch (error) { + // API key might not exist yet, that's okay + setApiKey(''); + } + }; + const handleGenerateApiKey = async () => { try { setGeneratingKey(true); // Generate API key - format: igny8_site_{siteId}_{timestamp}_{random} - // This will be stored in the integration credentials const timestamp = Date.now(); const random = Math.random().toString(36).substring(2, 15); const token = `igny8_site_${siteId}_${timestamp}_${random}`; setApiKey(token); setApiKeyVisible(true); - toast.success('API key generated successfully. Make sure to save the integration to store this key.'); + + // Save API key to site settings immediately + await saveApiKeyToSite(token); + + // Reload the API key from backend to ensure consistency + await loadApiKeyFromSite(); + + toast.success('API key generated and saved successfully'); } catch (error: any) { toast.error(`Failed to generate API key: ${error.message}`); } finally { @@ -80,6 +94,45 @@ export default function WordPressIntegrationForm({ } }; + const handleRegenerateApiKey = async () => { + if (!confirm('Are you sure you want to regenerate the API key? The old key will no longer work.')) { + return; + } + try { + setGeneratingKey(true); + // Generate new API key + const timestamp = Date.now(); + const random = Math.random().toString(36).substring(2, 15); + const token = `igny8_site_${siteId}_${timestamp}_${random}`; + setApiKey(token); + setApiKeyVisible(true); + + // Save new API key to site settings + await saveApiKeyToSite(token); + + // Reload the API key from backend to ensure consistency + await loadApiKeyFromSite(); + + toast.success('API key regenerated and saved successfully'); + } catch (error: any) { + toast.error(`Failed to regenerate API key: ${error.message}`); + } finally { + setGeneratingKey(false); + } + }; + + const saveApiKeyToSite = async (key: string) => { + try { + await fetchAPI(`/v1/auth/sites/${siteId}/`, { + method: 'PATCH', + body: JSON.stringify({ wp_api_key: key }), + }); + } catch (error: any) { + console.error('Failed to save API key to site:', error); + throw error; + } + }; + const handleCopyApiKey = () => { if (apiKey) { navigator.clipboard.writeText(apiKey); @@ -88,62 +141,69 @@ export default function WordPressIntegrationForm({ }; const handleDownloadPlugin = () => { - // TODO: Replace with actual plugin download URL const pluginUrl = `https://github.com/igny8/igny8-wp-bridge/releases/latest/download/igny8-wp-bridge.zip`; window.open(pluginUrl, '_blank'); toast.success('Plugin download started'); }; - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!formData.url || !formData.username || !formData.app_password) { - toast.error('Please fill in all required fields'); - return; - } - - if (!apiKey) { - toast.error('Please generate an API key first'); - return; - } - + const handleSaveSettings = async () => { try { setLoading(true); - // Save integration with API key in credentials - const updatedIntegration = await integrationApi.saveWordPressIntegration(siteId, { - url: formData.url, - username: formData.username, - app_password: formData.app_password, - api_key: apiKey, // Include API key in credentials - is_active: formData.is_active, - sync_enabled: formData.sync_enabled, - }); - if (onIntegrationUpdate) { - onIntegrationUpdate(updatedIntegration); + // Update integration with active/sync settings + if (integration) { + await integrationApi.updateIntegration(integration.id, { + is_active: isActive, + sync_enabled: syncEnabled, + } as any); + } else { + // Create integration if it doesn't exist + await integrationApi.saveWordPressIntegration(siteId, { + url: siteUrl || '', + username: '', + app_password: '', + api_key: apiKey, + is_active: isActive, + sync_enabled: syncEnabled, + }); } - toast.success('WordPress integration saved successfully'); + // Reload integration + const updated = await integrationApi.getWordPressIntegration(siteId); + if (onIntegrationUpdate && updated) { + onIntegrationUpdate(updated); + } + + toast.success('Integration settings saved successfully'); } catch (error: any) { - toast.error(`Failed to save integration: ${error.message}`); + toast.error(`Failed to save settings: ${error.message}`); } finally { setLoading(false); } }; const handleTestConnection = async () => { - if (!integration?.id) { - toast.error('Please save the integration first'); + if (!apiKey || !siteUrl) { + toast.error('Please ensure API key and site URL are configured'); return; } try { setLoading(true); - const result = await integrationApi.testIntegration(integration.id); - if (result.success) { + // Test connection using API key and site URL + const result = await fetchAPI(`/v1/integration/integrations/test-connection/`, { + method: 'POST', + body: JSON.stringify({ + site_id: siteId, + api_key: apiKey, + site_url: siteUrl, + }), + }); + + if (result?.success) { toast.success('Connection test successful'); } else { - toast.error(`Connection test failed: ${result.message}`); + toast.error(`Connection test failed: ${result?.message || 'Unknown error'}`); } } catch (error: any) { toast.error(`Connection test failed: ${error.message}`); @@ -152,6 +212,12 @@ export default function WordPressIntegrationForm({ } }; + const maskApiKey = (key: string) => { + if (!key) return ''; + if (key.length <= 12) return key; + return key.substring(0, 8) + '**********' + key.substring(key.length - 4); + }; + return (
{/* Header */} @@ -169,214 +235,191 @@ export default function WordPressIntegrationForm({
- {/* Integration Guide */} - -
    -
  1. Generate an API key for your site
  2. -
  3. Download and install the IGNY8 WP Bridge plugin
  4. -
  5. Enter your WordPress site URL and credentials
  6. -
  7. Configure the plugin with your API key
  8. -
-
- - {/* API Key Section */} - -
-
-
-

- - API Key -

-

- Generate an API key to authenticate the WordPress plugin with IGNY8 -

-
- + {/* API Keys Table */} + +
+
+

API Keys

+

+ API keys are used to authenticate requests to the IGNY8 API +

- - {apiKey && ( -
-
- -
- - - -
-

- Keep this key secure. You'll need it to configure the WordPress plugin. -

-
+ {!apiKey && ( +
+
)}
+ + {apiKey ? ( +
+ + + + + + + + + + + + + + + + + +
NameStatusCreatedAction
+
+ +
+
+ + +
+
+ +
+
+
+ Regenerate +
+
+
+
+
+ +
+
+
+ + Active + + + {new Date().toLocaleDateString('en-US', { day: 'numeric', month: 'short', year: 'numeric' })} + +
+ +
+
+
+ ) : ( +
+

No API key generated yet. Click "Add API Key" to generate one.

+
+ )} {/* Plugin Download Section */} {apiKey && ( -
-
-
-

- - IGNY8 WP Bridge Plugin -

-

- Download and install the plugin on your WordPress site -

-
- +
+
+

+ + IGNY8 WP Bridge Plugin +

+

+ Download and install the plugin on your WordPress site +

- - -
-

Installation Instructions:

-
    -
  1. Download the plugin ZIP file
  2. -
  3. Go to WordPress Admin → Plugins → Add New
  4. -
  5. Click "Upload Plugin" and select the ZIP file
  6. -
  7. Activate the plugin
  8. -
  9. Go to Settings → IGNY8 Bridge and enter your API key
  10. -
-
-
+ + Download Plugin +
)} - {/* WordPress Credentials Form */} + {/* Integration Settings */} -
+

- WordPress Site Configuration + Integration Settings

-
- - setFormData({ ...formData, url: e.target.value })} - placeholder="https://example.com" - required - className="mt-1" - /> -

- Enter your WordPress site URL (with https://) -

-
- -
- - setFormData({ ...formData, username: e.target.value })} - placeholder="admin" - required - className="mt-1" - /> -

- WordPress administrator username -

-
- -
- - setFormData({ ...formData, app_password: e.target.value })} - placeholder="xxxx xxxx xxxx xxxx xxxx xxxx" - required - className="mt-1" - /> -

- Create an Application Password in WordPress: Users → Profile → Application Passwords -

-
- -
+ {/* Checkboxes at the top */} +
setFormData({ ...formData, is_active: e.target.checked })} + checked={isActive} + onChange={(checked) => setIsActive(checked)} label="Enable Integration" /> setFormData({ ...formData, sync_enabled: e.target.checked })} + checked={syncEnabled} + onChange={(checked) => setSyncEnabled(checked)} label="Enable Two-Way Sync" />
-
- {integration && ( +
+ {apiKey && siteUrl && (
- +
{/* Integration Status */} @@ -406,9 +454,9 @@ export default function WordPressIntegrationForm({

Status

{integration.is_active ? ( - + ) : ( - + )} {integration.is_active ? 'Active' : 'Inactive'} @@ -419,9 +467,9 @@ export default function WordPressIntegrationForm({

Sync Status

{integration.sync_status === 'success' ? ( - + ) : integration.sync_status === 'failed' ? ( - + ) : ( )} @@ -445,4 +493,3 @@ export default function WordPressIntegrationForm({
); } - diff --git a/frontend/src/pages/Sites/Settings.tsx b/frontend/src/pages/Sites/Settings.tsx index 1082e062..972857ea 100644 --- a/frontend/src/pages/Sites/Settings.tsx +++ b/frontend/src/pages/Sites/Settings.tsx @@ -540,6 +540,8 @@ export default function SiteSettings() { )} diff --git a/sample compoeents/sample-api-keys table.html b/sample compoeents/sample-api-keys table.html new file mode 100644 index 00000000..b846d308 --- /dev/null +++ b/sample compoeents/sample-api-keys table.html @@ -0,0 +1 @@ +

API Keys

API keys are used to authentication requests to the tailadmin API

NameStatusCreatedLast usedDisable/EnableAction
Disabled25 Jan, 2025Today, 10:45 AM
Active29 Dec, 2024Today, 12:40 AM
Active12 Mar, 2024Today, 11:45 PM
\ No newline at end of file