Spaces:
Sleeping
Sleeping
AI Development Team commited on
Commit Β·
8a34091
1
Parent(s): 5c4bfe8
Fix backend API files for HF deployment
Browse files- README.md +16 -208
- api/auth.py +123 -89
- api/translation.py +79 -67
- requirements.txt +15 -18
README.md
CHANGED
|
@@ -1,212 +1,20 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
```
|
| 8 |
-
βββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
|
| 9 |
-
β Frontend ββββββ FastAPI ββββββ Better Auth β
|
| 10 |
-
β (Future) β β Backend β β Service β
|
| 11 |
-
βββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
|
| 12 |
-
β
|
| 13 |
-
ββββββββββββββββββββββΌβββββββββββββββββββββ
|
| 14 |
-
β β β
|
| 15 |
-
βββββββββββββββ βββββββββββββββ βββββββββββββββ
|
| 16 |
-
β Qdrant β β Neon β β Gemini β
|
| 17 |
-
β Vector DB β β Postgres β β API β
|
| 18 |
-
βββββββββββββββ βββββββββββββββ βββββββββββββββ
|
| 19 |
-
```
|
| 20 |
|
| 21 |
## Features
|
| 22 |
-
|
| 23 |
-
-
|
| 24 |
-
-
|
| 25 |
-
-
|
| 26 |
-
-
|
| 27 |
-
- **Security**: Password hashing, JWT validation, user isolation
|
| 28 |
-
- **Scalability**: Async architecture with connection pooling
|
| 29 |
-
|
| 30 |
-
## Prerequisites
|
| 31 |
-
|
| 32 |
-
- Python 3.9+
|
| 33 |
-
- Qdrant vector database instance
|
| 34 |
-
- Neon Postgres database
|
| 35 |
-
- Google Gemini API key
|
| 36 |
-
- Node.js (for development tools, optional)
|
| 37 |
-
|
| 38 |
-
## Setup
|
| 39 |
-
|
| 40 |
-
### 1. Clone the repository
|
| 41 |
-
|
| 42 |
-
```bash
|
| 43 |
-
git clone <repository-url>
|
| 44 |
-
cd backend
|
| 45 |
-
```
|
| 46 |
-
|
| 47 |
-
### 2. Create a virtual environment
|
| 48 |
-
|
| 49 |
-
```bash
|
| 50 |
-
python -m venv venv
|
| 51 |
-
source venv/bin/activate # On Windows: venv\Scripts\activate
|
| 52 |
-
```
|
| 53 |
-
|
| 54 |
-
### 3. Install dependencies
|
| 55 |
-
|
| 56 |
-
```bash
|
| 57 |
-
pip install -r requirements.txt
|
| 58 |
-
```
|
| 59 |
-
|
| 60 |
-
### 4. Configure environment variables
|
| 61 |
-
|
| 62 |
-
Copy the example environment file:
|
| 63 |
-
|
| 64 |
-
```bash
|
| 65 |
-
cp .env.example .env
|
| 66 |
-
```
|
| 67 |
-
|
| 68 |
-
Then edit `.env` with your actual configuration:
|
| 69 |
-
|
| 70 |
-
```env
|
| 71 |
-
# API Configuration
|
| 72 |
-
GEMINI_API_KEY=your_gemini_api_key_here
|
| 73 |
-
QDRANT_URL=your_qdrant_url_here
|
| 74 |
-
QDRANT_API_KEY=your_qdrant_api_key_here
|
| 75 |
-
NEON_DB_URL=your_neon_db_connection_string_here
|
| 76 |
-
|
| 77 |
-
# JWT Configuration
|
| 78 |
-
SECRET_KEY=your_secret_key_here # Use a strong, random secret key
|
| 79 |
-
JWT_EXPIRES_IN=3600
|
| 80 |
-
|
| 81 |
-
# Application Configuration
|
| 82 |
-
DEBUG=false
|
| 83 |
-
LOG_LEVEL=info
|
| 84 |
-
```
|
| 85 |
-
|
| 86 |
-
### 5. Run the application
|
| 87 |
-
|
| 88 |
-
```bash
|
| 89 |
-
cd src
|
| 90 |
-
python main.py
|
| 91 |
-
```
|
| 92 |
-
|
| 93 |
-
Or using uvicorn directly:
|
| 94 |
-
|
| 95 |
-
```bash
|
| 96 |
-
cd src
|
| 97 |
-
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
| 98 |
-
```
|
| 99 |
-
|
| 100 |
-
The application will be available at `http://localhost:8000`
|
| 101 |
-
|
| 102 |
-
## API Endpoints
|
| 103 |
-
|
| 104 |
-
### Authentication
|
| 105 |
-
- `POST /auth/signup` - User registration
|
| 106 |
-
- `POST /auth/login` - User login
|
| 107 |
-
- `GET /auth/me` - Get current user info
|
| 108 |
-
|
| 109 |
-
### RAG & Embeddings
|
| 110 |
-
- `POST /embed` - Generate embeddings for text
|
| 111 |
-
- `POST /save-document` - Save and embed a document
|
| 112 |
-
- `POST /search` - Semantic search in documents
|
| 113 |
-
- `POST /chat` - Chat with RAG context
|
| 114 |
-
|
| 115 |
-
### History
|
| 116 |
-
- `GET /history` - Get chat history
|
| 117 |
-
- `GET /history/{conversation_id}` - Get specific conversation
|
| 118 |
-
|
| 119 |
-
### Health
|
| 120 |
-
- `GET /health` - Health check endpoint
|
| 121 |
-
|
| 122 |
-
## Project Structure
|
| 123 |
-
|
| 124 |
-
```
|
| 125 |
-
backend/
|
| 126 |
-
βββ src/
|
| 127 |
-
β βββ __init__.py
|
| 128 |
-
β βββ main.py # Application entry point
|
| 129 |
-
β βββ config/ # Configuration management
|
| 130 |
-
β β βββ __init__.py
|
| 131 |
-
β β βββ settings.py # App settings and env vars
|
| 132 |
-
β β βββ database.py # Database configuration
|
| 133 |
-
β βββ auth/ # Authentication module
|
| 134 |
-
β βββ db/ # Database module
|
| 135 |
-
β β βββ __init__.py
|
| 136 |
-
β β βββ base.py # Base model class
|
| 137 |
-
β β βββ models/ # SQLAlchemy models
|
| 138 |
-
β β βββ database.py # Database connection
|
| 139 |
-
β β βββ crud.py # CRUD operations
|
| 140 |
-
β βββ qdrant/ # Vector database module
|
| 141 |
-
β βββ embeddings/ # Embedding module
|
| 142 |
-
β βββ rag/ # RAG pipeline module
|
| 143 |
-
β βββ routes/ # API routes
|
| 144 |
-
β βββ models/ # Pydantic models
|
| 145 |
-
β βββ utils/ # Utility functions
|
| 146 |
-
β βββ scripts/ # Utility scripts
|
| 147 |
-
βββ tests/ # Test suite
|
| 148 |
-
βββ requirements.txt # Python dependencies
|
| 149 |
-
βββ .env.example # Environment variables template
|
| 150 |
-
βββ README.md # Documentation
|
| 151 |
-
```
|
| 152 |
-
|
| 153 |
-
## Development
|
| 154 |
-
|
| 155 |
-
### Running tests
|
| 156 |
-
|
| 157 |
-
```bash
|
| 158 |
-
cd backend
|
| 159 |
-
python -m pytest tests/ -v
|
| 160 |
-
```
|
| 161 |
-
|
| 162 |
-
### Running with auto-reload during development
|
| 163 |
-
|
| 164 |
-
```bash
|
| 165 |
-
cd src
|
| 166 |
-
uvicorn main:app --reload
|
| 167 |
-
```
|
| 168 |
-
|
| 169 |
-
## Environment Variables
|
| 170 |
-
|
| 171 |
-
| Variable | Description | Required |
|
| 172 |
-
|----------|-------------|----------|
|
| 173 |
-
| GEMINI_API_KEY | Google Gemini API key | Yes |
|
| 174 |
-
| QDRANT_URL | Qdrant vector database URL | Yes |
|
| 175 |
-
| QDRANT_API_KEY | Qdrant API key (if secured) | No |
|
| 176 |
-
| NEON_DB_URL | Neon Postgres connection string | Yes |
|
| 177 |
-
| SECRET_KEY | JWT secret key | Yes |
|
| 178 |
-
| JWT_EXPIRES_IN | JWT expiration time in seconds | No (default: 3600) |
|
| 179 |
-
| DEBUG | Enable debug mode | No (default: false) |
|
| 180 |
-
| LOG_LEVEL | Logging level | No (default: info) |
|
| 181 |
-
|
| 182 |
-
## Security Considerations
|
| 183 |
-
|
| 184 |
-
- Always use HTTPS in production
|
| 185 |
-
- Store secrets securely (not in version control)
|
| 186 |
-
- Validate and sanitize all user inputs
|
| 187 |
-
- Use parameterized queries to prevent SQL injection
|
| 188 |
-
- Implement rate limiting to prevent abuse
|
| 189 |
-
- Use strong, randomly generated secret keys
|
| 190 |
-
|
| 191 |
-
## Performance
|
| 192 |
-
|
| 193 |
-
- Async architecture for high concurrency
|
| 194 |
-
- Connection pooling for database operations
|
| 195 |
-
- Caching mechanisms for frequently accessed data
|
| 196 |
-
- Optimized vector search with Qdrant
|
| 197 |
-
- Efficient embedding processing pipeline
|
| 198 |
-
|
| 199 |
-
## Contributing
|
| 200 |
-
|
| 201 |
-
1. Fork the repository
|
| 202 |
-
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
| 203 |
-
3. Make your changes
|
| 204 |
-
4. Add tests if applicable
|
| 205 |
-
5. Run tests (`python -m pytest`)
|
| 206 |
-
6. Commit your changes (`git commit -m 'Add amazing feature'`)
|
| 207 |
-
7. Push to the branch (`git push origin feature/amazing-feature`)
|
| 208 |
-
8. Open a Pull Request
|
| 209 |
-
|
| 210 |
-
## License
|
| 211 |
-
|
| 212 |
-
[Add your license here]
|
|
|
|
| 1 |
+
ο»Ώ---
|
| 2 |
+
title: AI Textbook Backend
|
| 3 |
+
emoji: π€
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: blue
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_file: main.py
|
| 8 |
+
pinned: false
|
| 9 |
+
---
|
| 10 |
|
| 11 |
+
# AI Native Textbook Backend
|
| 12 |
|
| 13 |
+
RAG-based chatbot backend for Physical AI & Humanoid Robotics textbook.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
## Features
|
| 16 |
+
- RAG-based Q&A with Qdrant vector database
|
| 17 |
+
- OpenRouter LLM integration
|
| 18 |
+
- Urdu translation support
|
| 19 |
+
- User authentication with JWT
|
| 20 |
+
- Personalized learning roadmaps
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
api/auth.py
CHANGED
|
@@ -2,24 +2,25 @@ from fastapi import APIRouter, HTTPException, Depends, Header
|
|
| 2 |
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
| 3 |
from pydantic import BaseModel, EmailStr
|
| 4 |
from typing import Optional
|
| 5 |
-
from sqlalchemy.orm import Session
|
| 6 |
-
from sqlalchemy.exc import IntegrityError
|
| 7 |
import logging
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
# Import database utilities
|
| 10 |
try:
|
| 11 |
from database.db import get_db
|
| 12 |
from database.models import User as DBUser
|
| 13 |
from auth.jwt_utils import hash_password, verify_password, create_access_token, get_current_user_id_from_token
|
|
|
|
|
|
|
| 14 |
DB_ENABLED = True
|
| 15 |
except ImportError:
|
| 16 |
DB_ENABLED = False
|
|
|
|
| 17 |
logging.warning("Database modules not available. Auth will use mock mode.")
|
| 18 |
|
| 19 |
-
logger = logging.getLogger(__name__)
|
| 20 |
-
router = APIRouter()
|
| 21 |
-
security = HTTPBearer()
|
| 22 |
-
|
| 23 |
class SignupRequest(BaseModel):
|
| 24 |
email: EmailStr
|
| 25 |
password: str
|
|
@@ -44,11 +45,19 @@ class UserProfileResponse(BaseModel):
|
|
| 44 |
hardware_background: Optional[str]
|
| 45 |
experience_level: Optional[str]
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
@router.post("/auth/signup", response_model=AuthResponse)
|
| 48 |
-
async def signup(request: SignupRequest
|
| 49 |
"""Handle user registration with background information"""
|
| 50 |
try:
|
| 51 |
-
if not DB_ENABLED
|
| 52 |
# Mock mode for testing without database
|
| 53 |
return AuthResponse(
|
| 54 |
user_id="mock_user_id",
|
|
@@ -57,59 +66,68 @@ async def signup(request: SignupRequest, db: Session = Depends(get_db) if DB_ENA
|
|
| 57 |
token_type="bearer"
|
| 58 |
)
|
| 59 |
|
| 60 |
-
#
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
except Exception as e:
|
| 103 |
logger.error(f"Error during signup: {str(e)}")
|
| 104 |
-
if db:
|
| 105 |
-
db.rollback()
|
| 106 |
raise HTTPException(status_code=500, detail=f"Error during signup: {str(e)}")
|
| 107 |
|
|
|
|
| 108 |
@router.post("/auth/login", response_model=AuthResponse)
|
| 109 |
-
async def login(request: LoginRequest
|
| 110 |
"""Handle user login"""
|
| 111 |
try:
|
| 112 |
-
if not DB_ENABLED
|
| 113 |
# Mock mode for testing without database
|
| 114 |
return AuthResponse(
|
| 115 |
user_id="mock_user_id",
|
|
@@ -118,27 +136,35 @@ async def login(request: LoginRequest, db: Session = Depends(get_db) if DB_ENABL
|
|
| 118 |
token_type="bearer"
|
| 119 |
)
|
| 120 |
|
| 121 |
-
#
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
raise HTTPException(status_code=401, detail="Invalid credentials")
|
| 125 |
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
|
|
|
|
|
|
| 129 |
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
"email": user.email
|
| 134 |
-
})
|
| 135 |
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
except HTTPException:
|
| 144 |
raise
|
|
@@ -146,15 +172,15 @@ async def login(request: LoginRequest, db: Session = Depends(get_db) if DB_ENABL
|
|
| 146 |
logger.error(f"Error during login: {str(e)}")
|
| 147 |
raise HTTPException(status_code=500, detail="Error during login")
|
| 148 |
|
|
|
|
| 149 |
@router.get("/auth/profile", response_model=UserProfileResponse)
|
| 150 |
async def get_profile(
|
| 151 |
authorization: Optional[str] = Header(None),
|
| 152 |
-
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)
|
| 153 |
-
db: Session = Depends(get_db) if DB_ENABLED else None
|
| 154 |
):
|
| 155 |
"""Get user profile information"""
|
| 156 |
try:
|
| 157 |
-
if not DB_ENABLED
|
| 158 |
# Mock mode for testing without database
|
| 159 |
return UserProfileResponse(
|
| 160 |
user_id="mock_user_id",
|
|
@@ -166,10 +192,10 @@ async def get_profile(
|
|
| 166 |
|
| 167 |
# Get token from Authorization header or credentials
|
| 168 |
token = None
|
| 169 |
-
if
|
| 170 |
-
token = authorization
|
| 171 |
-
elif credentials:
|
| 172 |
token = credentials.credentials
|
|
|
|
|
|
|
| 173 |
|
| 174 |
if not token:
|
| 175 |
raise HTTPException(status_code=401, detail="Authorization token required")
|
|
@@ -179,18 +205,25 @@ async def get_profile(
|
|
| 179 |
if not user_id:
|
| 180 |
raise HTTPException(status_code=401, detail="Invalid or expired token")
|
| 181 |
|
| 182 |
-
# Get
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
raise HTTPException(status_code=404, detail="User not found")
|
| 186 |
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
|
| 195 |
except HTTPException:
|
| 196 |
raise
|
|
@@ -198,6 +231,7 @@ async def get_profile(
|
|
| 198 |
logger.error(f"Error retrieving profile: {str(e)}")
|
| 199 |
raise HTTPException(status_code=500, detail="Error retrieving profile")
|
| 200 |
|
|
|
|
| 201 |
@router.get("/auth/health")
|
| 202 |
async def auth_health():
|
| 203 |
"""Health check for auth service"""
|
|
|
|
| 2 |
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
| 3 |
from pydantic import BaseModel, EmailStr
|
| 4 |
from typing import Optional
|
|
|
|
|
|
|
| 5 |
import logging
|
| 6 |
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
router = APIRouter()
|
| 9 |
+
security = HTTPBearer(auto_error=False)
|
| 10 |
+
|
| 11 |
# Import database utilities
|
| 12 |
try:
|
| 13 |
from database.db import get_db
|
| 14 |
from database.models import User as DBUser
|
| 15 |
from auth.jwt_utils import hash_password, verify_password, create_access_token, get_current_user_id_from_token
|
| 16 |
+
from sqlalchemy.orm import Session
|
| 17 |
+
from sqlalchemy.exc import IntegrityError
|
| 18 |
DB_ENABLED = True
|
| 19 |
except ImportError:
|
| 20 |
DB_ENABLED = False
|
| 21 |
+
get_db = None
|
| 22 |
logging.warning("Database modules not available. Auth will use mock mode.")
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
class SignupRequest(BaseModel):
|
| 25 |
email: EmailStr
|
| 26 |
password: str
|
|
|
|
| 45 |
hardware_background: Optional[str]
|
| 46 |
experience_level: Optional[str]
|
| 47 |
|
| 48 |
+
|
| 49 |
+
def get_db_session():
|
| 50 |
+
"""Get database session or None if DB not enabled"""
|
| 51 |
+
if DB_ENABLED and get_db:
|
| 52 |
+
return Depends(get_db)
|
| 53 |
+
return None
|
| 54 |
+
|
| 55 |
+
|
| 56 |
@router.post("/auth/signup", response_model=AuthResponse)
|
| 57 |
+
async def signup(request: SignupRequest):
|
| 58 |
"""Handle user registration with background information"""
|
| 59 |
try:
|
| 60 |
+
if not DB_ENABLED:
|
| 61 |
# Mock mode for testing without database
|
| 62 |
return AuthResponse(
|
| 63 |
user_id="mock_user_id",
|
|
|
|
| 66 |
token_type="bearer"
|
| 67 |
)
|
| 68 |
|
| 69 |
+
# Get database session
|
| 70 |
+
from database.db import SessionLocal
|
| 71 |
+
db = SessionLocal()
|
| 72 |
+
|
| 73 |
+
try:
|
| 74 |
+
# Validate password strength
|
| 75 |
+
if len(request.password) < 8:
|
| 76 |
+
raise HTTPException(status_code=400, detail="Password must be at least 8 characters long")
|
| 77 |
+
|
| 78 |
+
# Validate background fields
|
| 79 |
+
if request.software_background and len(request.software_background) < 10:
|
| 80 |
+
raise HTTPException(status_code=400, detail="Software background must be at least 10 characters")
|
| 81 |
+
if request.hardware_background and len(request.hardware_background) < 10:
|
| 82 |
+
raise HTTPException(status_code=400, detail="Hardware background must be at least 10 characters")
|
| 83 |
+
|
| 84 |
+
# Hash the password
|
| 85 |
+
hashed_password = hash_password(request.password)
|
| 86 |
+
|
| 87 |
+
# Create new user in database
|
| 88 |
+
db_user = DBUser(
|
| 89 |
+
email=request.email,
|
| 90 |
+
password_hash=hashed_password,
|
| 91 |
+
software_background=request.software_background,
|
| 92 |
+
hardware_background=request.hardware_background,
|
| 93 |
+
experience_level=request.experience_level or "Intermediate"
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
db.add(db_user)
|
| 97 |
+
db.commit()
|
| 98 |
+
db.refresh(db_user)
|
| 99 |
+
|
| 100 |
+
# Generate JWT token
|
| 101 |
+
access_token = create_access_token(data={
|
| 102 |
+
"sub": str(db_user.id),
|
| 103 |
+
"email": db_user.email
|
| 104 |
+
})
|
| 105 |
+
|
| 106 |
+
return AuthResponse(
|
| 107 |
+
user_id=str(db_user.id),
|
| 108 |
+
email=db_user.email,
|
| 109 |
+
access_token=access_token,
|
| 110 |
+
token_type="bearer"
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
except IntegrityError:
|
| 114 |
+
db.rollback()
|
| 115 |
+
raise HTTPException(status_code=400, detail="Email already registered")
|
| 116 |
+
finally:
|
| 117 |
+
db.close()
|
| 118 |
+
|
| 119 |
+
except HTTPException:
|
| 120 |
+
raise
|
| 121 |
except Exception as e:
|
| 122 |
logger.error(f"Error during signup: {str(e)}")
|
|
|
|
|
|
|
| 123 |
raise HTTPException(status_code=500, detail=f"Error during signup: {str(e)}")
|
| 124 |
|
| 125 |
+
|
| 126 |
@router.post("/auth/login", response_model=AuthResponse)
|
| 127 |
+
async def login(request: LoginRequest):
|
| 128 |
"""Handle user login"""
|
| 129 |
try:
|
| 130 |
+
if not DB_ENABLED:
|
| 131 |
# Mock mode for testing without database
|
| 132 |
return AuthResponse(
|
| 133 |
user_id="mock_user_id",
|
|
|
|
| 136 |
token_type="bearer"
|
| 137 |
)
|
| 138 |
|
| 139 |
+
# Get database session
|
| 140 |
+
from database.db import SessionLocal
|
| 141 |
+
db = SessionLocal()
|
|
|
|
| 142 |
|
| 143 |
+
try:
|
| 144 |
+
# Find user by email
|
| 145 |
+
user = db.query(DBUser).filter(DBUser.email == request.email).first()
|
| 146 |
+
if not user:
|
| 147 |
+
raise HTTPException(status_code=401, detail="Invalid credentials")
|
| 148 |
|
| 149 |
+
# Verify password
|
| 150 |
+
if not verify_password(request.password, user.password_hash):
|
| 151 |
+
raise HTTPException(status_code=401, detail="Invalid credentials")
|
|
|
|
|
|
|
| 152 |
|
| 153 |
+
# Generate JWT token
|
| 154 |
+
access_token = create_access_token(data={
|
| 155 |
+
"sub": str(user.id),
|
| 156 |
+
"email": user.email
|
| 157 |
+
})
|
| 158 |
+
|
| 159 |
+
return AuthResponse(
|
| 160 |
+
user_id=str(user.id),
|
| 161 |
+
email=user.email,
|
| 162 |
+
access_token=access_token,
|
| 163 |
+
token_type="bearer"
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
finally:
|
| 167 |
+
db.close()
|
| 168 |
|
| 169 |
except HTTPException:
|
| 170 |
raise
|
|
|
|
| 172 |
logger.error(f"Error during login: {str(e)}")
|
| 173 |
raise HTTPException(status_code=500, detail="Error during login")
|
| 174 |
|
| 175 |
+
|
| 176 |
@router.get("/auth/profile", response_model=UserProfileResponse)
|
| 177 |
async def get_profile(
|
| 178 |
authorization: Optional[str] = Header(None),
|
| 179 |
+
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)
|
|
|
|
| 180 |
):
|
| 181 |
"""Get user profile information"""
|
| 182 |
try:
|
| 183 |
+
if not DB_ENABLED:
|
| 184 |
# Mock mode for testing without database
|
| 185 |
return UserProfileResponse(
|
| 186 |
user_id="mock_user_id",
|
|
|
|
| 192 |
|
| 193 |
# Get token from Authorization header or credentials
|
| 194 |
token = None
|
| 195 |
+
if credentials:
|
|
|
|
|
|
|
| 196 |
token = credentials.credentials
|
| 197 |
+
elif authorization and authorization.startswith("Bearer "):
|
| 198 |
+
token = authorization.replace("Bearer ", "")
|
| 199 |
|
| 200 |
if not token:
|
| 201 |
raise HTTPException(status_code=401, detail="Authorization token required")
|
|
|
|
| 205 |
if not user_id:
|
| 206 |
raise HTTPException(status_code=401, detail="Invalid or expired token")
|
| 207 |
|
| 208 |
+
# Get database session
|
| 209 |
+
from database.db import SessionLocal
|
| 210 |
+
db = SessionLocal()
|
|
|
|
| 211 |
|
| 212 |
+
try:
|
| 213 |
+
# Get user from database
|
| 214 |
+
user = db.query(DBUser).filter(DBUser.id == user_id).first()
|
| 215 |
+
if not user:
|
| 216 |
+
raise HTTPException(status_code=404, detail="User not found")
|
| 217 |
+
|
| 218 |
+
return UserProfileResponse(
|
| 219 |
+
user_id=str(user.id),
|
| 220 |
+
email=user.email,
|
| 221 |
+
software_background=user.software_background,
|
| 222 |
+
hardware_background=user.hardware_background,
|
| 223 |
+
experience_level=user.experience_level
|
| 224 |
+
)
|
| 225 |
+
finally:
|
| 226 |
+
db.close()
|
| 227 |
|
| 228 |
except HTTPException:
|
| 229 |
raise
|
|
|
|
| 231 |
logger.error(f"Error retrieving profile: {str(e)}")
|
| 232 |
raise HTTPException(status_code=500, detail="Error retrieving profile")
|
| 233 |
|
| 234 |
+
|
| 235 |
@router.get("/auth/health")
|
| 236 |
async def auth_health():
|
| 237 |
"""Health check for auth service"""
|
api/translation.py
CHANGED
|
@@ -1,30 +1,38 @@
|
|
| 1 |
-
from fastapi import APIRouter, HTTPException,
|
| 2 |
from pydantic import BaseModel
|
| 3 |
from typing import Optional
|
| 4 |
-
from sqlalchemy.orm import Session
|
| 5 |
-
from sqlalchemy.exc import IntegrityError
|
| 6 |
import hashlib
|
| 7 |
import logging
|
| 8 |
import os
|
| 9 |
|
|
|
|
|
|
|
|
|
|
| 10 |
# Import dependencies
|
| 11 |
try:
|
| 12 |
from services.translation_service import TranslationService
|
| 13 |
from services.rate_limiter import RateLimiter
|
| 14 |
-
from database.db import get_db
|
| 15 |
-
from database.models import Translation
|
| 16 |
from auth.jwt_utils import get_current_user_id_from_token
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
DB_ENABLED = True
|
| 18 |
except ImportError as e:
|
| 19 |
DB_ENABLED = False
|
| 20 |
-
logging.warning(f"Database
|
| 21 |
-
|
| 22 |
-
logger = logging.getLogger(__name__)
|
| 23 |
-
router = APIRouter()
|
| 24 |
|
| 25 |
# Initialize services
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
# Cost and performance metrics (in-memory)
|
| 30 |
translation_metrics = {
|
|
@@ -42,9 +50,9 @@ def log_metrics():
|
|
| 42 |
if total > 0:
|
| 43 |
cache_hit_rate = (translation_metrics["cache_hits"] / total) * 100
|
| 44 |
avg_latency = translation_metrics["total_latency_ms"] / total
|
| 45 |
-
cost_savings = (translation_metrics["cache_hits"] / total) * 100
|
| 46 |
logger.info(
|
| 47 |
-
f"
|
| 48 |
f"Total={total}, Cache Hit Rate={cache_hit_rate:.1f}%, "
|
| 49 |
f"API Calls={translation_metrics['api_calls']}, "
|
| 50 |
f"Avg Latency={avg_latency:.0f}ms, "
|
|
@@ -75,26 +83,19 @@ class TranslationResponse(BaseModel):
|
|
| 75 |
source_lang: str
|
| 76 |
target_lang: str
|
| 77 |
|
|
|
|
| 78 |
@router.post("/translate/urdu", response_model=UrduTranslationResponse)
|
| 79 |
async def translate_to_urdu(
|
| 80 |
request: UrduTranslationRequest,
|
| 81 |
-
authorization: Optional[str] = Header(None)
|
| 82 |
-
db: Session = Depends(get_db) if DB_ENABLED else None
|
| 83 |
):
|
| 84 |
"""
|
| 85 |
Translate chapter content to Urdu with JWT authentication, caching, and rate limiting
|
| 86 |
-
|
| 87 |
-
**Requirements**:
|
| 88 |
-
- JWT token in Authorization header
|
| 89 |
-
- Content hash must match SHA-256 of content
|
| 90 |
-
- Rate limit: 10 translations per user per hour
|
| 91 |
-
|
| 92 |
-
**Returns**:
|
| 93 |
-
- translated_content: Urdu translation
|
| 94 |
-
- cached: Whether result came from cache
|
| 95 |
-
- translation_id: UUID of translation record
|
| 96 |
"""
|
| 97 |
try:
|
|
|
|
|
|
|
|
|
|
| 98 |
# 1. Verify JWT authentication
|
| 99 |
if not authorization or not authorization.startswith("Bearer "):
|
| 100 |
raise HTTPException(status_code=401, detail="Missing or invalid authorization header")
|
|
@@ -130,49 +131,56 @@ async def translate_to_urdu(
|
|
| 130 |
)
|
| 131 |
|
| 132 |
# 4. Check database cache (if enabled)
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
# 5. Cache MISS - Translate using OpenRouter
|
| 155 |
translation_metrics["cache_misses"] += 1
|
| 156 |
translation_metrics["api_calls"] += 1
|
| 157 |
-
logger.info(f"
|
| 158 |
|
| 159 |
api_start_time = time.time()
|
| 160 |
try:
|
| 161 |
translated_text = translation_service.translate_to_urdu(request.content)
|
| 162 |
api_latency_ms = (time.time() - api_start_time) * 1000
|
| 163 |
-
logger.info(f"
|
| 164 |
except Exception as api_error:
|
| 165 |
translation_metrics["api_failures"] += 1
|
| 166 |
-
logger.error(f"
|
| 167 |
# Retry once with exponential backoff
|
| 168 |
time.sleep(2)
|
| 169 |
try:
|
| 170 |
translated_text = translation_service.translate_to_urdu(request.content)
|
| 171 |
api_latency_ms = (time.time() - api_start_time) * 1000
|
| 172 |
-
logger.info(f"
|
| 173 |
except Exception as retry_error:
|
| 174 |
translation_metrics["api_failures"] += 1
|
| 175 |
-
logger.error(f"
|
| 176 |
raise HTTPException(
|
| 177 |
status_code=503,
|
| 178 |
detail="Translation service temporarily unavailable. Please try again."
|
|
@@ -180,12 +188,12 @@ async def translate_to_urdu(
|
|
| 180 |
|
| 181 |
# 6. Save to database (if enabled)
|
| 182 |
translation_id = None
|
| 183 |
-
if DB_ENABLED
|
| 184 |
try:
|
| 185 |
-
from database.models import Translation as DBTranslation
|
| 186 |
import uuid
|
|
|
|
| 187 |
|
| 188 |
-
db_translation =
|
| 189 |
id=uuid.uuid4(),
|
| 190 |
chapter_id=request.chapter_id,
|
| 191 |
content_hash=request.content_hash,
|
|
@@ -203,22 +211,18 @@ async def translate_to_urdu(
|
|
| 203 |
translation_id = str(db_translation.id)
|
| 204 |
logger.info(f"Saved translation to database: {translation_id}")
|
| 205 |
|
| 206 |
-
except
|
| 207 |
-
logger.warning(f"
|
| 208 |
-
db
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
DBTranslation.target_language == "urdu"
|
| 214 |
-
).first()
|
| 215 |
-
if cached_translation:
|
| 216 |
-
translation_id = str(cached_translation.id)
|
| 217 |
|
| 218 |
# Log final metrics
|
| 219 |
total_latency_ms = (time.time() - start_time) * 1000
|
| 220 |
translation_metrics["total_latency_ms"] += total_latency_ms
|
| 221 |
-
logger.info(f"
|
| 222 |
log_metrics()
|
| 223 |
|
| 224 |
return UrduTranslationResponse(
|
|
@@ -239,9 +243,11 @@ async def translate_to_urdu(
|
|
| 239 |
async def translate_text(request: TranslationRequest):
|
| 240 |
"""
|
| 241 |
Legacy translation endpoint - translate text between languages
|
| 242 |
-
(Kept for backward compatibility with existing frontend)
|
| 243 |
"""
|
| 244 |
try:
|
|
|
|
|
|
|
|
|
|
| 245 |
if request.source_lang == "en" and request.target_lang == "ur":
|
| 246 |
translated_text = translation_service.translate_to_urdu(request.text)
|
| 247 |
elif request.source_lang == "ur" and request.target_lang == "en":
|
|
@@ -259,6 +265,8 @@ async def translate_text(request: TranslationRequest):
|
|
| 259 |
target_lang=request.target_lang
|
| 260 |
)
|
| 261 |
|
|
|
|
|
|
|
| 262 |
except Exception as e:
|
| 263 |
logger.error(f"Translation error: {str(e)}")
|
| 264 |
raise HTTPException(status_code=500, detail=f"Error during translation: {str(e)}")
|
|
@@ -269,8 +277,9 @@ async def translation_health():
|
|
| 269 |
"""Health check for translation service"""
|
| 270 |
return {
|
| 271 |
"status": "translation service is running",
|
|
|
|
| 272 |
"database_enabled": DB_ENABLED,
|
| 273 |
-
"rate_limiter_enabled":
|
| 274 |
}
|
| 275 |
|
| 276 |
|
|
@@ -280,6 +289,9 @@ async def translation_stats(
|
|
| 280 |
):
|
| 281 |
"""Get translation statistics for current user"""
|
| 282 |
try:
|
|
|
|
|
|
|
|
|
|
| 283 |
if not authorization or not authorization.startswith("Bearer "):
|
| 284 |
raise HTTPException(status_code=401, detail="Missing authorization header")
|
| 285 |
|
|
|
|
| 1 |
+
from fastapi import APIRouter, HTTPException, Header
|
| 2 |
from pydantic import BaseModel
|
| 3 |
from typing import Optional
|
|
|
|
|
|
|
| 4 |
import hashlib
|
| 5 |
import logging
|
| 6 |
import os
|
| 7 |
|
| 8 |
+
logger = logging.getLogger(__name__)
|
| 9 |
+
router = APIRouter()
|
| 10 |
+
|
| 11 |
# Import dependencies
|
| 12 |
try:
|
| 13 |
from services.translation_service import TranslationService
|
| 14 |
from services.rate_limiter import RateLimiter
|
|
|
|
|
|
|
| 15 |
from auth.jwt_utils import get_current_user_id_from_token
|
| 16 |
+
SERVICES_ENABLED = True
|
| 17 |
+
except ImportError as e:
|
| 18 |
+
SERVICES_ENABLED = False
|
| 19 |
+
logging.warning(f"Services not available: {str(e)}")
|
| 20 |
+
|
| 21 |
+
try:
|
| 22 |
+
from database.db import SessionLocal
|
| 23 |
+
from database.models import Translation
|
| 24 |
DB_ENABLED = True
|
| 25 |
except ImportError as e:
|
| 26 |
DB_ENABLED = False
|
| 27 |
+
logging.warning(f"Database not available: {str(e)}")
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
# Initialize services
|
| 30 |
+
if SERVICES_ENABLED:
|
| 31 |
+
translation_service = TranslationService()
|
| 32 |
+
rate_limiter = RateLimiter(max_requests=10, window_seconds=3600)
|
| 33 |
+
else:
|
| 34 |
+
translation_service = None
|
| 35 |
+
rate_limiter = None
|
| 36 |
|
| 37 |
# Cost and performance metrics (in-memory)
|
| 38 |
translation_metrics = {
|
|
|
|
| 50 |
if total > 0:
|
| 51 |
cache_hit_rate = (translation_metrics["cache_hits"] / total) * 100
|
| 52 |
avg_latency = translation_metrics["total_latency_ms"] / total
|
| 53 |
+
cost_savings = (translation_metrics["cache_hits"] / total) * 100
|
| 54 |
logger.info(
|
| 55 |
+
f"Translation Metrics: "
|
| 56 |
f"Total={total}, Cache Hit Rate={cache_hit_rate:.1f}%, "
|
| 57 |
f"API Calls={translation_metrics['api_calls']}, "
|
| 58 |
f"Avg Latency={avg_latency:.0f}ms, "
|
|
|
|
| 83 |
source_lang: str
|
| 84 |
target_lang: str
|
| 85 |
|
| 86 |
+
|
| 87 |
@router.post("/translate/urdu", response_model=UrduTranslationResponse)
|
| 88 |
async def translate_to_urdu(
|
| 89 |
request: UrduTranslationRequest,
|
| 90 |
+
authorization: Optional[str] = Header(None)
|
|
|
|
| 91 |
):
|
| 92 |
"""
|
| 93 |
Translate chapter content to Urdu with JWT authentication, caching, and rate limiting
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
"""
|
| 95 |
try:
|
| 96 |
+
if not SERVICES_ENABLED:
|
| 97 |
+
raise HTTPException(status_code=503, detail="Translation service not available")
|
| 98 |
+
|
| 99 |
# 1. Verify JWT authentication
|
| 100 |
if not authorization or not authorization.startswith("Bearer "):
|
| 101 |
raise HTTPException(status_code=401, detail="Missing or invalid authorization header")
|
|
|
|
| 131 |
)
|
| 132 |
|
| 133 |
# 4. Check database cache (if enabled)
|
| 134 |
+
db = None
|
| 135 |
+
if DB_ENABLED:
|
| 136 |
+
try:
|
| 137 |
+
db = SessionLocal()
|
| 138 |
+
cached_translation = db.query(Translation).filter(
|
| 139 |
+
Translation.chapter_id == request.chapter_id,
|
| 140 |
+
Translation.content_hash == request.content_hash,
|
| 141 |
+
Translation.target_language == "urdu"
|
| 142 |
+
).first()
|
| 143 |
+
|
| 144 |
+
if cached_translation:
|
| 145 |
+
latency_ms = (time.time() - start_time) * 1000
|
| 146 |
+
translation_metrics["cache_hits"] += 1
|
| 147 |
+
translation_metrics["total_latency_ms"] += latency_ms
|
| 148 |
+
logger.info(f"Cache HIT for chapter {request.chapter_id} | Latency: {latency_ms:.0f}ms")
|
| 149 |
+
log_metrics()
|
| 150 |
+
return UrduTranslationResponse(
|
| 151 |
+
translated_content=cached_translation.translated_content,
|
| 152 |
+
cached=True,
|
| 153 |
+
translation_id=str(cached_translation.id)
|
| 154 |
+
)
|
| 155 |
+
except Exception as db_err:
|
| 156 |
+
logger.warning(f"Database cache check failed: {str(db_err)}")
|
| 157 |
+
finally:
|
| 158 |
+
if db:
|
| 159 |
+
db.close()
|
| 160 |
+
db = None
|
| 161 |
|
| 162 |
# 5. Cache MISS - Translate using OpenRouter
|
| 163 |
translation_metrics["cache_misses"] += 1
|
| 164 |
translation_metrics["api_calls"] += 1
|
| 165 |
+
logger.info(f"Cache MISS for chapter {request.chapter_id} - calling OpenRouter API")
|
| 166 |
|
| 167 |
api_start_time = time.time()
|
| 168 |
try:
|
| 169 |
translated_text = translation_service.translate_to_urdu(request.content)
|
| 170 |
api_latency_ms = (time.time() - api_start_time) * 1000
|
| 171 |
+
logger.info(f"OpenRouter API call successful | API Latency: {api_latency_ms:.0f}ms")
|
| 172 |
except Exception as api_error:
|
| 173 |
translation_metrics["api_failures"] += 1
|
| 174 |
+
logger.error(f"OpenRouter API error: {str(api_error)}")
|
| 175 |
# Retry once with exponential backoff
|
| 176 |
time.sleep(2)
|
| 177 |
try:
|
| 178 |
translated_text = translation_service.translate_to_urdu(request.content)
|
| 179 |
api_latency_ms = (time.time() - api_start_time) * 1000
|
| 180 |
+
logger.info(f"Retry successful after {api_latency_ms:.0f}ms")
|
| 181 |
except Exception as retry_error:
|
| 182 |
translation_metrics["api_failures"] += 1
|
| 183 |
+
logger.error(f"Retry failed: {str(retry_error)}")
|
| 184 |
raise HTTPException(
|
| 185 |
status_code=503,
|
| 186 |
detail="Translation service temporarily unavailable. Please try again."
|
|
|
|
| 188 |
|
| 189 |
# 6. Save to database (if enabled)
|
| 190 |
translation_id = None
|
| 191 |
+
if DB_ENABLED:
|
| 192 |
try:
|
|
|
|
| 193 |
import uuid
|
| 194 |
+
db = SessionLocal()
|
| 195 |
|
| 196 |
+
db_translation = Translation(
|
| 197 |
id=uuid.uuid4(),
|
| 198 |
chapter_id=request.chapter_id,
|
| 199 |
content_hash=request.content_hash,
|
|
|
|
| 211 |
translation_id = str(db_translation.id)
|
| 212 |
logger.info(f"Saved translation to database: {translation_id}")
|
| 213 |
|
| 214 |
+
except Exception as save_err:
|
| 215 |
+
logger.warning(f"Failed to save translation: {str(save_err)}")
|
| 216 |
+
if db:
|
| 217 |
+
db.rollback()
|
| 218 |
+
finally:
|
| 219 |
+
if db:
|
| 220 |
+
db.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
# Log final metrics
|
| 223 |
total_latency_ms = (time.time() - start_time) * 1000
|
| 224 |
translation_metrics["total_latency_ms"] += total_latency_ms
|
| 225 |
+
logger.info(f"Translation complete | Total Latency: {total_latency_ms:.0f}ms | Cached: False")
|
| 226 |
log_metrics()
|
| 227 |
|
| 228 |
return UrduTranslationResponse(
|
|
|
|
| 243 |
async def translate_text(request: TranslationRequest):
|
| 244 |
"""
|
| 245 |
Legacy translation endpoint - translate text between languages
|
|
|
|
| 246 |
"""
|
| 247 |
try:
|
| 248 |
+
if not SERVICES_ENABLED:
|
| 249 |
+
raise HTTPException(status_code=503, detail="Translation service not available")
|
| 250 |
+
|
| 251 |
if request.source_lang == "en" and request.target_lang == "ur":
|
| 252 |
translated_text = translation_service.translate_to_urdu(request.text)
|
| 253 |
elif request.source_lang == "ur" and request.target_lang == "en":
|
|
|
|
| 265 |
target_lang=request.target_lang
|
| 266 |
)
|
| 267 |
|
| 268 |
+
except HTTPException:
|
| 269 |
+
raise
|
| 270 |
except Exception as e:
|
| 271 |
logger.error(f"Translation error: {str(e)}")
|
| 272 |
raise HTTPException(status_code=500, detail=f"Error during translation: {str(e)}")
|
|
|
|
| 277 |
"""Health check for translation service"""
|
| 278 |
return {
|
| 279 |
"status": "translation service is running",
|
| 280 |
+
"services_enabled": SERVICES_ENABLED,
|
| 281 |
"database_enabled": DB_ENABLED,
|
| 282 |
+
"rate_limiter_enabled": rate_limiter is not None
|
| 283 |
}
|
| 284 |
|
| 285 |
|
|
|
|
| 289 |
):
|
| 290 |
"""Get translation statistics for current user"""
|
| 291 |
try:
|
| 292 |
+
if not SERVICES_ENABLED:
|
| 293 |
+
raise HTTPException(status_code=503, detail="Service not available")
|
| 294 |
+
|
| 295 |
if not authorization or not authorization.startswith("Bearer "):
|
| 296 |
raise HTTPException(status_code=401, detail="Missing authorization header")
|
| 297 |
|
requirements.txt
CHANGED
|
@@ -1,18 +1,15 @@
|
|
| 1 |
-
fastapi
|
| 2 |
-
uvicorn
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
qdrant-client
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
python-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
pytest-asyncio>=0.21.1
|
| 17 |
-
alembic>=1.13.1
|
| 18 |
-
openai>=1.10.0
|
|
|
|
| 1 |
+
ο»Ώfastapi==0.109.0
|
| 2 |
+
uvicorn==0.27.0
|
| 3 |
+
python-dotenv==1.0.0
|
| 4 |
+
openai==1.12.0
|
| 5 |
+
qdrant-client==1.7.0
|
| 6 |
+
pydantic==2.5.3
|
| 7 |
+
pydantic[email]
|
| 8 |
+
python-multipart==0.0.6
|
| 9 |
+
python-jose[cryptography]==3.3.0
|
| 10 |
+
passlib[bcrypt]==1.7.4
|
| 11 |
+
sqlalchemy==2.0.25
|
| 12 |
+
asyncpg==0.29.0
|
| 13 |
+
httpx==0.26.0
|
| 14 |
+
alembic==1.13.1
|
| 15 |
+
psycopg2-binary
|
|
|
|
|
|
|
|
|