Added new features to the project
This commit is contained in:
766
lib/database/twitter_service.py
Normal file
766
lib/database/twitter_service.py
Normal file
@@ -0,0 +1,766 @@
|
||||
"""
|
||||
Twitter Database Service Layer
|
||||
=============================
|
||||
|
||||
This module provides high-level service functions for managing Twitter data
|
||||
in the database. It acts as an interface between the application and the
|
||||
database models, providing convenient methods for common operations.
|
||||
|
||||
Key Features:
|
||||
- User profile management and synchronization
|
||||
- Tweet creation, updating, and analytics tracking
|
||||
- Scheduled tweet management
|
||||
- Analytics data aggregation and reporting
|
||||
- Hashtag performance tracking
|
||||
- Audience insights management
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Any, Optional, Tuple
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func, desc, and_, or_
|
||||
import json
|
||||
from cryptography.fernet import Fernet
|
||||
import os
|
||||
|
||||
from .twitter_models import (
|
||||
TwitterUser, Tweet, ScheduledTweet, TwitterAnalytics, TweetAnalytics,
|
||||
EngagementData, AudienceInsight, HashtagPerformance, ContentTemplate,
|
||||
TwitterSettings, TwitterCredentials, TweetMetrics,
|
||||
TwitterAccountType, TweetType, TweetStatus, EngagementType,
|
||||
AnalyticsTimeframe, ContentCategory,
|
||||
get_twitter_engine, init_twitter_db, get_twitter_session,
|
||||
create_twitter_user, update_user_metrics, create_tweet_record,
|
||||
update_tweet_metrics, calculate_virality_score, get_user_analytics_summary
|
||||
)
|
||||
|
||||
# Configure logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class TwitterDatabaseService:
|
||||
"""
|
||||
High-level service for managing Twitter data in the database.
|
||||
"""
|
||||
|
||||
def __init__(self, db_url: str = "sqlite:///twitter_data.db", encryption_key: Optional[str] = None):
|
||||
"""Initialize the Twitter database service."""
|
||||
self.engine = get_twitter_engine(db_url)
|
||||
self.encryption_key = encryption_key or self._get_or_create_encryption_key()
|
||||
self.cipher = Fernet(self.encryption_key.encode() if isinstance(self.encryption_key, str) else self.encryption_key)
|
||||
|
||||
# Initialize database
|
||||
init_twitter_db(self.engine)
|
||||
|
||||
logger.info("Twitter database service initialized")
|
||||
|
||||
def _get_or_create_encryption_key(self) -> str:
|
||||
"""Get or create encryption key for sensitive data."""
|
||||
key_file = "twitter_encryption.key"
|
||||
|
||||
if os.path.exists(key_file):
|
||||
with open(key_file, 'rb') as f:
|
||||
return f.read()
|
||||
else:
|
||||
key = Fernet.generate_key()
|
||||
with open(key_file, 'wb') as f:
|
||||
f.write(key)
|
||||
return key
|
||||
|
||||
def _encrypt_credentials(self, credentials: TwitterCredentials) -> str:
|
||||
"""Encrypt Twitter credentials for secure storage."""
|
||||
credentials_json = json.dumps(credentials.to_dict())
|
||||
encrypted = self.cipher.encrypt(credentials_json.encode())
|
||||
return encrypted.decode()
|
||||
|
||||
def _decrypt_credentials(self, encrypted_credentials: str) -> TwitterCredentials:
|
||||
"""Decrypt Twitter credentials from storage."""
|
||||
try:
|
||||
decrypted = self.cipher.decrypt(encrypted_credentials.encode())
|
||||
credentials_dict = json.loads(decrypted.decode())
|
||||
return TwitterCredentials.from_dict(credentials_dict)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to decrypt credentials: {e}")
|
||||
return TwitterCredentials()
|
||||
|
||||
def get_session(self) -> Session:
|
||||
"""Get a database session."""
|
||||
return get_twitter_session(self.engine)
|
||||
|
||||
# --- USER MANAGEMENT ---
|
||||
|
||||
def create_or_update_user(self, user_data: Dict[str, Any]) -> TwitterUser:
|
||||
"""Create a new Twitter user or update existing one."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
# Check if user already exists
|
||||
existing_user = session.query(TwitterUser).filter_by(
|
||||
user_id=user_data['user_id']
|
||||
).first()
|
||||
|
||||
if existing_user:
|
||||
# Update existing user
|
||||
for key, value in user_data.items():
|
||||
if hasattr(existing_user, key) and key != 'id':
|
||||
setattr(existing_user, key, value)
|
||||
existing_user.updated_at = datetime.utcnow()
|
||||
session.commit()
|
||||
logger.info(f"Updated Twitter user: {existing_user.username}")
|
||||
return existing_user
|
||||
else:
|
||||
# Create new user
|
||||
twitter_user = create_twitter_user(session, user_data)
|
||||
logger.info(f"Created new Twitter user: {twitter_user.username}")
|
||||
return twitter_user
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
logger.error(f"Error creating/updating user: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def save_user_credentials(self, user_id: str, credentials: TwitterCredentials) -> bool:
|
||||
"""Save encrypted Twitter credentials for a user."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if user:
|
||||
encrypted_creds = self._encrypt_credentials(credentials)
|
||||
user.credentials_encrypted = encrypted_creds
|
||||
user.updated_at = datetime.utcnow()
|
||||
session.commit()
|
||||
logger.info(f"Saved credentials for user: {user.username}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"User not found: {user_id}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
logger.error(f"Error saving credentials: {e}")
|
||||
return False
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_user_credentials(self, user_id: str) -> Optional[TwitterCredentials]:
|
||||
"""Get decrypted Twitter credentials for a user."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if user and user.credentials_encrypted:
|
||||
return self._decrypt_credentials(user.credentials_encrypted)
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting credentials: {e}")
|
||||
return None
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_user_by_id(self, user_id: str) -> Optional[TwitterUser]:
|
||||
"""Get Twitter user by ALwrity user ID."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
return session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_user_by_twitter_id(self, twitter_user_id: int) -> Optional[TwitterUser]:
|
||||
"""Get Twitter user by Twitter user ID."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
return session.query(TwitterUser).filter_by(twitter_user_id=twitter_user_id).first()
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def update_user_profile(self, user_id: str, profile_data: Dict[str, Any]) -> bool:
|
||||
"""Update user profile information from Twitter API."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if user:
|
||||
update_user_metrics(session, user.id, profile_data)
|
||||
logger.info(f"Updated profile for user: {user.username}")
|
||||
return True
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
logger.error(f"Error updating user profile: {e}")
|
||||
return False
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
# --- TWEET MANAGEMENT ---
|
||||
|
||||
def save_tweet(self, tweet_data: Dict[str, Any]) -> Tweet:
|
||||
"""Save a tweet to the database."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
tweet = create_tweet_record(session, tweet_data)
|
||||
logger.info(f"Saved tweet: {tweet.id}")
|
||||
return tweet
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
logger.error(f"Error saving tweet: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def update_tweet_status(self, tweet_id: int, status: TweetStatus, twitter_tweet_id: Optional[int] = None) -> bool:
|
||||
"""Update tweet status (e.g., from draft to posted)."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
tweet = session.query(Tweet).filter_by(id=tweet_id).first()
|
||||
if tweet:
|
||||
tweet.status = status
|
||||
if twitter_tweet_id:
|
||||
tweet.tweet_id = twitter_tweet_id
|
||||
if status == TweetStatus.POSTED:
|
||||
tweet.posted_at = datetime.utcnow()
|
||||
tweet.updated_at = datetime.utcnow()
|
||||
session.commit()
|
||||
logger.info(f"Updated tweet {tweet_id} status to {status.value}")
|
||||
return True
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
logger.error(f"Error updating tweet status: {e}")
|
||||
return False
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_user_tweets(self, user_id: str, status: Optional[TweetStatus] = None, limit: int = 50) -> List[Tweet]:
|
||||
"""Get tweets for a user, optionally filtered by status."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if not user:
|
||||
return []
|
||||
|
||||
query = session.query(Tweet).filter_by(user_id=user.id)
|
||||
|
||||
if status:
|
||||
query = query.filter_by(status=status)
|
||||
|
||||
return query.order_by(desc(Tweet.created_at)).limit(limit).all()
|
||||
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_tweet_by_id(self, tweet_id: int) -> Optional[Tweet]:
|
||||
"""Get tweet by database ID."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
return session.query(Tweet).filter_by(id=tweet_id).first()
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_tweet_by_twitter_id(self, twitter_tweet_id: int) -> Optional[Tweet]:
|
||||
"""Get tweet by Twitter tweet ID."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
return session.query(Tweet).filter_by(tweet_id=twitter_tweet_id).first()
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def update_tweet_analytics(self, tweet_id: int, metrics: TweetMetrics) -> bool:
|
||||
"""Update tweet analytics from Twitter API."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
update_tweet_metrics(session, tweet_id, metrics)
|
||||
logger.info(f"Updated analytics for tweet: {tweet_id}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
logger.error(f"Error updating tweet analytics: {e}")
|
||||
return False
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_top_performing_tweets(self, user_id: str, days: int = 30, limit: int = 10) -> List[Tweet]:
|
||||
"""Get top performing tweets for a user."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if not user:
|
||||
return []
|
||||
|
||||
start_date = datetime.utcnow() - timedelta(days=days)
|
||||
|
||||
return session.query(Tweet).filter(
|
||||
and_(
|
||||
Tweet.user_id == user.id,
|
||||
Tweet.status == TweetStatus.POSTED,
|
||||
Tweet.posted_at >= start_date
|
||||
)
|
||||
).order_by(desc(Tweet.engagement_rate)).limit(limit).all()
|
||||
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
# --- SCHEDULED TWEETS ---
|
||||
|
||||
def schedule_tweet(self, tweet_id: int, scheduled_time: datetime, settings: Dict[str, Any] = None) -> ScheduledTweet:
|
||||
"""Schedule a tweet for posting."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
tweet = session.query(Tweet).filter_by(id=tweet_id).first()
|
||||
if not tweet:
|
||||
raise ValueError(f"Tweet {tweet_id} not found")
|
||||
|
||||
scheduled_tweet = ScheduledTweet(
|
||||
user_id=tweet.user_id,
|
||||
tweet_id=tweet_id,
|
||||
scheduled_time=scheduled_time,
|
||||
timezone=settings.get('timezone', 'UTC'),
|
||||
auto_optimize_time=settings.get('auto_optimize_time', False),
|
||||
auto_add_hashtags=settings.get('auto_add_hashtags', False),
|
||||
auto_add_emojis=settings.get('auto_add_emojis', False)
|
||||
)
|
||||
|
||||
session.add(scheduled_tweet)
|
||||
|
||||
# Update tweet status
|
||||
tweet.status = TweetStatus.SCHEDULED
|
||||
tweet.scheduled_for = scheduled_time
|
||||
|
||||
session.commit()
|
||||
logger.info(f"Scheduled tweet {tweet_id} for {scheduled_time}")
|
||||
return scheduled_tweet
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
logger.error(f"Error scheduling tweet: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_pending_scheduled_tweets(self, user_id: Optional[str] = None) -> List[ScheduledTweet]:
|
||||
"""Get tweets scheduled for posting."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
query = session.query(ScheduledTweet).filter(
|
||||
and_(
|
||||
ScheduledTweet.status == TweetStatus.SCHEDULED,
|
||||
ScheduledTweet.scheduled_time <= datetime.utcnow()
|
||||
)
|
||||
)
|
||||
|
||||
if user_id:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if user:
|
||||
query = query.filter_by(user_id=user.id)
|
||||
|
||||
return query.order_by(ScheduledTweet.scheduled_time).all()
|
||||
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def mark_scheduled_tweet_posted(self, scheduled_tweet_id: int, twitter_tweet_id: int) -> bool:
|
||||
"""Mark a scheduled tweet as posted."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
scheduled_tweet = session.query(ScheduledTweet).filter_by(id=scheduled_tweet_id).first()
|
||||
if scheduled_tweet:
|
||||
scheduled_tweet.status = TweetStatus.POSTED
|
||||
|
||||
# Update the associated tweet
|
||||
tweet = session.query(Tweet).filter_by(id=scheduled_tweet.tweet_id).first()
|
||||
if tweet:
|
||||
tweet.status = TweetStatus.POSTED
|
||||
tweet.tweet_id = twitter_tweet_id
|
||||
tweet.posted_at = datetime.utcnow()
|
||||
|
||||
session.commit()
|
||||
logger.info(f"Marked scheduled tweet {scheduled_tweet_id} as posted")
|
||||
return True
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
logger.error(f"Error marking scheduled tweet as posted: {e}")
|
||||
return False
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
# --- ANALYTICS ---
|
||||
|
||||
def save_daily_analytics(self, user_id: str, analytics_data: Dict[str, Any]) -> TwitterAnalytics:
|
||||
"""Save daily analytics data for a user."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if not user:
|
||||
raise ValueError(f"User {user_id} not found")
|
||||
|
||||
# Check if analytics for today already exist
|
||||
today = datetime.utcnow().date()
|
||||
existing = session.query(TwitterAnalytics).filter(
|
||||
and_(
|
||||
TwitterAnalytics.user_id == user.id,
|
||||
func.date(TwitterAnalytics.date) == today,
|
||||
TwitterAnalytics.timeframe == AnalyticsTimeframe.DAILY
|
||||
)
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
# Update existing record
|
||||
for key, value in analytics_data.items():
|
||||
if hasattr(existing, key):
|
||||
setattr(existing, key, value)
|
||||
existing.updated_at = datetime.utcnow()
|
||||
session.commit()
|
||||
return existing
|
||||
else:
|
||||
# Create new record
|
||||
analytics = TwitterAnalytics(
|
||||
user_id=user.id,
|
||||
date=datetime.utcnow(),
|
||||
timeframe=AnalyticsTimeframe.DAILY,
|
||||
**analytics_data
|
||||
)
|
||||
session.add(analytics)
|
||||
session.commit()
|
||||
logger.info(f"Saved daily analytics for user: {user.username}")
|
||||
return analytics
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
logger.error(f"Error saving analytics: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_analytics_summary(self, user_id: str, days: int = 30) -> Dict[str, Any]:
|
||||
"""Get comprehensive analytics summary for a user."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
return get_user_analytics_summary(session, user_id, days)
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_engagement_trends(self, user_id: str, days: int = 30) -> List[Dict[str, Any]]:
|
||||
"""Get engagement trends over time."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if not user:
|
||||
return []
|
||||
|
||||
start_date = datetime.utcnow() - timedelta(days=days)
|
||||
|
||||
analytics = session.query(TwitterAnalytics).filter(
|
||||
and_(
|
||||
TwitterAnalytics.user_id == user.id,
|
||||
TwitterAnalytics.date >= start_date,
|
||||
TwitterAnalytics.timeframe == AnalyticsTimeframe.DAILY
|
||||
)
|
||||
).order_by(TwitterAnalytics.date).all()
|
||||
|
||||
return [
|
||||
{
|
||||
'date': a.date.isoformat(),
|
||||
'engagement_rate': a.average_engagement_rate,
|
||||
'total_engagements': a.total_engagements,
|
||||
'impressions': a.total_impressions,
|
||||
'followers_change': a.net_follower_change
|
||||
}
|
||||
for a in analytics
|
||||
]
|
||||
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
# --- HASHTAG PERFORMANCE ---
|
||||
|
||||
def track_hashtag_performance(self, user_id: str, hashtag: str, tweet_id: int, engagement_metrics: Dict[str, Any]) -> bool:
|
||||
"""Track performance of a hashtag."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if not user:
|
||||
return False
|
||||
|
||||
# Get or create hashtag performance record
|
||||
hashtag_perf = session.query(HashtagPerformance).filter(
|
||||
and_(
|
||||
HashtagPerformance.user_id == user.id,
|
||||
HashtagPerformance.hashtag == hashtag
|
||||
)
|
||||
).first()
|
||||
|
||||
if hashtag_perf:
|
||||
# Update existing record
|
||||
hashtag_perf.usage_count += 1
|
||||
hashtag_perf.total_impressions += engagement_metrics.get('impressions', 0)
|
||||
hashtag_perf.total_engagements += engagement_metrics.get('engagements', 0)
|
||||
hashtag_perf.last_used = datetime.utcnow()
|
||||
|
||||
# Update average engagement rate
|
||||
if hashtag_perf.usage_count > 0:
|
||||
hashtag_perf.average_engagement_rate = (
|
||||
hashtag_perf.total_engagements / hashtag_perf.total_impressions * 100
|
||||
if hashtag_perf.total_impressions > 0 else 0
|
||||
)
|
||||
|
||||
# Update best performing tweet if this one is better
|
||||
current_engagement = engagement_metrics.get('engagements', 0)
|
||||
if current_engagement > hashtag_perf.best_tweet_engagement:
|
||||
hashtag_perf.best_tweet_id = tweet_id
|
||||
hashtag_perf.best_tweet_engagement = current_engagement
|
||||
|
||||
else:
|
||||
# Create new record
|
||||
hashtag_perf = HashtagPerformance(
|
||||
user_id=user.id,
|
||||
hashtag=hashtag,
|
||||
usage_count=1,
|
||||
total_impressions=engagement_metrics.get('impressions', 0),
|
||||
total_engagements=engagement_metrics.get('engagements', 0),
|
||||
average_engagement_rate=(
|
||||
engagement_metrics.get('engagements', 0) /
|
||||
max(engagement_metrics.get('impressions', 1), 1) * 100
|
||||
),
|
||||
best_tweet_id=tweet_id,
|
||||
best_tweet_engagement=engagement_metrics.get('engagements', 0),
|
||||
first_used=datetime.utcnow(),
|
||||
last_used=datetime.utcnow()
|
||||
)
|
||||
session.add(hashtag_perf)
|
||||
|
||||
session.commit()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
logger.error(f"Error tracking hashtag performance: {e}")
|
||||
return False
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_top_hashtags(self, user_id: str, limit: int = 10) -> List[HashtagPerformance]:
|
||||
"""Get top performing hashtags for a user."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if not user:
|
||||
return []
|
||||
|
||||
return session.query(HashtagPerformance).filter_by(
|
||||
user_id=user.id
|
||||
).order_by(desc(HashtagPerformance.average_engagement_rate)).limit(limit).all()
|
||||
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
# --- CONTENT TEMPLATES ---
|
||||
|
||||
def save_content_template(self, user_id: str, template_data: Dict[str, Any]) -> ContentTemplate:
|
||||
"""Save a content template."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if not user:
|
||||
raise ValueError(f"User {user_id} not found")
|
||||
|
||||
template = ContentTemplate(
|
||||
user_id=user.id,
|
||||
name=template_data['name'],
|
||||
description=template_data.get('description', ''),
|
||||
template_text=template_data['template_text'],
|
||||
category=ContentCategory(template_data['category']) if template_data.get('category') else None,
|
||||
variables=template_data.get('variables', []),
|
||||
default_hashtags=template_data.get('default_hashtags', []),
|
||||
ai_prompt=template_data.get('ai_prompt', ''),
|
||||
ai_tone=template_data.get('ai_tone', ''),
|
||||
ai_target_audience=template_data.get('ai_target_audience', '')
|
||||
)
|
||||
|
||||
session.add(template)
|
||||
session.commit()
|
||||
logger.info(f"Saved content template: {template.name}")
|
||||
return template
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
logger.error(f"Error saving content template: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_user_templates(self, user_id: str, category: Optional[ContentCategory] = None) -> List[ContentTemplate]:
|
||||
"""Get content templates for a user."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if not user:
|
||||
return []
|
||||
|
||||
query = session.query(ContentTemplate).filter(
|
||||
and_(
|
||||
ContentTemplate.user_id == user.id,
|
||||
ContentTemplate.is_active == True
|
||||
)
|
||||
)
|
||||
|
||||
if category:
|
||||
query = query.filter_by(category=category)
|
||||
|
||||
return query.order_by(desc(ContentTemplate.average_performance)).all()
|
||||
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
# --- SETTINGS ---
|
||||
|
||||
def save_user_settings(self, user_id: str, settings_data: Dict[str, Any]) -> TwitterSettings:
|
||||
"""Save user Twitter settings."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if not user:
|
||||
raise ValueError(f"User {user_id} not found")
|
||||
|
||||
# Check if settings already exist
|
||||
existing_settings = session.query(TwitterSettings).filter_by(user_id=user.id).first()
|
||||
|
||||
if existing_settings:
|
||||
# Update existing settings
|
||||
for key, value in settings_data.items():
|
||||
if hasattr(existing_settings, key):
|
||||
setattr(existing_settings, key, value)
|
||||
existing_settings.updated_at = datetime.utcnow()
|
||||
session.commit()
|
||||
return existing_settings
|
||||
else:
|
||||
# Create new settings
|
||||
settings = TwitterSettings(
|
||||
user_id=user.id,
|
||||
**settings_data
|
||||
)
|
||||
session.add(settings)
|
||||
session.commit()
|
||||
logger.info(f"Saved settings for user: {user.username}")
|
||||
return settings
|
||||
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
logger.error(f"Error saving user settings: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_user_settings(self, user_id: str) -> Optional[TwitterSettings]:
|
||||
"""Get user Twitter settings."""
|
||||
session = self.get_session()
|
||||
try:
|
||||
user = session.query(TwitterUser).filter_by(user_id=user_id).first()
|
||||
if not user:
|
||||
return None
|
||||
|
||||
return session.query(TwitterSettings).filter_by(user_id=user.id).first()
|
||||
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
# --- UTILITY METHODS ---
|
||||
|
||||
def cleanup_old_data(self, days_old: int = 30) -> Dict[str, int]:
|
||||
"""
|
||||
Clean up old data to maintain database performance.
|
||||
|
||||
Args:
|
||||
days_old: Number of days old data to keep
|
||||
|
||||
Returns:
|
||||
Dictionary with cleanup statistics
|
||||
"""
|
||||
try:
|
||||
cutoff_date = datetime.utcnow() - timedelta(days=days_old)
|
||||
|
||||
with self.get_session() as session:
|
||||
# Clean up old analytics data
|
||||
old_analytics = session.query(TwitterAnalytics).filter(
|
||||
TwitterAnalytics.created_at < cutoff_date
|
||||
).count()
|
||||
|
||||
session.query(TwitterAnalytics).filter(
|
||||
TwitterAnalytics.created_at < cutoff_date
|
||||
).delete()
|
||||
|
||||
# Clean up old tweet analytics
|
||||
old_tweet_analytics = session.query(TweetAnalytics).filter(
|
||||
TweetAnalytics.created_at < cutoff_date
|
||||
).count()
|
||||
|
||||
session.query(TweetAnalytics).filter(
|
||||
TweetAnalytics.created_at < cutoff_date
|
||||
).delete()
|
||||
|
||||
session.commit()
|
||||
|
||||
stats = {
|
||||
'old_analytics_removed': old_analytics,
|
||||
'old_tweet_analytics_removed': old_tweet_analytics,
|
||||
'cutoff_date': cutoff_date.isoformat()
|
||||
}
|
||||
|
||||
logger.info(f"Cleaned up old data: {stats}")
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error cleaning up old data: {e}")
|
||||
return {'error': str(e)}
|
||||
|
||||
def get_database_stats(self) -> Dict[str, int]:
|
||||
"""
|
||||
Get database statistics.
|
||||
|
||||
Returns:
|
||||
Dictionary with database statistics
|
||||
"""
|
||||
try:
|
||||
with self.get_session() as session:
|
||||
stats = {
|
||||
'total_users': session.query(TwitterUser).count(),
|
||||
'total_tweets': session.query(Tweet).count(),
|
||||
'posted_tweets': session.query(Tweet).filter(
|
||||
Tweet.status == TweetStatus.POSTED
|
||||
).count(),
|
||||
'scheduled_tweets': session.query(ScheduledTweet).filter(
|
||||
ScheduledTweet.status == TweetStatus.SCHEDULED
|
||||
).count(),
|
||||
'total_analytics_records': session.query(TwitterAnalytics).count(),
|
||||
'total_templates': session.query(ContentTemplate).count()
|
||||
}
|
||||
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting database stats: {e}")
|
||||
return {'error': str(e)}
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Close database connections and clean up resources.
|
||||
"""
|
||||
try:
|
||||
if hasattr(self, 'engine') and self.engine:
|
||||
self.engine.dispose()
|
||||
logger.info("Database connections closed successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"Error closing database connections: {e}")
|
||||
|
||||
# Create a global instance for easy access
|
||||
twitter_db = TwitterDatabaseService()
|
||||
|
||||
# Export the service and key functions
|
||||
__all__ = [
|
||||
'TwitterDatabaseService',
|
||||
'twitter_db'
|
||||
]
|
||||
Reference in New Issue
Block a user