diff --git a/README.md b/README.md index 28df718..1460ff3 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/alembic/env.py b/alembic/env.py index c967203..d7a9f2b 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -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 = ( diff --git a/app/core/security.py b/app/core/security.py index 735261a..4ba8901 100644 --- a/app/core/security.py +++ b/app/core/security.py @@ -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), diff --git a/app/routers/analytics.py b/app/routers/analytics.py index 6e95e08..bfc09f7 100644 --- a/app/routers/analytics.py +++ b/app/routers/analytics.py @@ -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(""" diff --git a/app/services/ai_moderation.py b/app/services/ai_moderation.py index 8cb840c..d2373b6 100644 --- a/app/services/ai_moderation.py +++ b/app/services/ai_moderation.py @@ -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" @@ -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 = ( "Сформуй коротку, релевантну відповідь українською мовою на цей коментар, враховуючи зміст поста. " "Відповідь має бути простою, щирою, неформальною. " @@ -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 "Дякую за ваш коментар!" \ No newline at end of file + return "Дякую за ваш коментар!" # Default Ukrainian response \ No newline at end of file diff --git a/app/services/auto_reply.py b/app/services/auto_reply.py index 572f01a..e55fa82 100644 --- a/app/services/auto_reply.py +++ b/app/services/auto_reply.py @@ -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) diff --git a/tests/test_analytics.py b/tests/test_analytics.py index c85f38f..8931edc 100644 --- a/tests/test_analytics.py +++ b/tests/test_analytics.py @@ -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) diff --git a/tests/test_posts.py b/tests/test_posts.py index ea2efc9..713ea5a 100644 --- a/tests/test_posts.py +++ b/tests/test_posts.py @@ -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)