ALwrity + Wordpress + Wix + GSC integration
This commit is contained in:
170
backend/services/integrations/README.md
Normal file
170
backend/services/integrations/README.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# WordPress Integration Service
|
||||
|
||||
A comprehensive WordPress integration service for ALwrity that enables seamless content publishing to WordPress sites.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
1. **WordPressService** (`wordpress_service.py`)
|
||||
- Manages WordPress site connections
|
||||
- Handles site credentials and authentication
|
||||
- Provides site management operations
|
||||
|
||||
2. **WordPressContentManager** (`wordpress_content.py`)
|
||||
- Manages WordPress content operations
|
||||
- Handles media uploads and compression
|
||||
- Manages categories, tags, and posts
|
||||
- Provides WordPress REST API interactions
|
||||
|
||||
3. **WordPressPublisher** (`wordpress_publisher.py`)
|
||||
- High-level publishing service
|
||||
- Orchestrates content creation and publishing
|
||||
- Manages post references and tracking
|
||||
|
||||
## Features
|
||||
|
||||
### Site Management
|
||||
- ✅ Connect multiple WordPress sites
|
||||
- ✅ Site credential management
|
||||
- ✅ Connection testing and validation
|
||||
- ✅ Site disconnection
|
||||
|
||||
### Content Publishing
|
||||
- ✅ Blog post creation and publishing
|
||||
- ✅ Media upload with compression
|
||||
- ✅ Category and tag management
|
||||
- ✅ Featured image support
|
||||
- ✅ SEO metadata (meta descriptions)
|
||||
- ✅ Draft and published status control
|
||||
|
||||
### Advanced Features
|
||||
- ✅ Image compression for better performance
|
||||
- ✅ Automatic category/tag creation
|
||||
- ✅ Post status management
|
||||
- ✅ Post deletion and updates
|
||||
- ✅ Publishing history tracking
|
||||
|
||||
## Database Schema
|
||||
|
||||
### WordPress Sites Table
|
||||
```sql
|
||||
CREATE TABLE wordpress_sites (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
site_url TEXT NOT NULL,
|
||||
site_name TEXT,
|
||||
username TEXT NOT NULL,
|
||||
app_password TEXT NOT NULL,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, site_url)
|
||||
);
|
||||
```
|
||||
|
||||
### WordPress Posts Table
|
||||
```sql
|
||||
CREATE TABLE wordpress_posts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
site_id INTEGER NOT NULL,
|
||||
wp_post_id INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'draft',
|
||||
published_at TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (site_id) REFERENCES wordpress_sites (id)
|
||||
);
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Site Connection
|
||||
```python
|
||||
from backend.services.integrations import WordPressService
|
||||
|
||||
wp_service = WordPressService()
|
||||
success = wp_service.add_site(
|
||||
user_id="user123",
|
||||
site_url="https://mysite.com",
|
||||
site_name="My Blog",
|
||||
username="admin",
|
||||
app_password="xxxx-xxxx-xxxx-xxxx"
|
||||
)
|
||||
```
|
||||
|
||||
### Publishing Content
|
||||
```python
|
||||
from backend.services.integrations import WordPressPublisher
|
||||
|
||||
publisher = WordPressPublisher()
|
||||
result = publisher.publish_blog_post(
|
||||
user_id="user123",
|
||||
site_id=1,
|
||||
title="My Blog Post",
|
||||
content="<p>This is my blog post content.</p>",
|
||||
excerpt="A brief excerpt",
|
||||
featured_image_path="/path/to/image.jpg",
|
||||
categories=["Technology", "AI"],
|
||||
tags=["wordpress", "automation"],
|
||||
status="publish"
|
||||
)
|
||||
```
|
||||
|
||||
### Content Management
|
||||
```python
|
||||
from backend.services.integrations import WordPressContentManager
|
||||
|
||||
content_manager = WordPressContentManager(
|
||||
site_url="https://mysite.com",
|
||||
username="admin",
|
||||
app_password="xxxx-xxxx-xxxx-xxxx"
|
||||
)
|
||||
|
||||
# Upload media
|
||||
media = content_manager.upload_media(
|
||||
file_path="/path/to/image.jpg",
|
||||
alt_text="Description",
|
||||
title="Image Title"
|
||||
)
|
||||
|
||||
# Create post
|
||||
post = content_manager.create_post(
|
||||
title="Post Title",
|
||||
content="<p>Post content</p>",
|
||||
featured_media_id=media['id'],
|
||||
status="draft"
|
||||
)
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
WordPress integration uses **Application Passwords** for authentication:
|
||||
|
||||
1. Go to WordPress Admin → Users → Profile
|
||||
2. Scroll down to "Application Passwords"
|
||||
3. Create a new application password
|
||||
4. Use the generated password for authentication
|
||||
|
||||
## Error Handling
|
||||
|
||||
All services include comprehensive error handling:
|
||||
- Connection validation
|
||||
- API response checking
|
||||
- Graceful failure handling
|
||||
- Detailed logging
|
||||
|
||||
## Logging
|
||||
|
||||
The service uses structured logging with different levels:
|
||||
- `INFO`: Successful operations
|
||||
- `WARNING`: Non-critical issues
|
||||
- `ERROR`: Failed operations
|
||||
|
||||
## Security
|
||||
|
||||
- Credentials are stored securely in the database
|
||||
- Application passwords are used instead of main passwords
|
||||
- Connection testing before credential storage
|
||||
- Proper authentication for all API calls
|
||||
13
backend/services/integrations/__init__.py
Normal file
13
backend/services/integrations/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
WordPress Integration Package
|
||||
"""
|
||||
|
||||
from .wordpress_service import WordPressService
|
||||
from .wordpress_content import WordPressContentManager
|
||||
from .wordpress_publisher import WordPressPublisher
|
||||
|
||||
__all__ = [
|
||||
'WordPressService',
|
||||
'WordPressContentManager',
|
||||
'WordPressPublisher'
|
||||
]
|
||||
320
backend/services/integrations/wordpress_content.py
Normal file
320
backend/services/integrations/wordpress_content.py
Normal file
@@ -0,0 +1,320 @@
|
||||
"""
|
||||
WordPress Content Management Module
|
||||
Handles content creation, media upload, and publishing to WordPress sites.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import base64
|
||||
import mimetypes
|
||||
import tempfile
|
||||
from typing import Optional, Dict, List, Any, Union
|
||||
from datetime import datetime
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from PIL import Image
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class WordPressContentManager:
|
||||
"""Manages WordPress content operations including posts, media, and taxonomies."""
|
||||
|
||||
def __init__(self, site_url: str, username: str, app_password: str):
|
||||
"""Initialize with WordPress site credentials."""
|
||||
self.site_url = site_url.rstrip('/')
|
||||
self.username = username
|
||||
self.app_password = app_password
|
||||
self.api_base = f"{self.site_url}/wp-json/wp/v2"
|
||||
self.auth = HTTPBasicAuth(username, app_password)
|
||||
|
||||
def _make_request(self, method: str, endpoint: str, **kwargs) -> Optional[Dict[str, Any]]:
|
||||
"""Make authenticated request to WordPress API."""
|
||||
try:
|
||||
url = f"{self.api_base}/{endpoint.lstrip('/')}"
|
||||
response = requests.request(method, url, auth=self.auth, **kwargs)
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
return response.json()
|
||||
else:
|
||||
logger.error(f"WordPress API error: {response.status_code} - {response.text}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"WordPress API request error: {e}")
|
||||
return None
|
||||
|
||||
def get_categories(self) -> List[Dict[str, Any]]:
|
||||
"""Get all categories from WordPress site."""
|
||||
try:
|
||||
result = self._make_request('GET', 'categories', params={'per_page': 100})
|
||||
if result:
|
||||
logger.info(f"Retrieved {len(result)} categories from {self.site_url}")
|
||||
return result
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting categories: {e}")
|
||||
return []
|
||||
|
||||
def get_tags(self) -> List[Dict[str, Any]]:
|
||||
"""Get all tags from WordPress site."""
|
||||
try:
|
||||
result = self._make_request('GET', 'tags', params={'per_page': 100})
|
||||
if result:
|
||||
logger.info(f"Retrieved {len(result)} tags from {self.site_url}")
|
||||
return result
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting tags: {e}")
|
||||
return []
|
||||
|
||||
def create_category(self, name: str, description: str = "") -> Optional[Dict[str, Any]]:
|
||||
"""Create a new category."""
|
||||
try:
|
||||
data = {
|
||||
'name': name,
|
||||
'description': description
|
||||
}
|
||||
result = self._make_request('POST', 'categories', json=data)
|
||||
if result:
|
||||
logger.info(f"Created category: {name}")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating category {name}: {e}")
|
||||
return None
|
||||
|
||||
def create_tag(self, name: str, description: str = "") -> Optional[Dict[str, Any]]:
|
||||
"""Create a new tag."""
|
||||
try:
|
||||
data = {
|
||||
'name': name,
|
||||
'description': description
|
||||
}
|
||||
result = self._make_request('POST', 'tags', json=data)
|
||||
if result:
|
||||
logger.info(f"Created tag: {name}")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating tag {name}: {e}")
|
||||
return None
|
||||
|
||||
def get_or_create_category(self, name: str, description: str = "") -> Optional[int]:
|
||||
"""Get existing category or create new one."""
|
||||
try:
|
||||
# First, try to find existing category
|
||||
categories = self.get_categories()
|
||||
for category in categories:
|
||||
if category['name'].lower() == name.lower():
|
||||
logger.info(f"Found existing category: {name}")
|
||||
return category['id']
|
||||
|
||||
# Create new category if not found
|
||||
new_category = self.create_category(name, description)
|
||||
if new_category:
|
||||
return new_category['id']
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting or creating category {name}: {e}")
|
||||
return None
|
||||
|
||||
def get_or_create_tag(self, name: str, description: str = "") -> Optional[int]:
|
||||
"""Get existing tag or create new one."""
|
||||
try:
|
||||
# First, try to find existing tag
|
||||
tags = self.get_tags()
|
||||
for tag in tags:
|
||||
if tag['name'].lower() == name.lower():
|
||||
logger.info(f"Found existing tag: {name}")
|
||||
return tag['id']
|
||||
|
||||
# Create new tag if not found
|
||||
new_tag = self.create_tag(name, description)
|
||||
if new_tag:
|
||||
return new_tag['id']
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting or creating tag {name}: {e}")
|
||||
return None
|
||||
|
||||
def upload_media(self, file_path: str, alt_text: str = "", title: str = "", caption: str = "", description: str = "") -> Optional[Dict[str, Any]]:
|
||||
"""Upload media file to WordPress."""
|
||||
try:
|
||||
if not os.path.exists(file_path):
|
||||
logger.error(f"Media file not found: {file_path}")
|
||||
return None
|
||||
|
||||
# Get file info
|
||||
file_name = os.path.basename(file_path)
|
||||
mime_type, _ = mimetypes.guess_type(file_path)
|
||||
if not mime_type:
|
||||
logger.error(f"Unable to determine MIME type for: {file_path}")
|
||||
return None
|
||||
|
||||
# Prepare headers
|
||||
headers = {
|
||||
'Content-Disposition': f'attachment; filename="{file_name}"'
|
||||
}
|
||||
|
||||
# Upload file
|
||||
with open(file_path, 'rb') as file:
|
||||
files = {'file': (file_name, file, mime_type)}
|
||||
response = requests.post(
|
||||
f"{self.api_base}/media",
|
||||
auth=self.auth,
|
||||
headers=headers,
|
||||
files=files
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
media_data = response.json()
|
||||
media_id = media_data['id']
|
||||
|
||||
# Update media with metadata
|
||||
update_data = {
|
||||
'alt_text': alt_text,
|
||||
'title': title,
|
||||
'caption': caption,
|
||||
'description': description
|
||||
}
|
||||
|
||||
update_response = requests.post(
|
||||
f"{self.api_base}/media/{media_id}",
|
||||
auth=self.auth,
|
||||
json=update_data
|
||||
)
|
||||
|
||||
if update_response.status_code == 200:
|
||||
logger.info(f"Media uploaded successfully: {file_name}")
|
||||
return update_response.json()
|
||||
else:
|
||||
logger.warning(f"Media uploaded but metadata update failed: {update_response.text}")
|
||||
return media_data
|
||||
else:
|
||||
logger.error(f"Media upload failed: {response.status_code} - {response.text}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error uploading media {file_path}: {e}")
|
||||
return None
|
||||
|
||||
def compress_image(self, image_path: str, quality: int = 85) -> str:
|
||||
"""Compress image for better upload performance."""
|
||||
try:
|
||||
if not os.path.exists(image_path):
|
||||
raise ValueError(f"Image file not found: {image_path}")
|
||||
|
||||
original_size = os.path.getsize(image_path)
|
||||
|
||||
with Image.open(image_path) as img:
|
||||
img_format = img.format or 'JPEG'
|
||||
|
||||
# Create temporary file
|
||||
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=f'.{img_format.lower()}')
|
||||
|
||||
# Save with compression
|
||||
img.save(temp_file, format=img_format, quality=quality, optimize=True)
|
||||
compressed_size = os.path.getsize(temp_file.name)
|
||||
|
||||
reduction = (1 - (compressed_size / original_size)) * 100
|
||||
logger.info(f"Image compressed: {original_size/1024:.2f}KB -> {compressed_size/1024:.2f}KB ({reduction:.1f}% reduction)")
|
||||
|
||||
return temp_file.name
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error compressing image {image_path}: {e}")
|
||||
return image_path # Return original if compression fails
|
||||
|
||||
def _test_connection(self) -> bool:
|
||||
"""Test WordPress site connection."""
|
||||
try:
|
||||
# Test with a simple API call
|
||||
api_url = f"{self.api_base}/users/me"
|
||||
response = requests.get(api_url, auth=self.auth, timeout=10)
|
||||
|
||||
if response.status_code == 200:
|
||||
logger.info(f"WordPress connection test successful for {self.site_url}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"WordPress connection test failed for {self.site_url}: {response.status_code}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"WordPress connection test error for {self.site_url}: {e}")
|
||||
return False
|
||||
|
||||
def create_post(self, title: str, content: str, excerpt: str = "",
|
||||
featured_media_id: Optional[int] = None,
|
||||
categories: Optional[List[int]] = None,
|
||||
tags: Optional[List[int]] = None,
|
||||
status: str = 'draft',
|
||||
meta: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
|
||||
"""Create a new WordPress post."""
|
||||
try:
|
||||
post_data = {
|
||||
'title': title,
|
||||
'content': content,
|
||||
'excerpt': excerpt,
|
||||
'status': status
|
||||
}
|
||||
|
||||
if featured_media_id:
|
||||
post_data['featured_media'] = featured_media_id
|
||||
|
||||
if categories:
|
||||
post_data['categories'] = categories
|
||||
|
||||
if tags:
|
||||
post_data['tags'] = tags
|
||||
|
||||
if meta:
|
||||
post_data['meta'] = meta
|
||||
|
||||
result = self._make_request('POST', 'posts', json=post_data)
|
||||
if result:
|
||||
logger.info(f"Post created successfully: {title}")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating post {title}: {e}")
|
||||
return None
|
||||
|
||||
def update_post(self, post_id: int, **kwargs) -> Optional[Dict[str, Any]]:
|
||||
"""Update an existing WordPress post."""
|
||||
try:
|
||||
result = self._make_request('POST', f'posts/{post_id}', json=kwargs)
|
||||
if result:
|
||||
logger.info(f"Post {post_id} updated successfully")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating post {post_id}: {e}")
|
||||
return None
|
||||
|
||||
def get_post(self, post_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Get a specific WordPress post."""
|
||||
try:
|
||||
result = self._make_request('GET', f'posts/{post_id}')
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting post {post_id}: {e}")
|
||||
return None
|
||||
|
||||
def delete_post(self, post_id: int, force: bool = False) -> bool:
|
||||
"""Delete a WordPress post."""
|
||||
try:
|
||||
params = {'force': force} if force else {}
|
||||
result = self._make_request('DELETE', f'posts/{post_id}', params=params)
|
||||
if result:
|
||||
logger.info(f"Post {post_id} deleted successfully")
|
||||
return True
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting post {post_id}: {e}")
|
||||
return False
|
||||
287
backend/services/integrations/wordpress_oauth.py
Normal file
287
backend/services/integrations/wordpress_oauth.py
Normal file
@@ -0,0 +1,287 @@
|
||||
"""
|
||||
WordPress OAuth2 Service
|
||||
Handles WordPress.com OAuth2 authentication flow for simplified user connection.
|
||||
"""
|
||||
|
||||
import os
|
||||
import secrets
|
||||
import sqlite3
|
||||
import requests
|
||||
from typing import Optional, Dict, Any, List
|
||||
from datetime import datetime, timedelta
|
||||
from loguru import logger
|
||||
import json
|
||||
import base64
|
||||
|
||||
class WordPressOAuthService:
|
||||
"""Manages WordPress.com OAuth2 authentication flow."""
|
||||
|
||||
def __init__(self, db_path: str = "alwrity.db"):
|
||||
self.db_path = db_path
|
||||
# WordPress.com OAuth2 credentials
|
||||
self.client_id = os.getenv('WORDPRESS_CLIENT_ID', '')
|
||||
self.client_secret = os.getenv('WORDPRESS_CLIENT_SECRET', '')
|
||||
self.redirect_uri = os.getenv('WORDPRESS_REDIRECT_URI', 'https://littery-sonny-unscrutinisingly.ngrok-free.dev/wp/callback')
|
||||
self.base_url = "https://public-api.wordpress.com"
|
||||
|
||||
# Validate configuration
|
||||
if not self.client_id or not self.client_secret or self.client_id == 'your_wordpress_com_client_id_here':
|
||||
logger.error("WordPress OAuth client credentials not configured. Please set WORDPRESS_CLIENT_ID and WORDPRESS_CLIENT_SECRET environment variables with valid WordPress.com application credentials.")
|
||||
logger.error("To get credentials: 1. Go to https://developer.wordpress.com/apps/ 2. Create a new application 3. Set redirect URI to: https://your-domain.com/wp/callback")
|
||||
|
||||
self._init_db()
|
||||
|
||||
def _init_db(self):
|
||||
"""Initialize database tables for OAuth tokens."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS wordpress_oauth_tokens (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
access_token TEXT NOT NULL,
|
||||
refresh_token TEXT,
|
||||
token_type TEXT DEFAULT 'bearer',
|
||||
expires_at TIMESTAMP,
|
||||
scope TEXT,
|
||||
blog_id TEXT,
|
||||
blog_url TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT TRUE
|
||||
)
|
||||
''')
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS wordpress_oauth_states (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
state TEXT NOT NULL UNIQUE,
|
||||
user_id TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP DEFAULT (datetime('now', '+10 minutes'))
|
||||
)
|
||||
''')
|
||||
conn.commit()
|
||||
logger.info("WordPress OAuth database initialized.")
|
||||
|
||||
def generate_authorization_url(self, user_id: str, scope: str = "global") -> Dict[str, Any]:
|
||||
"""Generate WordPress OAuth2 authorization URL."""
|
||||
try:
|
||||
# Check if credentials are properly configured
|
||||
if not self.client_id or not self.client_secret or self.client_id == 'your_wordpress_com_client_id_here':
|
||||
logger.error("WordPress OAuth client credentials not configured")
|
||||
return None
|
||||
|
||||
# Generate secure state parameter
|
||||
state = secrets.token_urlsafe(32)
|
||||
|
||||
# Store state in database for validation
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
INSERT INTO wordpress_oauth_states (state, user_id)
|
||||
VALUES (?, ?)
|
||||
''', (state, user_id))
|
||||
conn.commit()
|
||||
|
||||
# Build authorization URL
|
||||
# For WordPress.com, use "global" scope for full access to enable posting
|
||||
params = [
|
||||
f"client_id={self.client_id}",
|
||||
f"redirect_uri={self.redirect_uri}",
|
||||
"response_type=code",
|
||||
f"state={state}",
|
||||
f"scope={scope}" # WordPress.com requires "global" scope for full access
|
||||
]
|
||||
|
||||
auth_url = f"{self.base_url}/oauth2/authorize?{'&'.join(params)}"
|
||||
|
||||
logger.info(f"Generated WordPress OAuth URL for user {user_id}")
|
||||
return {
|
||||
"auth_url": auth_url,
|
||||
"state": state
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating WordPress OAuth URL: {e}")
|
||||
return None
|
||||
|
||||
def handle_oauth_callback(self, code: str, state: str) -> Optional[Dict[str, Any]]:
|
||||
"""Handle OAuth callback and exchange code for access token."""
|
||||
try:
|
||||
# Validate state parameter
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT user_id FROM wordpress_oauth_states
|
||||
WHERE state = ? AND expires_at > datetime('now')
|
||||
''', (state,))
|
||||
result = cursor.fetchone()
|
||||
|
||||
if not result:
|
||||
logger.error(f"Invalid or expired state parameter: {state}")
|
||||
return None
|
||||
|
||||
user_id = result[0]
|
||||
|
||||
# Clean up used state
|
||||
cursor.execute('DELETE FROM wordpress_oauth_states WHERE state = ?', (state,))
|
||||
conn.commit()
|
||||
|
||||
# Exchange authorization code for access token
|
||||
token_data = {
|
||||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'code': code,
|
||||
'grant_type': 'authorization_code'
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{self.base_url}/oauth2/token",
|
||||
data=token_data,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error(f"Token exchange failed: {response.status_code} - {response.text}")
|
||||
return None
|
||||
|
||||
token_info = response.json()
|
||||
|
||||
# Store token information
|
||||
access_token = token_info.get('access_token')
|
||||
blog_id = token_info.get('blog_id')
|
||||
blog_url = token_info.get('blog_url')
|
||||
scope = token_info.get('scope', '')
|
||||
|
||||
# Calculate expiration (WordPress tokens typically expire in 2 weeks)
|
||||
expires_at = datetime.now() + timedelta(days=14)
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
INSERT INTO wordpress_oauth_tokens
|
||||
(user_id, access_token, token_type, expires_at, scope, blog_id, blog_url)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
''', (user_id, access_token, 'bearer', expires_at, scope, blog_id, blog_url))
|
||||
conn.commit()
|
||||
|
||||
logger.info(f"WordPress OAuth token stored for user {user_id}")
|
||||
return {
|
||||
"success": True,
|
||||
"access_token": access_token,
|
||||
"blog_id": blog_id,
|
||||
"blog_url": blog_url,
|
||||
"scope": scope,
|
||||
"expires_at": expires_at.isoformat()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling WordPress OAuth callback: {e}")
|
||||
return None
|
||||
|
||||
def get_user_tokens(self, user_id: str) -> List[Dict[str, Any]]:
|
||||
"""Get all active WordPress tokens for a user."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT id, access_token, token_type, expires_at, scope, blog_id, blog_url, created_at
|
||||
FROM wordpress_oauth_tokens
|
||||
WHERE user_id = ? AND is_active = TRUE AND expires_at > datetime('now')
|
||||
ORDER BY created_at DESC
|
||||
''', (user_id,))
|
||||
|
||||
tokens = []
|
||||
for row in cursor.fetchall():
|
||||
tokens.append({
|
||||
"id": row[0],
|
||||
"access_token": row[1],
|
||||
"token_type": row[2],
|
||||
"expires_at": row[3],
|
||||
"scope": row[4],
|
||||
"blog_id": row[5],
|
||||
"blog_url": row[6],
|
||||
"created_at": row[7]
|
||||
})
|
||||
|
||||
return tokens
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting WordPress tokens for user {user_id}: {e}")
|
||||
return []
|
||||
|
||||
def test_token(self, access_token: str) -> bool:
|
||||
"""Test if a WordPress access token is valid."""
|
||||
try:
|
||||
headers = {'Authorization': f'Bearer {access_token}'}
|
||||
response = requests.get(
|
||||
f"{self.base_url}/rest/v1/me/",
|
||||
headers=headers,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
return response.status_code == 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error testing WordPress token: {e}")
|
||||
return False
|
||||
|
||||
def revoke_token(self, user_id: str, token_id: int) -> bool:
|
||||
"""Revoke a WordPress OAuth token."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
UPDATE wordpress_oauth_tokens
|
||||
SET is_active = FALSE, updated_at = datetime('now')
|
||||
WHERE user_id = ? AND id = ?
|
||||
''', (user_id, token_id))
|
||||
conn.commit()
|
||||
|
||||
if cursor.rowcount > 0:
|
||||
logger.info(f"WordPress token {token_id} revoked for user {user_id}")
|
||||
return True
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error revoking WordPress token: {e}")
|
||||
return False
|
||||
|
||||
def get_connection_status(self, user_id: str) -> Dict[str, Any]:
|
||||
"""Get WordPress connection status for a user."""
|
||||
try:
|
||||
tokens = self.get_user_tokens(user_id)
|
||||
|
||||
if not tokens:
|
||||
return {
|
||||
"connected": False,
|
||||
"sites": [],
|
||||
"total_sites": 0
|
||||
}
|
||||
|
||||
# Test each token and get site information
|
||||
active_sites = []
|
||||
for token in tokens:
|
||||
if self.test_token(token["access_token"]):
|
||||
active_sites.append({
|
||||
"id": token["id"],
|
||||
"blog_id": token["blog_id"],
|
||||
"blog_url": token["blog_url"],
|
||||
"scope": token["scope"],
|
||||
"created_at": token["created_at"]
|
||||
})
|
||||
|
||||
return {
|
||||
"connected": len(active_sites) > 0,
|
||||
"sites": active_sites,
|
||||
"total_sites": len(active_sites)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting WordPress connection status: {e}")
|
||||
return {
|
||||
"connected": False,
|
||||
"sites": [],
|
||||
"total_sites": 0
|
||||
}
|
||||
287
backend/services/integrations/wordpress_publisher.py
Normal file
287
backend/services/integrations/wordpress_publisher.py
Normal file
@@ -0,0 +1,287 @@
|
||||
"""
|
||||
WordPress Publishing Service
|
||||
High-level service for publishing content to WordPress sites.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import tempfile
|
||||
from typing import Optional, Dict, List, Any, Union
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
|
||||
from .wordpress_service import WordPressService
|
||||
from .wordpress_content import WordPressContentManager
|
||||
import sqlite3
|
||||
|
||||
|
||||
class WordPressPublisher:
|
||||
"""High-level WordPress publishing service."""
|
||||
|
||||
def __init__(self, db_path: str = "alwrity.db"):
|
||||
"""Initialize WordPress publisher."""
|
||||
self.wp_service = WordPressService(db_path)
|
||||
self.db_path = db_path
|
||||
|
||||
def publish_blog_post(self, user_id: str, site_id: int,
|
||||
title: str, content: str,
|
||||
excerpt: str = "",
|
||||
featured_image_path: Optional[str] = None,
|
||||
categories: Optional[List[str]] = None,
|
||||
tags: Optional[List[str]] = None,
|
||||
status: str = 'draft',
|
||||
meta_description: str = "") -> Dict[str, Any]:
|
||||
"""Publish a blog post to WordPress."""
|
||||
try:
|
||||
# Get site credentials
|
||||
credentials = self.wp_service.get_site_credentials(site_id)
|
||||
if not credentials:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'WordPress site not found or inactive',
|
||||
'post_id': None
|
||||
}
|
||||
|
||||
# Initialize content manager
|
||||
content_manager = WordPressContentManager(
|
||||
credentials['site_url'],
|
||||
credentials['username'],
|
||||
credentials['app_password']
|
||||
)
|
||||
|
||||
# Test connection
|
||||
if not content_manager._test_connection():
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Cannot connect to WordPress site',
|
||||
'post_id': None
|
||||
}
|
||||
|
||||
# Handle featured image
|
||||
featured_media_id = None
|
||||
if featured_image_path and os.path.exists(featured_image_path):
|
||||
try:
|
||||
# Compress image if it's an image file
|
||||
if featured_image_path.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')):
|
||||
compressed_path = content_manager.compress_image(featured_image_path)
|
||||
featured_media = content_manager.upload_media(
|
||||
compressed_path,
|
||||
alt_text=title,
|
||||
title=title,
|
||||
caption=excerpt
|
||||
)
|
||||
# Clean up temporary file if created
|
||||
if compressed_path != featured_image_path:
|
||||
os.unlink(compressed_path)
|
||||
else:
|
||||
featured_media = content_manager.upload_media(
|
||||
featured_image_path,
|
||||
alt_text=title,
|
||||
title=title,
|
||||
caption=excerpt
|
||||
)
|
||||
|
||||
if featured_media:
|
||||
featured_media_id = featured_media['id']
|
||||
logger.info(f"Featured image uploaded: {featured_media_id}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to upload featured image: {e}")
|
||||
|
||||
# Handle categories
|
||||
category_ids = []
|
||||
if categories:
|
||||
for category_name in categories:
|
||||
category_id = content_manager.get_or_create_category(category_name)
|
||||
if category_id:
|
||||
category_ids.append(category_id)
|
||||
|
||||
# Handle tags
|
||||
tag_ids = []
|
||||
if tags:
|
||||
for tag_name in tags:
|
||||
tag_id = content_manager.get_or_create_tag(tag_name)
|
||||
if tag_id:
|
||||
tag_ids.append(tag_id)
|
||||
|
||||
# Prepare meta data
|
||||
meta_data = {}
|
||||
if meta_description:
|
||||
meta_data['description'] = meta_description
|
||||
|
||||
# Create the post
|
||||
post_data = content_manager.create_post(
|
||||
title=title,
|
||||
content=content,
|
||||
excerpt=excerpt,
|
||||
featured_media_id=featured_media_id,
|
||||
categories=category_ids if category_ids else None,
|
||||
tags=tag_ids if tag_ids else None,
|
||||
status=status,
|
||||
meta=meta_data if meta_data else None
|
||||
)
|
||||
|
||||
if post_data:
|
||||
# Store post reference in database
|
||||
self._store_post_reference(user_id, site_id, post_data['id'], title, status)
|
||||
|
||||
logger.info(f"Blog post published successfully: {title}")
|
||||
return {
|
||||
'success': True,
|
||||
'post_id': post_data['id'],
|
||||
'post_url': post_data.get('link'),
|
||||
'featured_media_id': featured_media_id,
|
||||
'categories': category_ids,
|
||||
'tags': tag_ids
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Failed to create WordPress post',
|
||||
'post_id': None
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error publishing blog post: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
'post_id': None
|
||||
}
|
||||
|
||||
def _store_post_reference(self, user_id: str, site_id: int, wp_post_id: int, title: str, status: str) -> None:
|
||||
"""Store post reference in database."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
INSERT INTO wordpress_posts
|
||||
(user_id, site_id, wp_post_id, title, status, published_at, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
''', (user_id, site_id, wp_post_id, title, status,
|
||||
datetime.now().isoformat() if status == 'publish' else None))
|
||||
conn.commit()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error storing post reference: {e}")
|
||||
|
||||
def get_user_posts(self, user_id: str, site_id: Optional[int] = None) -> List[Dict[str, Any]]:
|
||||
"""Get all posts published by user."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
if site_id:
|
||||
cursor.execute('''
|
||||
SELECT wp.id, wp.wp_post_id, wp.title, wp.status, wp.published_at, wp.created_at,
|
||||
ws.site_name, ws.site_url
|
||||
FROM wordpress_posts wp
|
||||
JOIN wordpress_sites ws ON wp.site_id = ws.id
|
||||
WHERE wp.user_id = ? AND wp.site_id = ?
|
||||
ORDER BY wp.created_at DESC
|
||||
''', (user_id, site_id))
|
||||
else:
|
||||
cursor.execute('''
|
||||
SELECT wp.id, wp.wp_post_id, wp.title, wp.status, wp.published_at, wp.created_at,
|
||||
ws.site_name, ws.site_url
|
||||
FROM wordpress_posts wp
|
||||
JOIN wordpress_sites ws ON wp.site_id = ws.id
|
||||
WHERE wp.user_id = ?
|
||||
ORDER BY wp.created_at DESC
|
||||
''', (user_id,))
|
||||
|
||||
posts = []
|
||||
for row in cursor.fetchall():
|
||||
posts.append({
|
||||
'id': row[0],
|
||||
'wp_post_id': row[1],
|
||||
'title': row[2],
|
||||
'status': row[3],
|
||||
'published_at': row[4],
|
||||
'created_at': row[5],
|
||||
'site_name': row[6],
|
||||
'site_url': row[7]
|
||||
})
|
||||
|
||||
return posts
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user posts: {e}")
|
||||
return []
|
||||
|
||||
def update_post_status(self, user_id: str, post_id: int, status: str) -> bool:
|
||||
"""Update post status (draft/publish)."""
|
||||
try:
|
||||
# Get post info
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT wp.site_id, wp.wp_post_id, ws.site_url, ws.username, ws.app_password
|
||||
FROM wordpress_posts wp
|
||||
JOIN wordpress_sites ws ON wp.site_id = ws.id
|
||||
WHERE wp.id = ? AND wp.user_id = ?
|
||||
''', (post_id, user_id))
|
||||
|
||||
result = cursor.fetchone()
|
||||
if not result:
|
||||
return False
|
||||
|
||||
site_id, wp_post_id, site_url, username, app_password = result
|
||||
|
||||
# Update in WordPress
|
||||
content_manager = WordPressContentManager(site_url, username, app_password)
|
||||
wp_result = content_manager.update_post(wp_post_id, status=status)
|
||||
|
||||
if wp_result:
|
||||
# Update in database
|
||||
cursor.execute('''
|
||||
UPDATE wordpress_posts
|
||||
SET status = ?, published_at = ?
|
||||
WHERE id = ?
|
||||
''', (status, datetime.now().isoformat() if status == 'publish' else None, post_id))
|
||||
conn.commit()
|
||||
|
||||
logger.info(f"Post {post_id} status updated to {status}")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating post status: {e}")
|
||||
return False
|
||||
|
||||
def delete_post(self, user_id: str, post_id: int, force: bool = False) -> bool:
|
||||
"""Delete a WordPress post."""
|
||||
try:
|
||||
# Get post info
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT wp.site_id, wp.wp_post_id, ws.site_url, ws.username, ws.app_password
|
||||
FROM wordpress_posts wp
|
||||
JOIN wordpress_sites ws ON wp.site_id = ws.id
|
||||
WHERE wp.id = ? AND wp.user_id = ?
|
||||
''', (post_id, user_id))
|
||||
|
||||
result = cursor.fetchone()
|
||||
if not result:
|
||||
return False
|
||||
|
||||
site_id, wp_post_id, site_url, username, app_password = result
|
||||
|
||||
# Delete from WordPress
|
||||
content_manager = WordPressContentManager(site_url, username, app_password)
|
||||
wp_result = content_manager.delete_post(wp_post_id, force=force)
|
||||
|
||||
if wp_result:
|
||||
# Remove from database
|
||||
cursor.execute('DELETE FROM wordpress_posts WHERE id = ?', (post_id,))
|
||||
conn.commit()
|
||||
|
||||
logger.info(f"Post {post_id} deleted successfully")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting post: {e}")
|
||||
return False
|
||||
249
backend/services/integrations/wordpress_service.py
Normal file
249
backend/services/integrations/wordpress_service.py
Normal file
@@ -0,0 +1,249 @@
|
||||
"""
|
||||
WordPress Service for ALwrity
|
||||
Handles WordPress site connections, content publishing, and media management.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import sqlite3
|
||||
import base64
|
||||
import mimetypes
|
||||
import tempfile
|
||||
from typing import Optional, Dict, List, Any, Tuple
|
||||
from datetime import datetime
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from PIL import Image
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class WordPressService:
|
||||
"""Main WordPress service class for managing WordPress integrations."""
|
||||
|
||||
def __init__(self, db_path: str = "alwrity.db"):
|
||||
"""Initialize WordPress service with database path."""
|
||||
self.db_path = db_path
|
||||
self.api_version = "v2"
|
||||
self._ensure_tables()
|
||||
|
||||
def _ensure_tables(self) -> None:
|
||||
"""Ensure required database tables exist."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# WordPress sites table
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS wordpress_sites (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
site_url TEXT NOT NULL,
|
||||
site_name TEXT,
|
||||
username TEXT NOT NULL,
|
||||
app_password TEXT NOT NULL,
|
||||
is_active BOOLEAN DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, site_url)
|
||||
)
|
||||
''')
|
||||
|
||||
# WordPress posts table for tracking published content
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS wordpress_posts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
site_id INTEGER NOT NULL,
|
||||
wp_post_id INTEGER NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'draft',
|
||||
published_at TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (site_id) REFERENCES wordpress_sites (id)
|
||||
)
|
||||
''')
|
||||
|
||||
conn.commit()
|
||||
logger.info("WordPress database tables ensured")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error ensuring WordPress tables: {e}")
|
||||
raise
|
||||
|
||||
def add_site(self, user_id: str, site_url: str, site_name: str, username: str, app_password: str) -> bool:
|
||||
"""Add a new WordPress site connection."""
|
||||
try:
|
||||
# Validate site URL format
|
||||
if not site_url.startswith(('http://', 'https://')):
|
||||
site_url = f"https://{site_url}"
|
||||
|
||||
# Test connection before saving
|
||||
if not self._test_connection(site_url, username, app_password):
|
||||
logger.error(f"Failed to connect to WordPress site: {site_url}")
|
||||
return False
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
INSERT OR REPLACE INTO wordpress_sites
|
||||
(user_id, site_url, site_name, username, app_password, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
''', (user_id, site_url, site_name, username, app_password))
|
||||
conn.commit()
|
||||
|
||||
logger.info(f"WordPress site added for user {user_id}: {site_name}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error adding WordPress site: {e}")
|
||||
return False
|
||||
|
||||
def get_user_sites(self, user_id: str) -> List[Dict[str, Any]]:
|
||||
"""Get all WordPress sites for a user."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT id, site_url, site_name, username, is_active, created_at, updated_at
|
||||
FROM wordpress_sites
|
||||
WHERE user_id = ? AND is_active = 1
|
||||
ORDER BY updated_at DESC
|
||||
''', (user_id,))
|
||||
|
||||
sites = []
|
||||
for row in cursor.fetchall():
|
||||
sites.append({
|
||||
'id': row[0],
|
||||
'site_url': row[1],
|
||||
'site_name': row[2],
|
||||
'username': row[3],
|
||||
'is_active': bool(row[4]),
|
||||
'created_at': row[5],
|
||||
'updated_at': row[6]
|
||||
})
|
||||
|
||||
logger.info(f"Retrieved {len(sites)} WordPress sites for user {user_id}")
|
||||
return sites
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting WordPress sites for user {user_id}: {e}")
|
||||
return []
|
||||
|
||||
def get_site_credentials(self, site_id: int) -> Optional[Dict[str, str]]:
|
||||
"""Get credentials for a specific WordPress site."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT site_url, username, app_password
|
||||
FROM wordpress_sites
|
||||
WHERE id = ? AND is_active = 1
|
||||
''', (site_id,))
|
||||
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
return {
|
||||
'site_url': result[0],
|
||||
'username': result[1],
|
||||
'app_password': result[2]
|
||||
}
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting credentials for site {site_id}: {e}")
|
||||
return None
|
||||
|
||||
def _test_connection(self, site_url: str, username: str, app_password: str) -> bool:
|
||||
"""Test WordPress site connection."""
|
||||
try:
|
||||
# Test with a simple API call
|
||||
api_url = f"{site_url}/wp-json/wp/v2/users/me"
|
||||
response = requests.get(api_url, auth=HTTPBasicAuth(username, app_password), timeout=10)
|
||||
|
||||
if response.status_code == 200:
|
||||
logger.info(f"WordPress connection test successful for {site_url}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"WordPress connection test failed for {site_url}: {response.status_code}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"WordPress connection test error for {site_url}: {e}")
|
||||
return False
|
||||
|
||||
def disconnect_site(self, user_id: str, site_id: int) -> bool:
|
||||
"""Disconnect a WordPress site."""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
UPDATE wordpress_sites
|
||||
SET is_active = 0, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ? AND user_id = ?
|
||||
''', (site_id, user_id))
|
||||
conn.commit()
|
||||
|
||||
logger.info(f"WordPress site {site_id} disconnected for user {user_id}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error disconnecting WordPress site {site_id}: {e}")
|
||||
return False
|
||||
|
||||
def get_site_info(self, site_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Get detailed information about a WordPress site."""
|
||||
try:
|
||||
credentials = self.get_site_credentials(site_id)
|
||||
if not credentials:
|
||||
return None
|
||||
|
||||
site_url = credentials['site_url']
|
||||
username = credentials['username']
|
||||
app_password = credentials['app_password']
|
||||
|
||||
# Get site information
|
||||
info = {
|
||||
'site_url': site_url,
|
||||
'username': username,
|
||||
'api_version': self.api_version
|
||||
}
|
||||
|
||||
# Test connection and get basic info
|
||||
if self._test_connection(site_url, username, app_password):
|
||||
info['connected'] = True
|
||||
info['last_checked'] = datetime.now().isoformat()
|
||||
else:
|
||||
info['connected'] = False
|
||||
info['last_checked'] = datetime.now().isoformat()
|
||||
|
||||
return info
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting site info for {site_id}: {e}")
|
||||
return None
|
||||
|
||||
def get_posts_for_all_sites(self, user_id: str) -> List[Dict[str, Any]]:
|
||||
"""Get all tracked WordPress posts for all sites of a user."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT wp.id, wp.wordpress_post_id, wp.title, wp.status, wp.published_at, wp.last_updated_at,
|
||||
ws.site_name, ws.site_url
|
||||
FROM wordpress_posts wp
|
||||
JOIN wordpress_sites ws ON wp.site_id = ws.id
|
||||
WHERE wp.user_id = ? AND ws.is_active = TRUE
|
||||
ORDER BY wp.published_at DESC
|
||||
''', (user_id,))
|
||||
posts = []
|
||||
for post_data in cursor.fetchall():
|
||||
posts.append({
|
||||
"id": post_data[0],
|
||||
"wp_post_id": post_data[1],
|
||||
"title": post_data[2],
|
||||
"status": post_data[3],
|
||||
"published_at": post_data[4],
|
||||
"created_at": post_data[5],
|
||||
"site_name": post_data[6],
|
||||
"site_url": post_data[7]
|
||||
})
|
||||
return posts
|
||||
Reference in New Issue
Block a user