Blog Writer Implementation Specification¶
This technical specification document outlines the implementation details, architecture, and technical requirements for ALwrity's Blog Writer feature.
Architecture Overview¶
System Architecture¶
The Blog Writer is built on a microservices architecture with the following key components:
graph TB
subgraph "Frontend Layer"
UI[React UI Components]
State[Redux State Management]
Router[React Router]
end
subgraph "Backend Layer"
API[FastAPI Application]
Auth[Authentication Service]
Cache[Redis Cache]
Queue[Celery Task Queue]
end
subgraph "AI Services Layer"
Gemini[Google Gemini API]
Research[Research Services]
SEO[SEO Analysis Engine]
Content[Content Generation]
end
subgraph "Data Layer"
DB[(PostgreSQL Database)]
Files[File Storage]
Logs[Application Logs]
end
subgraph "External APIs"
Tavily[Tavily Research API]
Serper[Serper Search API]
GSC[Google Search Console]
end
UI --> API
State --> API
Router --> API
API --> Auth
API --> Cache
API --> Queue
API --> Gemini
API --> Research
API --> SEO
API --> Content
Research --> Tavily
Research --> Serper
SEO --> GSC
API --> DB
Content --> Files
API --> Logs
style UI fill:#e3f2fd
style API fill:#f3e5f5
style Gemini fill:#e8f5e8
style DB fill:#fff3e0
Technology Stack¶
Frontend¶
- Framework: React 18+ with TypeScript
- UI Library: Material-UI (MUI) v5
- State Management: Redux Toolkit
- Routing: React Router v6
- HTTP Client: Axios
- Form Handling: React Hook Form
- Rich Text Editor: TinyMCE or Quill
Backend¶
- Framework: FastAPI (Python 3.10+)
- Database: PostgreSQL with SQLAlchemy ORM
- Authentication: JWT with Clerk integration
- API Documentation: OpenAPI/Swagger
- Background Tasks: Celery with Redis
- Caching: Redis
- File Storage: AWS S3 or local storage
AI Services¶
- Primary AI: Google Gemini API
- Research: Tavily, Serper, Metaphor APIs
- SEO Analysis: Custom algorithms + external APIs
- Image Generation: Stability AI
- Content Moderation: Custom + external services
API Endpoints¶
Core Blog Writer Endpoints¶
Content Generation¶
POST /api/blog-writer/generate
Content-Type: application/json
Authorization: Bearer {api_key}
{
"topic": "AI in Digital Marketing",
"target_audience": "Marketing professionals",
"content_type": "how-to-guide",
"word_count": 1500,
"tone": "professional",
"keywords": ["AI", "digital marketing", "automation"],
"research_depth": "comprehensive",
"include_seo_analysis": true
}
Research Integration¶
POST /api/blog-writer/research
Content-Type: application/json
Authorization: Bearer {api_key}
{
"topic": "Content Strategy",
"research_depth": "comprehensive",
"sources": ["web", "academic", "industry"],
"language": "en",
"date_range": "last_12_months"
}
SEO Analysis¶
POST /api/blog-writer/seo/analyze
Content-Type: application/json
Authorization: Bearer {api_key}
{
"content": "Your blog post content here...",
"target_keywords": ["content strategy", "digital marketing"],
"competitor_urls": ["https://example.com"],
"analysis_depth": "comprehensive"
}
Response Formats¶
Success Response¶
{
"success": true,
"data": {
"content": {
"title": "AI in Digital Marketing: A Comprehensive Guide",
"body": "Generated content here...",
"word_count": 1500,
"reading_time": "6 minutes"
},
"research": {
"sources": [...],
"key_facts": [...],
"trends": [...]
},
"seo_analysis": {
"score": 85,
"recommendations": [...],
"keyword_analysis": {...}
},
"metadata": {
"generated_at": "2024-01-15T10:30:00Z",
"processing_time": "45 seconds",
"ai_model": "gemini-pro"
}
}
}
Error Response¶
{
"success": false,
"error": {
"code": "CONTENT_GENERATION_FAILED",
"message": "Failed to generate content",
"details": {
"reason": "AI service timeout",
"suggestion": "Try again with a shorter content length"
}
}
}
Database Schema¶
Core Tables¶
Blog Posts¶
CREATE TABLE blog_posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
status VARCHAR(50) DEFAULT 'draft',
word_count INTEGER,
reading_time INTEGER,
seo_score INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
published_at TIMESTAMP
);
CREATE INDEX idx_blog_posts_user_id ON blog_posts(user_id);
CREATE INDEX idx_blog_posts_status ON blog_posts(status);
CREATE INDEX idx_blog_posts_created_at ON blog_posts(created_at);
Research Data¶
CREATE TABLE research_data (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
blog_post_id UUID REFERENCES blog_posts(id),
source_url VARCHAR(500),
source_title VARCHAR(255),
content TEXT,
credibility_score INTEGER,
relevance_score INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_research_data_blog_post_id ON research_data(blog_post_id);
CREATE INDEX idx_research_data_credibility ON research_data(credibility_score);
SEO Analysis¶
CREATE TABLE seo_analysis (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
blog_post_id UUID REFERENCES blog_posts(id),
overall_score INTEGER,
keyword_score INTEGER,
content_score INTEGER,
technical_score INTEGER,
readability_score INTEGER,
recommendations JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_seo_analysis_blog_post_id ON seo_analysis(blog_post_id);
AI Integration¶
Google Gemini Integration¶
Configuration¶
import google.generativeai as genai
class GeminiService:
def __init__(self, api_key: str):
genai.configure(api_key=api_key)
self.model = genai.GenerativeModel('gemini-pro')
async def generate_content(self, prompt: str, **kwargs) -> str:
try:
response = await self.model.generate_content_async(
prompt,
generation_config=genai.types.GenerationConfig(
temperature=kwargs.get('temperature', 0.7),
max_output_tokens=kwargs.get('max_tokens', 2048),
top_p=kwargs.get('top_p', 0.8),
top_k=kwargs.get('top_k', 40)
)
)
return response.text
except Exception as e:
raise ContentGenerationError(f"Gemini API error: {str(e)}")
Prompt Engineering¶
class BlogWriterPrompts:
@staticmethod
def generate_blog_post(topic: str, audience: str, word_count: int) -> str:
return f"""
Write a comprehensive blog post about "{topic}" for {audience}.
Requirements:
- Word count: {word_count} words
- Tone: Professional and engaging
- Structure: Introduction, main sections, conclusion
- Include actionable insights and examples
- Use subheadings for better readability
- Include a compelling call-to-action
Please ensure the content is:
- Well-researched and factual
- SEO-friendly
- Engaging and valuable to readers
- Free from plagiarism
"""
@staticmethod
def generate_outline(topic: str, audience: str) -> str:
return f"""
Create a detailed outline for a blog post about "{topic}" for {audience}.
Include:
- Compelling headline
- Introduction hook
- 3-5 main sections with sub-points
- Conclusion with call-to-action
- Suggested word count for each section
"""
Research Service Integration¶
Multi-Source Research¶
class ResearchService:
def __init__(self):
self.tavily_client = TavilyClient(api_key=settings.TAVILY_API_KEY)
self.serper_client = SerperClient(api_key=settings.SERPER_API_KEY)
self.metaphor_client = MetaphorClient(api_key=settings.METAPHOR_API_KEY)
async def comprehensive_research(self, topic: str, depth: str = "comprehensive") -> Dict:
research_results = {
"web_sources": await self._web_research(topic),
"academic_sources": await self._academic_research(topic),
"industry_sources": await self._industry_research(topic),
"news_sources": await self._news_research(topic)
}
return self._process_research_results(research_results)
async def _web_research(self, topic: str) -> List[Dict]:
# Tavily web search
tavily_results = await self.tavily_client.search(
query=topic,
search_depth="advanced",
max_results=10
)
# Serper Google search
serper_results = await self.serper_client.search(
query=topic,
num_results=10
)
return self._merge_search_results(tavily_results, serper_results)
Frontend Components¶
React Components Structure¶
src/
├── components/
│ ├── BlogWriter/
│ │ ├── BlogWriterContainer.tsx
│ │ ├── TopicInput.tsx
│ │ ├── ContentEditor.tsx
│ │ ├── ResearchPanel.tsx
│ │ ├── SEOAnalysis.tsx
│ │ └── ContentPreview.tsx
│ ├── shared/
│ │ ├── LoadingSpinner.tsx
│ │ ├── ErrorBoundary.tsx
│ │ └── ProgressBar.tsx
│ └── ui/
│ ├── Button.tsx
│ ├── Input.tsx
│ └── Modal.tsx
Main Blog Writer Component¶
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { BlogWriterContainer } from './BlogWriterContainer';
import { ResearchPanel } from './ResearchPanel';
import { SEOAnalysis } from './SEOAnalysis';
import { ContentEditor } from './ContentEditor';
interface BlogWriterProps {
initialTopic?: string;
onContentGenerated?: (content: BlogContent) => void;
}
export const BlogWriter: React.FC<BlogWriterProps> = ({
initialTopic,
onContentGenerated
}) => {
const [currentStep, setCurrentStep] = useState<'input' | 'research' | 'generation' | 'editing' | 'analysis'>('input');
const [blogData, setBlogData] = useState<BlogData>({
topic: initialTopic || '',
audience: '',
wordCount: 1000,
tone: 'professional',
keywords: []
});
const dispatch = useDispatch();
const { content, research, seoAnalysis, loading, error } = useSelector(
(state: RootState) => state.blogWriter
);
const handleGenerateContent = async () => {
setCurrentStep('generation');
dispatch(generateBlogContent(blogData));
};
const handleResearchComplete = (researchData: ResearchData) => {
setBlogData(prev => ({ ...prev, research: researchData }));
setCurrentStep('generation');
};
return (
<div className="blog-writer">
<BlogWriterContainer
currentStep={currentStep}
blogData={blogData}
onDataChange={setBlogData}
onGenerate={handleGenerateContent}
/>
{currentStep === 'research' && (
<ResearchPanel
topic={blogData.topic}
onComplete={handleResearchComplete}
/>
)}
{currentStep === 'editing' && content && (
<ContentEditor
content={content}
onContentChange={(newContent) => setBlogData(prev => ({ ...prev, content: newContent }))}
/>
)}
{currentStep === 'analysis' && (
<SEOAnalysis
content={content}
targetKeywords={blogData.keywords}
onAnalysisComplete={(analysis) => setBlogData(prev => ({ ...prev, seoAnalysis: analysis }))}
/>
)}
</div>
);
};
State Management¶
Redux Store Structure¶
interface BlogWriterState {
// Input data
topic: string;
audience: string;
wordCount: number;
tone: string;
keywords: string[];
// Generated content
content: BlogContent | null;
research: ResearchData | null;
seoAnalysis: SEOAnalysis | null;
// UI state
currentStep: 'input' | 'research' | 'generation' | 'editing' | 'analysis';
loading: boolean;
error: string | null;
// Progress tracking
generationProgress: number;
researchProgress: number;
}
// Actions
export const blogWriterSlice = createSlice({
name: 'blogWriter',
initialState,
reducers: {
setTopic: (state, action) => {
state.topic = action.payload;
},
setAudience: (state, action) => {
state.audience = action.payload;
},
setWordCount: (state, action) => {
state.wordCount = action.payload;
},
setTone: (state, action) => {
state.tone = action.payload;
},
setKeywords: (state, action) => {
state.keywords = action.payload;
},
setCurrentStep: (state, action) => {
state.currentStep = action.payload;
},
setLoading: (state, action) => {
state.loading = action.payload;
},
setError: (state, action) => {
state.error = action.payload;
},
setContent: (state, action) => {
state.content = action.payload;
},
setResearch: (state, action) => {
state.research = action.payload;
},
setSEOAnalysis: (state, action) => {
state.seoAnalysis = action.payload;
}
}
});
Error Handling¶
Error Types¶
class BlogWriterError(Exception):
"""Base exception for Blog Writer errors"""
pass
class ContentGenerationError(BlogWriterError):
"""Error during content generation"""
pass
class ResearchError(BlogWriterError):
"""Error during research process"""
pass
class SEOAnalysisError(BlogWriterError):
"""Error during SEO analysis"""
pass
class ValidationError(BlogWriterError):
"""Input validation error"""
pass
Error Handling Middleware¶
from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
@app.exception_handler(BlogWriterError)
async def blog_writer_error_handler(request: Request, exc: BlogWriterError):
return JSONResponse(
status_code=400,
content={
"success": False,
"error": {
"code": exc.__class__.__name__,
"message": str(exc),
"details": getattr(exc, 'details', {})
}
}
)
@app.exception_handler(ValidationError)
async def validation_error_handler(request: Request, exc: ValidationError):
return JSONResponse(
status_code=422,
content={
"success": False,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": {
"field": exc.field,
"message": str(exc)
}
}
}
)
Performance Optimization¶
Caching Strategy¶
from functools import lru_cache
import redis
class CacheService:
def __init__(self):
self.redis_client = redis.Redis(host='localhost', port=6379, db=0)
@lru_cache(maxsize=1000)
def get_research_cache(self, topic: str, depth: str) -> Dict:
cache_key = f"research:{topic}:{depth}"
cached_data = self.redis_client.get(cache_key)
if cached_data:
return json.loads(cached_data)
return None
def set_research_cache(self, topic: str, depth: str, data: Dict, ttl: int = 3600):
cache_key = f"research:{topic}:{depth}"
self.redis_client.setex(
cache_key,
ttl,
json.dumps(data)
)
Background Processing¶
from celery import Celery
celery_app = Celery('blog_writer')
@celery_app.task
def generate_blog_content_async(topic: str, audience: str, word_count: int):
"""Generate blog content asynchronously"""
try:
# Generate content
content = generate_content(topic, audience, word_count)
# Perform research
research = perform_research(topic)
# SEO analysis
seo_analysis = perform_seo_analysis(content)
return {
"content": content,
"research": research,
"seo_analysis": seo_analysis
}
except Exception as e:
raise ContentGenerationError(f"Async generation failed: {str(e)}")
Security Considerations¶
Input Validation¶
from pydantic import BaseModel, validator
import re
class BlogGenerationRequest(BaseModel):
topic: str
audience: str
word_count: int
tone: str
keywords: List[str]
@validator('topic')
def validate_topic(cls, v):
if len(v) < 3 or len(v) > 200:
raise ValueError('Topic must be between 3 and 200 characters')
return v.strip()
@validator('word_count')
def validate_word_count(cls, v):
if v < 100 or v > 10000:
raise ValueError('Word count must be between 100 and 10,000')
return v
@validator('tone')
def validate_tone(cls, v):
allowed_tones = ['professional', 'casual', 'friendly', 'authoritative', 'conversational']
if v not in allowed_tones:
raise ValueError(f'Tone must be one of: {", ".join(allowed_tones)}')
return v
Rate Limiting¶
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.post("/api/blog-writer/generate")
@limiter.limit("10/minute")
async def generate_blog_content(request: Request, data: BlogGenerationRequest):
# Implementation
pass
Testing Strategy¶
Unit Tests¶
import pytest
from unittest.mock import Mock, patch
from blog_writer.services import BlogWriterService
class TestBlogWriterService:
@pytest.fixture
def blog_writer_service(self):
return BlogWriterService()
@patch('blog_writer.services.GeminiService')
def test_generate_content_success(self, mock_gemini, blog_writer_service):
# Mock Gemini response
mock_gemini.return_value.generate_content.return_value = "Generated content"
# Test content generation
result = blog_writer_service.generate_content(
topic="AI in Marketing",
audience="Marketing professionals",
word_count=1000
)
assert result["content"] == "Generated content"
assert result["word_count"] == 1000
def test_validate_input_data(self, blog_writer_service):
# Test input validation
with pytest.raises(ValidationError):
blog_writer_service.validate_input({
"topic": "", # Empty topic
"word_count": 50 # Too short
})
Integration Tests¶
import pytest
from fastapi.testclient import TestClient
from app import app
client = TestClient(app)
def test_blog_generation_endpoint():
response = client.post(
"/api/blog-writer/generate",
json={
"topic": "AI in Digital Marketing",
"audience": "Marketing professionals",
"word_count": 1000,
"tone": "professional"
},
headers={"Authorization": "Bearer test_token"}
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert "content" in data["data"]
Deployment Configuration¶
Docker Configuration¶
# Dockerfile
FROM python:3.10-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
Environment Variables¶
# .env
DATABASE_URL=postgresql://user:password@localhost/alwrity
REDIS_URL=redis://localhost:6379
GEMINI_API_KEY=your_gemini_api_key
TAVILY_API_KEY=your_tavily_api_key
SERPER_API_KEY=your_serper_api_key
METAPHOR_API_KEY=your_metaphor_api_key
STABILITY_API_KEY=your_stability_api_key
SECRET_KEY=your_secret_key
CORS_ORIGINS=http://localhost:3000
Kubernetes Deployment¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: blog-writer-api
spec:
replicas: 3
selector:
matchLabels:
app: blog-writer-api
template:
metadata:
labels:
app: blog-writer-api
spec:
containers:
- name: blog-writer-api
image: alwrity/blog-writer-api:latest
ports:
- containerPort: 8000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: alwrity-secrets
key: database-url
- name: GEMINI_API_KEY
valueFrom:
secretKeyRef:
name: alwrity-secrets
key: gemini-api-key
This implementation specification provides the technical foundation for building a robust, scalable Blog Writer feature. For more details on specific components, refer to the individual feature documentation.