- Use canonical Clerk user id (clerk_user_id) across all onboarding entrypoints to ensure consistent OnboardingSession.user_id lookup - Fix API key persistence in api_key_manager.py to use correct APIKey model columns (session_id, provider, key) - Increase Node heap for frontend build to 8GB and add build:nomap script to disable sourcemaps and reduce memory usage - Update onboarding endpoints (endpoints_core.py, onboarding_control_service.py, step_management_service.py) to prefer clerk_user_id over id - Fix frontend workflowStore.ts TypeScript error by returning WorkflowError instance - Add website_automation_service.py for onboarding automation
296 lines
11 KiB
Python
296 lines
11 KiB
Python
"""User Website Service for ALwrity website maker functionality."""
|
|
from typing import Optional, Dict, Any, List
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import desc
|
|
from loguru import logger
|
|
from datetime import datetime
|
|
|
|
from models.onboarding import UserWebsite
|
|
from models.user_website_request import (
|
|
UserWebsiteRequest,
|
|
UserWebsiteResponse,
|
|
WebsiteStatus,
|
|
TemplateType,
|
|
WebsiteStatusUpdate
|
|
)
|
|
from services.database import get_db
|
|
|
|
|
|
class UserWebsiteService:
|
|
"""Service for managing user website creation and deployment."""
|
|
|
|
def __init__(self):
|
|
logger.info("🔄 Initializing UserWebsiteService...")
|
|
|
|
def create_user_website(self, request: UserWebsiteRequest) -> UserWebsiteResponse:
|
|
"""Create a new user website record."""
|
|
try:
|
|
logger.info(f"Creating website record for user {request.user_id}")
|
|
|
|
# For testing, create a session directly
|
|
from services.database import get_session_for_user
|
|
db = get_session_for_user(str(request.user_id))
|
|
|
|
if not db:
|
|
logger.error(f"Could not create database session for user {request.user_id}")
|
|
raise Exception("Database session creation failed")
|
|
|
|
try:
|
|
# Check if user already has a website
|
|
existing_website = db.query(UserWebsite).filter(
|
|
UserWebsite.user_id == request.user_id
|
|
).first()
|
|
|
|
if existing_website:
|
|
logger.info(f"User {request.user_id} already has website ID {existing_website.id}, updating it")
|
|
# Update existing record
|
|
existing_website.template_type = request.template_type.value
|
|
existing_website.business_name = request.business_name
|
|
existing_website.business_description = request.business_description
|
|
existing_website.status = request.status.value
|
|
existing_website.site_brief = request.site_brief
|
|
existing_website.theme_tokens = request.theme_tokens
|
|
existing_website.custom_css = request.custom_css
|
|
existing_website.deployment_config = request.deployment_config
|
|
existing_website.updated_at = datetime.utcnow()
|
|
|
|
db.commit()
|
|
db.refresh(existing_website)
|
|
logger.success(f"Updated website record for user {request.user_id}")
|
|
return UserWebsiteResponse(**existing_website.to_dict())
|
|
|
|
# Create new website record
|
|
db_website = UserWebsite(
|
|
user_id=request.user_id,
|
|
template_type=request.template_type.value,
|
|
business_name=request.business_name,
|
|
business_description=request.business_description,
|
|
status=request.status.value,
|
|
site_brief=request.site_brief,
|
|
theme_tokens=request.theme_tokens,
|
|
custom_css=request.custom_css,
|
|
deployment_config=request.deployment_config
|
|
)
|
|
|
|
db.add(db_website)
|
|
db.commit()
|
|
db.refresh(db_website)
|
|
|
|
logger.success(f"Created website record {db_website.id} for user {request.user_id}")
|
|
return UserWebsiteResponse(**db_website.to_dict())
|
|
|
|
finally:
|
|
db.close()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to create website record for user {request.user_id}: {str(e)}")
|
|
raise
|
|
|
|
def get_user_website_by_user(self, user_id: int) -> Optional[UserWebsiteResponse]:
|
|
"""Get website record by user ID."""
|
|
try:
|
|
logger.debug(f"Retrieving website for user {user_id}")
|
|
|
|
# For testing, create a session directly
|
|
from services.database import get_session_for_user
|
|
db = get_session_for_user(str(user_id))
|
|
|
|
if not db:
|
|
logger.warning(f"Could not create database session for user {user_id}")
|
|
return None
|
|
|
|
try:
|
|
website = db.query(UserWebsite).filter(
|
|
UserWebsite.user_id == user_id
|
|
).first()
|
|
|
|
if website:
|
|
logger.debug(f"Found website {website.id} for user {user_id}")
|
|
return UserWebsiteResponse(**website.to_dict())
|
|
|
|
logger.debug(f"No website found for user {user_id}")
|
|
return None
|
|
finally:
|
|
db.close()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get website for user {user_id}: {str(e)}")
|
|
return None
|
|
|
|
def get_user_website_by_id(self, website_id: int) -> Optional[UserWebsiteResponse]:
|
|
"""Get website record by website ID."""
|
|
db: Session = next(get_db())
|
|
try:
|
|
logger.debug(f"Retrieving website {website_id}")
|
|
website = db.query(UserWebsite).filter(
|
|
UserWebsite.id == website_id
|
|
).first()
|
|
|
|
if website:
|
|
logger.debug(f"Found website {website_id}")
|
|
return UserWebsiteResponse(**website.to_dict())
|
|
|
|
logger.debug(f"Website {website_id} not found")
|
|
return None
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get website {website_id}: {str(e)}")
|
|
return None
|
|
finally:
|
|
db.close()
|
|
|
|
def update_user_website_status(
|
|
self,
|
|
user_id: int,
|
|
status_update: WebsiteStatusUpdate
|
|
) -> Optional[UserWebsiteResponse]:
|
|
"""Update website status and related fields."""
|
|
db: Session = next(get_db())
|
|
try:
|
|
logger.info(f"Updating website status for user {user_id} to {status_update.status}")
|
|
|
|
website = db.query(UserWebsite).filter(
|
|
UserWebsite.user_id == user_id
|
|
).first()
|
|
|
|
if not website:
|
|
logger.warning(f"No website found for user {user_id}")
|
|
return None
|
|
|
|
# Update fields
|
|
website.status = status_update.status.value
|
|
website.updated_at = datetime.utcnow()
|
|
|
|
if status_update.github_repo_url is not None:
|
|
website.github_repo_url = status_update.github_repo_url
|
|
if status_update.netlify_site_url is not None:
|
|
website.netlify_site_url = status_update.netlify_site_url
|
|
if status_update.netlify_admin_url is not None:
|
|
website.netlify_admin_url = status_update.netlify_admin_url
|
|
if status_update.preview_url is not None:
|
|
website.preview_url = status_update.preview_url
|
|
if status_update.error_message is not None:
|
|
website.error_message = status_update.error_message
|
|
|
|
db.commit()
|
|
db.refresh(website)
|
|
|
|
logger.success(f"Updated website {website.id} status to {status_update.status}")
|
|
return UserWebsiteResponse(**website.to_dict())
|
|
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"Failed to update website status for user {user_id}: {str(e)}")
|
|
raise
|
|
finally:
|
|
db.close()
|
|
|
|
def update_user_website_content(
|
|
self,
|
|
user_id: int,
|
|
site_brief: Optional[Dict[str, Any]] = None,
|
|
theme_tokens: Optional[Dict[str, Any]] = None,
|
|
custom_css: Optional[str] = None
|
|
) -> Optional[UserWebsiteResponse]:
|
|
"""Update website content (site brief, theme, CSS)."""
|
|
db: Session = next(get_db())
|
|
try:
|
|
logger.info(f"Updating website content for user {user_id}")
|
|
|
|
website = db.query(UserWebsite).filter(
|
|
UserWebsite.user_id == user_id
|
|
).first()
|
|
|
|
if not website:
|
|
logger.warning(f"No website found for user {user_id}")
|
|
return None
|
|
|
|
if site_brief is not None:
|
|
website.site_brief = site_brief
|
|
if theme_tokens is not None:
|
|
website.theme_tokens = theme_tokens
|
|
if custom_css is not None:
|
|
website.custom_css = custom_css
|
|
|
|
website.updated_at = datetime.utcnow()
|
|
|
|
db.commit()
|
|
db.refresh(website)
|
|
|
|
logger.success(f"Updated website {website.id} content")
|
|
return UserWebsiteResponse(**website.to_dict())
|
|
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"Failed to update website content for user {user_id}: {str(e)}")
|
|
raise
|
|
finally:
|
|
db.close()
|
|
|
|
def delete_user_website(self, user_id: int) -> bool:
|
|
"""Delete user website record."""
|
|
db: Session = next(get_db())
|
|
try:
|
|
logger.info(f"Deleting website for user {user_id}")
|
|
|
|
website = db.query(UserWebsite).filter(
|
|
UserWebsite.user_id == user_id
|
|
).first()
|
|
|
|
if not website:
|
|
logger.warning(f"No website found for user {user_id}")
|
|
return False
|
|
|
|
db.delete(website)
|
|
db.commit()
|
|
|
|
logger.success(f"Deleted website {website.id} for user {user_id}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"Failed to delete website for user {user_id}: {str(e)}")
|
|
raise
|
|
finally:
|
|
db.close()
|
|
|
|
def get_all_user_websites(self, user_id: int) -> List[UserWebsiteResponse]:
|
|
"""Get all websites for a user (for history/audit)."""
|
|
db: Session = next(get_db())
|
|
try:
|
|
logger.debug(f"Retrieving all websites for user {user_id}")
|
|
|
|
websites = db.query(UserWebsite).filter(
|
|
UserWebsite.user_id == user_id
|
|
).order_by(desc(UserWebsite.created_at)).all()
|
|
|
|
return [UserWebsiteResponse(**website.to_dict()) for website in websites]
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get websites for user {user_id}: {str(e)}")
|
|
return []
|
|
finally:
|
|
db.close()
|
|
|
|
def get_websites_by_status(self, status: WebsiteStatus) -> List[UserWebsiteResponse]:
|
|
"""Get all websites with a specific status (for admin/monitoring)."""
|
|
db: Session = next(get_db())
|
|
try:
|
|
logger.debug(f"Retrieving websites with status {status}")
|
|
|
|
websites = db.query(UserWebsite).filter(
|
|
UserWebsite.status == status.value
|
|
).order_by(desc(UserWebsite.created_at)).all()
|
|
|
|
return [UserWebsiteResponse(**website.to_dict()) for website in websites]
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get websites with status {status}: {str(e)}")
|
|
return []
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
# Singleton instance
|
|
user_website_service = UserWebsiteService()
|