Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
253 changes: 252 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,253 @@
# FastAPIBlog
A simple API for managing posts and comments with AI moderation and auto-reply.

A modern FastAPI-based blog application with AI-powered content moderation and automatic reply features.

## Features

- **Post Management**: Create and manage blog posts
- **Comment System**: Users can comment on posts with automatic moderation
- **AI Moderation**: Automatic detection of toxic/inappropriate content using Google's Gemini AI
- **Auto-Reply**: AI-generated automatic replies to comments (in Ukrainian)
- **Analytics**: Daily breakdown of comments and moderation statistics
- **User Authentication**: Secure user registration and login system
- **PostgreSQL Database**: Robust data storage with async support
- **Database Migrations**: Alembic-powered schema management

## Technology Stack

- **Backend**: FastAPI (Python 3.12+)
- **Database**: PostgreSQL with async support (asyncpg)
- **ORM**: SQLAlchemy 2.0 (async)
- **AI Integration**: Google Gemini AI for moderation and auto-replies
- **Authentication**: JWT tokens with bcrypt password hashing
- **Migrations**: Alembic
- **Testing**: pytest with async support
- **Code Quality**: Black formatter, Ruff linter

## Prerequisites

- Python 3.12 or higher
- PostgreSQL database
- Google AI API key (for Gemini integration)

## Quick Start

### 1. Clone the Repository

```bash
git clone https://github.com/ThreadsofDaemonS/FastAPIBlog.git
cd FastAPIBlog
```

### 2. Install Dependencies

```bash
pip install -r requirements.txt
```

### 3. Environment Configuration

Create a `.env` file in the project root based on `.env.sample`:

```bash
cp .env.sample .env
```

Configure the following environment variables in `.env`:

```env
# Database Configuration
POSTGRES_USER=your_db_user
POSTGRES_PASSWORD=your_db_password
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=fastapiblog

# Google AI Configuration
GOOGLE_API_KEY=your_gemini_api_key

# JWT Configuration
SECRET_KEY=your_secret_key_for_jwt
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30
```

### 4. Database Setup

#### Using Docker (Recommended)

```bash
docker-compose up -d
```

#### Manual PostgreSQL Setup

1. Install PostgreSQL
2. Create a database:
```sql
CREATE DATABASE fastapiblog;
```

3. Run migrations:
```bash
alembic upgrade head
```

### 5. Run the Application

#### Development Mode

```bash
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```

#### Production Mode

```bash
uvicorn app.main:app --host 0.0.0.0 --port 8000
```

The application will be available at `http://localhost:8000`

## API Documentation

Once the application is running, you can access:

- **Interactive API docs**: `http://localhost:8000/docs`
- **ReDoc documentation**: `http://localhost:8000/redoc`

## Project Structure

```
FastAPIBlog/
├── app/
│ ├── core/ # Core configurations (database, security)
│ ├── models/ # SQLAlchemy models
│ ├── routers/ # API route handlers
│ ├── schemas/ # Pydantic schemas
│ ├── services/ # Business logic services
│ └── main.py # FastAPI application entry point
├── alembic/ # Database migration files
├── tests/ # Test suite
├── requirements.txt # Python dependencies
└── docker-compose.yml # Docker configuration
```

## Key Features Explained

### AI Content Moderation

The application uses Google's Gemini AI to automatically moderate comments:

- **Manual Filtering**: First checks against a predefined list of inappropriate words
- **AI Analysis**: Uses Gemini AI to detect toxic or inappropriate content
- **Automatic Blocking**: Toxic comments are automatically marked as blocked

### Auto-Reply System

- Posts can be configured to automatically reply to comments
- Replies are generated using AI and are contextually relevant
- Configurable delay before sending replies
- Authors don't receive auto-replies to their own comments

### Analytics

Track engagement with detailed analytics:
- Daily comment counts
- Moderation statistics
- Blocked vs. approved comments

## API Endpoints

### Authentication
- `POST /auth/register` - User registration
- `POST /auth/login` - User login

### Posts
- `GET /posts/` - List all posts
- `POST /posts/` - Create a new post
- `GET /posts/{id}` - Get specific post

### Comments
- `GET /posts/{id}/comments` - Get comments for a post
- `POST /comments/` - Create a new comment

### Analytics
- `GET /analytics/comments-daily-breakdown` - Get daily comment statistics

## Development

### Running Tests

```bash
pytest
```

### Code Formatting

```bash
black .
```

### Linting

```bash
ruff check .
```

### Database Migrations

Create a new migration:
```bash
alembic revision --autogenerate -m "Description of changes"
```

Apply migrations:
```bash
alembic upgrade head
```

## Docker Deployment

The application includes Docker support for easy deployment:

```bash
docker-compose up -d
```

This will start:
- FastAPI application
- PostgreSQL database
- Automatic database migrations

## Configuration Notes

### AI Integration

- The AI moderation and auto-reply features are configured to work with Ukrainian text
- The system maintains Ukrainian prompts for optimal AI performance with Ukrainian content
- Auto-replies are generated in Ukrainian language

### Security

- Passwords are hashed using bcrypt
- JWT tokens are used for authentication
- Database relationships include proper CASCADE constraints
- Input validation using Pydantic schemas

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests for new functionality
5. Ensure all tests pass
6. Submit a pull request

## License

This project is open source and available under the MIT License.

## Support

For issues and questions, please use the GitHub issue tracker.
2 changes: 1 addition & 1 deletion alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from app.models.comment import Comment
from app.models.post import Post
from app.models.user import User
from app.core.db import Base # ← это важно
from app.core.db import Base # ← this is important

# Build DATABASE_URL from .env
DATABASE_URL = (
Expand Down
2 changes: 1 addition & 1 deletion app/core/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def create_access_token(data: dict) -> str:
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

# Отримання користувача з токена
# Get user from token
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db),
Expand Down
4 changes: 2 additions & 2 deletions app/routers/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

@router.get("/comments-daily-breakdown")
async def comments_daily_breakdown(
date_from: date = Query(..., description="Початкова дата (YYYY-MM-DD)"),
date_to: date = Query(..., description="Кінцева дата (YYYY-MM-DD)"),
date_from: date = Query(..., description="Start date (YYYY-MM-DD)"),
date_to: date = Query(..., description="End date (YYYY-MM-DD)"),
db: AsyncSession = Depends(get_db)
) -> list[dict]:
stmt = text("""
Expand Down
12 changes: 7 additions & 5 deletions app/services/ai_moderation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@

GOOGLE_API_KEY = config("GOOGLE_API_KEY")

# Ініціалізація клієнта
# Initialize client
client = genai.Client(api_key=GOOGLE_API_KEY)

def is_text_toxic(text: str) -> bool:
# Список заборонених слів для ручної перевірки
# List of banned words for manual checking
blacklist = ["хуйня", "пизда", "єбать", "хуй", "блядь", "сука"]

# Перевірка наявності слова зі списку
# Check for presence of words from the list
if any(bad_word in text.lower() for bad_word in blacklist):
print("[MANUAL TOXICITY DETECTED] YES")
return True

# Якщо немає збігу, використовуємо AI для перевірки
# If no match found, use AI for verification
# AI prompt for toxicity detection (in Ukrainian for better accuracy with Ukrainian text)
prompt = (
"Визнач, чи є наступний текст образливим, токсичним або неприйнятним. "
"Відповідай тільки 'YES' або 'NO'.\n\n"
Expand All @@ -40,6 +41,7 @@ def is_text_toxic(text: str) -> bool:
return False

def generate_reply(post_text: str, comment_text: str) -> str:
# AI prompt for generating Ukrainian replies (kept in Ukrainian to maintain functionality)
prompt = (
"Сформуй коротку, релевантну відповідь українською мовою на цей коментар, враховуючи зміст поста. "
"Відповідь має бути простою, щирою, неформальною. "
Expand All @@ -63,4 +65,4 @@ def generate_reply(post_text: str, comment_text: str) -> str:
return reply
except Exception as e:
print("[AI REPLY ERROR]", e)
return "Дякую за ваш коментар!"
return "Дякую за ваш коментар!" # Default Ukrainian response
2 changes: 1 addition & 1 deletion app/services/auto_reply.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ async def schedule_auto_reply(comment: Comment, db: AsyncSession):
if not post or not post.auto_reply_enabled:
return

if comment.user_id == post.user_id: ### НЕ ЗАБУДЬ ПРО ОСЬ ЦЕ
if comment.user_id == post.user_id: # Don't reply to author's own comments
return

await asyncio.sleep(post.reply_delay_sec or 1)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async def test_user_analytics(self, client: AsyncClient, auth_headers: dict):

@pytest.mark.asyncio
async def test_post_analytics(self, client: AsyncClient, auth_headers: dict):
# Тест допускает 200 (если пост есть) или 404 (если нет)
# Test allows 200 (if post exists) or 404 (if not found)
resp = await client.get("/api/analytics/post/1", headers=auth_headers)
assert resp.status_code in (200, 404)

Expand Down
2 changes: 1 addition & 1 deletion tests/test_posts.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class TestPosts:

@pytest.mark.asyncio
async def test_create_post(self, client: AsyncClient, auth_headers: dict):
# Твой API создает пост по /posts/ и возвращает 200 OK + поля content/is_blocked/...
# API creates post via /posts/ and returns 200 OK + content/is_blocked fields
payload = {"content": "This is a test post."}

resp = await client.post("/posts/", json=payload, headers=auth_headers)
Expand Down