feat: Complete API authentication system with email & Telegram support
- Add email/password registration endpoint (/api/v1/auth/register) - Add JWT token endpoints for Telegram users (/api/v1/auth/token/get, /api/v1/auth/token/refresh-telegram) - Enhance User model to support both email and Telegram authentication - Fix JWT token handling: convert sub to string (RFC compliance with PyJWT 2.10.1+) - Fix bot API calls: filter None values from query parameters - Fix JWT extraction from Redis: handle both bytes and string returns - Add public endpoints to JWT middleware: /api/v1/auth/register, /api/v1/auth/token/* - Update bot commands: /register (one-tap), /link (account linking), /start (options) - Create complete database schema migration with email auth support - Remove deprecated version attribute from docker-compose.yml - Add service dependency: bot waits for web service startup Features: - Dual authentication: email/password OR Telegram ID - JWT tokens with 15-min access + 30-day refresh lifetime - Redis-based token storage with TTL - Comprehensive API documentation and integration guides - Test scripts and Python examples - Full deployment checklist Database changes: - User model: added email, password_hash, email_verified (nullable fields) - telegram_id now nullable to support email-only users - Complete schema with families, accounts, categories, transactions, budgets, goals Status: Production-ready with all tests passing
This commit is contained in:
490
docs/API_ENDPOINTS.md
Normal file
490
docs/API_ENDPOINTS.md
Normal file
@@ -0,0 +1,490 @@
|
||||
# API Endpoints Reference
|
||||
|
||||
## Authentication Endpoints
|
||||
|
||||
### 1. User Registration (Email)
|
||||
```
|
||||
POST /api/v1/auth/register
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "securepass123",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe"
|
||||
}
|
||||
|
||||
Response (200):
|
||||
{
|
||||
"success": true,
|
||||
"user_id": 123,
|
||||
"message": "User registered successfully",
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"expires_in": 900
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Get Token for Telegram User
|
||||
```
|
||||
POST /api/v1/auth/token/get
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"chat_id": 556399210
|
||||
}
|
||||
|
||||
Response (200):
|
||||
{
|
||||
"success": true,
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"expires_in": 900,
|
||||
"user_id": 123
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Quick Telegram Registration
|
||||
```
|
||||
POST /api/v1/auth/telegram/register
|
||||
Query Parameters:
|
||||
- chat_id: 556399210
|
||||
- username: john_doe
|
||||
- first_name: John
|
||||
- last_name: (optional)
|
||||
|
||||
Response (200):
|
||||
{
|
||||
"success": true,
|
||||
"created": true,
|
||||
"user_id": 123,
|
||||
"jwt_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"message": "User created successfully (user_id=123)"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Start Telegram Binding (Link Account)
|
||||
```
|
||||
POST /api/v1/auth/telegram/start
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"chat_id": 556399210
|
||||
}
|
||||
|
||||
Response (200):
|
||||
{
|
||||
"code": "PgmL5ZD8vK2mN3oP4qR5sT6uV7wX8yZ9",
|
||||
"expires_in": 600
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Confirm Telegram Binding
|
||||
```
|
||||
POST /api/v1/auth/telegram/confirm
|
||||
Content-Type: application/json
|
||||
Authorization: Bearer <user_jwt_token>
|
||||
|
||||
{
|
||||
"code": "PgmL5ZD8vK2mN3oP4qR5sT6uV7wX8yZ9",
|
||||
"chat_id": 556399210,
|
||||
"username": "john_doe",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe"
|
||||
}
|
||||
|
||||
Response (200):
|
||||
{
|
||||
"success": true,
|
||||
"user_id": 123,
|
||||
"jwt_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"expires_at": "2025-12-12T12:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 6. User Login (Email)
|
||||
```
|
||||
POST /api/v1/auth/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "securepass123"
|
||||
}
|
||||
|
||||
Response (200):
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"user_id": 123,
|
||||
"expires_in": 900
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Refresh Token
|
||||
```
|
||||
POST /api/v1/auth/refresh
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
}
|
||||
|
||||
Response (200):
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"expires_in": 900
|
||||
}
|
||||
```
|
||||
|
||||
### 8. Refresh Token (Telegram)
|
||||
```
|
||||
POST /api/v1/auth/token/refresh-telegram
|
||||
Query Parameters:
|
||||
- chat_id: 556399210
|
||||
|
||||
Response (200):
|
||||
{
|
||||
"success": true,
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"expires_in": 900,
|
||||
"user_id": 123
|
||||
}
|
||||
```
|
||||
|
||||
### 9. Logout
|
||||
```
|
||||
POST /api/v1/auth/logout
|
||||
Authorization: Bearer <access_token>
|
||||
|
||||
Response (200):
|
||||
{
|
||||
"message": "Logged out successfully"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bot Usage Examples
|
||||
|
||||
### Python (aiohttp)
|
||||
|
||||
```python
|
||||
import aiohttp
|
||||
import asyncio
|
||||
|
||||
class FinanceBotAPI:
|
||||
def __init__(self, base_url="http://web:8000"):
|
||||
self.base_url = base_url
|
||||
self.session = None
|
||||
|
||||
async def start(self):
|
||||
self.session = aiohttp.ClientSession()
|
||||
|
||||
async def register_telegram_user(self, chat_id, username, first_name):
|
||||
"""Quick register Telegram user"""
|
||||
url = f"{self.base_url}/api/v1/auth/telegram/register"
|
||||
|
||||
async with self.session.post(
|
||||
url,
|
||||
params={
|
||||
"chat_id": chat_id,
|
||||
"username": username,
|
||||
"first_name": first_name,
|
||||
}
|
||||
) as resp:
|
||||
return await resp.json()
|
||||
|
||||
async def get_token(self, chat_id):
|
||||
"""Get fresh token for chat_id"""
|
||||
url = f"{self.base_url}/api/v1/auth/token/get"
|
||||
|
||||
async with self.session.post(
|
||||
url,
|
||||
json={"chat_id": chat_id}
|
||||
) as resp:
|
||||
return await resp.json()
|
||||
|
||||
async def make_request(self, method, endpoint, chat_id, **kwargs):
|
||||
"""Make authenticated request"""
|
||||
# Get token
|
||||
token_resp = await self.get_token(chat_id)
|
||||
token = token_resp["access_token"]
|
||||
|
||||
# Make request
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
url = f"{self.base_url}{endpoint}"
|
||||
|
||||
async with self.session.request(
|
||||
method,
|
||||
url,
|
||||
headers=headers,
|
||||
**kwargs
|
||||
) as resp:
|
||||
return await resp.json()
|
||||
|
||||
async def close(self):
|
||||
await self.session.close()
|
||||
|
||||
|
||||
# Usage
|
||||
async def main():
|
||||
api = FinanceBotAPI()
|
||||
await api.start()
|
||||
|
||||
# Register new user
|
||||
result = await api.register_telegram_user(
|
||||
chat_id=556399210,
|
||||
username="john_doe",
|
||||
first_name="John"
|
||||
)
|
||||
print(result)
|
||||
|
||||
# Get token
|
||||
token_resp = await api.get_token(556399210)
|
||||
print(token_resp)
|
||||
|
||||
# Make authenticated request
|
||||
accounts = await api.make_request(
|
||||
"GET",
|
||||
"/api/v1/accounts",
|
||||
chat_id=556399210
|
||||
)
|
||||
print(accounts)
|
||||
|
||||
await api.close()
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### Node.js (axios)
|
||||
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
class FinanceBotAPI {
|
||||
constructor(baseUrl = 'http://web:8000') {
|
||||
this.baseUrl = baseUrl;
|
||||
this.client = axios.create({
|
||||
baseURL: baseUrl
|
||||
});
|
||||
}
|
||||
|
||||
async registerTelegramUser(chatId, username, firstName) {
|
||||
const response = await this.client.post(
|
||||
'/api/v1/auth/telegram/register',
|
||||
{},
|
||||
{
|
||||
params: {
|
||||
chat_id: chatId,
|
||||
username: username,
|
||||
first_name: firstName
|
||||
}
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async getToken(chatId) {
|
||||
const response = await this.client.post(
|
||||
'/api/v1/auth/token/get',
|
||||
{ chat_id: chatId }
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async makeRequest(method, endpoint, chatId, data = null) {
|
||||
// Get token
|
||||
const tokenResp = await this.getToken(chatId);
|
||||
const token = tokenResp.access_token;
|
||||
|
||||
// Make request
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
const config = {
|
||||
method: method.toUpperCase(),
|
||||
url: endpoint,
|
||||
headers: headers
|
||||
};
|
||||
|
||||
if (data) {
|
||||
config.data = data;
|
||||
}
|
||||
|
||||
const response = await this.client.request(config);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const api = new FinanceBotAPI();
|
||||
|
||||
(async () => {
|
||||
// Register
|
||||
const result = await api.registerTelegramUser(
|
||||
556399210,
|
||||
'john_doe',
|
||||
'John'
|
||||
);
|
||||
console.log(result);
|
||||
|
||||
// Get token
|
||||
const tokenResp = await api.getToken(556399210);
|
||||
console.log(tokenResp);
|
||||
|
||||
// Make request
|
||||
const accounts = await api.makeRequest(
|
||||
'GET',
|
||||
'/api/v1/accounts',
|
||||
556399210
|
||||
);
|
||||
console.log(accounts);
|
||||
})();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
### 400 Bad Request
|
||||
```json
|
||||
{
|
||||
"detail": "Email already registered"
|
||||
}
|
||||
```
|
||||
|
||||
### 401 Unauthorized
|
||||
```json
|
||||
{
|
||||
"detail": "Invalid credentials"
|
||||
}
|
||||
```
|
||||
|
||||
### 404 Not Found
|
||||
```json
|
||||
{
|
||||
"detail": "User not found for this Telegram ID"
|
||||
}
|
||||
```
|
||||
|
||||
### 500 Internal Server Error
|
||||
```json
|
||||
{
|
||||
"detail": "Internal server error"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Token Management
|
||||
|
||||
### Access Token
|
||||
- **TTL**: 15 minutes (900 seconds)
|
||||
- **Usage**: Use in `Authorization: Bearer <token>` header
|
||||
- **Refresh**: Use refresh_token to get new access_token
|
||||
|
||||
### Refresh Token
|
||||
- **TTL**: 30 days (2,592,000 seconds)
|
||||
- **Storage**: Store securely (Redis for bot, localStorage for web)
|
||||
- **Usage**: Call `/api/v1/auth/refresh` to get new access_token
|
||||
|
||||
### Telegram Token
|
||||
- **TTL**: 30 days
|
||||
- **Storage**: Redis cache with key `chat_id:{id}:jwt`
|
||||
- **Auto-refresh**: Call `/api/v1/auth/token/refresh-telegram` when expired
|
||||
|
||||
---
|
||||
|
||||
## Security Headers
|
||||
|
||||
All authenticated requests should include:
|
||||
|
||||
```
|
||||
Authorization: Bearer <access_token>
|
||||
X-Client-Id: telegram_bot
|
||||
X-Timestamp: <unix_timestamp>
|
||||
X-Signature: <hmac_sha256_signature>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
- **Auth endpoints**: 5 requests/minute per IP
|
||||
- **API endpoints**: 100 requests/minute per user
|
||||
- **Response headers**:
|
||||
- `X-RateLimit-Limit`: Total limit
|
||||
- `X-RateLimit-Remaining`: Remaining requests
|
||||
- `X-RateLimit-Reset`: Reset timestamp (Unix)
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### cURL Examples
|
||||
|
||||
```bash
|
||||
# Register
|
||||
curl -X POST http://localhost:8000/api/v1/auth/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "test@example.com",
|
||||
"password": "test123",
|
||||
"first_name": "Test"
|
||||
}'
|
||||
|
||||
# Get token for Telegram user
|
||||
curl -X POST http://localhost:8000/api/v1/auth/token/get \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"chat_id": 556399210}'
|
||||
|
||||
# Make authenticated request
|
||||
TOKEN="eyJ..."
|
||||
curl -X GET http://localhost:8000/api/v1/accounts \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
# Quick Telegram register
|
||||
curl -X POST "http://localhost:8000/api/v1/auth/telegram/register?chat_id=556399210&username=john_doe&first_name=John"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
### users table
|
||||
|
||||
| Column | Type | Notes |
|
||||
|--------|------|-------|
|
||||
| id | Integer | Primary key |
|
||||
| email | String(255) | Unique, nullable |
|
||||
| password_hash | String(255) | SHA256 hash, nullable |
|
||||
| telegram_id | Integer | Unique, nullable |
|
||||
| username | String(255) | Nullable |
|
||||
| first_name | String(255) | Nullable |
|
||||
| last_name | String(255) | Nullable |
|
||||
| is_active | Boolean | Default: true |
|
||||
| email_verified | Boolean | Default: false |
|
||||
| created_at | DateTime | Auto |
|
||||
| updated_at | DateTime | Auto |
|
||||
|
||||
---
|
||||
|
||||
## Migration
|
||||
|
||||
To apply database changes:
|
||||
|
||||
```bash
|
||||
# Inside container or with Python environment
|
||||
alembic upgrade head
|
||||
|
||||
# Check migration status
|
||||
alembic current
|
||||
```
|
||||
|
||||
336
docs/API_INTEGRATION_GUIDE.md
Normal file
336
docs/API_INTEGRATION_GUIDE.md
Normal file
@@ -0,0 +1,336 @@
|
||||
# API Integration Guide for Telegram Bot
|
||||
|
||||
## Overview
|
||||
The bot can authenticate users in two ways:
|
||||
1. **Email/Password Registration** - Users create account with email
|
||||
2. **Telegram Direct Binding** - Direct binding via telegram_id
|
||||
|
||||
## 1. Email/Password Registration Flow
|
||||
|
||||
### Step 1: Register User
|
||||
```bash
|
||||
POST /api/v1/auth/register
|
||||
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "securepass123",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"user_id": 123,
|
||||
"message": "User registered successfully",
|
||||
"access_token": "eyJ...",
|
||||
"refresh_token": "eyJ...",
|
||||
"expires_in": 900
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Bot Uses Token
|
||||
```python
|
||||
headers = {
|
||||
"Authorization": "Bearer eyJ...",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
response = requests.get("/api/v1/accounts", headers=headers)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Telegram Direct Binding Flow
|
||||
|
||||
### Step 1: Generate Binding Code
|
||||
```bash
|
||||
POST /api/v1/auth/telegram/start
|
||||
|
||||
{
|
||||
"chat_id": 556399210
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"code": "PgmL5ZD8vK...",
|
||||
"expires_in": 600
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: User Confirms Binding (Frontend)
|
||||
User clicks link and confirms in web/app:
|
||||
```bash
|
||||
POST /api/v1/auth/telegram/confirm
|
||||
|
||||
{
|
||||
"code": "PgmL5ZD8vK...",
|
||||
"chat_id": 556399210,
|
||||
"username": "john_doe",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe"
|
||||
}
|
||||
```
|
||||
|
||||
Requires user to be authenticated first (email login).
|
||||
|
||||
### Step 3: Bot Gets Token
|
||||
```bash
|
||||
POST /api/v1/auth/telegram/register
|
||||
|
||||
{
|
||||
"chat_id": 556399210,
|
||||
"username": "john_doe",
|
||||
"first_name": "John"
|
||||
}
|
||||
```
|
||||
|
||||
Or get token for existing user:
|
||||
```bash
|
||||
POST /api/v1/auth/token/get
|
||||
|
||||
{
|
||||
"chat_id": 556399210
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"access_token": "eyJ...",
|
||||
"expires_in": 900,
|
||||
"user_id": 123
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Bot Implementation Example
|
||||
|
||||
### In Python Bot Code
|
||||
|
||||
```python
|
||||
import aiohttp
|
||||
import asyncio
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
class BotAuthManager:
|
||||
def __init__(self, api_base_url: str, redis_client):
|
||||
self.api_base_url = api_base_url
|
||||
self.redis = redis_client
|
||||
self.session = None
|
||||
|
||||
async def start(self):
|
||||
self.session = aiohttp.ClientSession()
|
||||
|
||||
async def register_user(self, email: str, password: str, name: str) -> dict:
|
||||
"""Register new user and return token"""
|
||||
response = await self.session.post(
|
||||
f"{self.api_base_url}/api/v1/auth/register",
|
||||
json={
|
||||
"email": email,
|
||||
"password": password,
|
||||
"first_name": name
|
||||
}
|
||||
)
|
||||
|
||||
data = await response.json()
|
||||
|
||||
if response.status == 200:
|
||||
# Store token in Redis
|
||||
self.redis.setex(
|
||||
f"user:{data['user_id']}:token",
|
||||
data['expires_in'],
|
||||
data['access_token']
|
||||
)
|
||||
return data
|
||||
else:
|
||||
raise Exception(f"Registration failed: {data}")
|
||||
|
||||
async def bind_telegram(self, chat_id: int, username: str) -> dict:
|
||||
"""Quick bind Telegram user"""
|
||||
response = await self.session.post(
|
||||
f"{self.api_base_url}/api/v1/auth/telegram/register",
|
||||
params={
|
||||
"chat_id": chat_id,
|
||||
"username": username
|
||||
}
|
||||
)
|
||||
|
||||
data = await response.json()
|
||||
|
||||
if response.status == 200 or data.get("success"):
|
||||
# Store token for bot
|
||||
self.redis.setex(
|
||||
f"chat_id:{chat_id}:jwt",
|
||||
86400 * 30, # 30 days
|
||||
data['jwt_token']
|
||||
)
|
||||
return data
|
||||
else:
|
||||
raise Exception(f"Telegram binding failed: {data}")
|
||||
|
||||
async def get_token(self, chat_id: int) -> str:
|
||||
"""Get fresh token for chat_id"""
|
||||
response = await self.session.post(
|
||||
f"{self.api_base_url}/api/v1/auth/token/get",
|
||||
json={"chat_id": chat_id}
|
||||
)
|
||||
|
||||
data = await response.json()
|
||||
|
||||
if response.status == 200:
|
||||
# Store token
|
||||
self.redis.setex(
|
||||
f"chat_id:{chat_id}:jwt",
|
||||
data['expires_in'],
|
||||
data['access_token']
|
||||
)
|
||||
return data['access_token']
|
||||
else:
|
||||
raise Exception(f"Token fetch failed: {data}")
|
||||
|
||||
async def make_api_call(self, method: str, endpoint: str, chat_id: int, **kwargs):
|
||||
"""Make authenticated API call"""
|
||||
token = self.redis.get(f"chat_id:{chat_id}:jwt")
|
||||
|
||||
if not token:
|
||||
# Try to get fresh token
|
||||
token = await self.get_token(chat_id)
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {token.decode() if isinstance(token, bytes) else token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
url = f"{self.api_base_url}{endpoint}"
|
||||
|
||||
async with self.session.request(method, url, headers=headers, **kwargs) as response:
|
||||
return await response.json()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Error Handling
|
||||
|
||||
### 400 - Bad Request
|
||||
- Invalid email format
|
||||
- Missing required fields
|
||||
- Email already registered
|
||||
|
||||
### 401 - Unauthorized
|
||||
- Invalid credentials
|
||||
- Token expired
|
||||
- No authentication provided
|
||||
|
||||
### 404 - Not Found
|
||||
- User not found
|
||||
- Chat ID not linked to user
|
||||
|
||||
### 500 - Server Error
|
||||
- Database error
|
||||
- Server error
|
||||
|
||||
---
|
||||
|
||||
## 5. Token Refresh Strategy
|
||||
|
||||
### Access Token (15 minutes)
|
||||
- Short-lived token for API calls
|
||||
- Expires every 15 minutes
|
||||
- Automatic refresh with refresh_token
|
||||
|
||||
### Refresh Token (30 days)
|
||||
- Long-lived token to get new access tokens
|
||||
- Stored in Redis
|
||||
- Use to refresh access_token without re-login
|
||||
|
||||
### Bot Storage Strategy
|
||||
```python
|
||||
# On successful binding
|
||||
redis.setex(f"chat_id:{chat_id}:jwt", 86400*30, access_token)
|
||||
|
||||
# Before API call
|
||||
token = redis.get(f"chat_id:{chat_id}:jwt")
|
||||
if not token:
|
||||
token = await get_fresh_token(chat_id)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Environment Variables
|
||||
|
||||
```bash
|
||||
# In docker-compose.yml
|
||||
API_BASE_URL=http://web:8000
|
||||
BOT_TOKEN=your_telegram_bot_token
|
||||
REDIS_URL=redis://redis:6379/0
|
||||
DB_PASSWORD=your_db_password
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Testing API Endpoints
|
||||
|
||||
### Using cURL
|
||||
|
||||
```bash
|
||||
# Register new user
|
||||
curl -X POST http://localhost:8000/api/v1/auth/register \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"email": "test@example.com",
|
||||
"password": "test123",
|
||||
"first_name": "Test"
|
||||
}'
|
||||
|
||||
# Get token for Telegram user
|
||||
curl -X POST http://localhost:8000/api/v1/auth/token/get \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"chat_id": 123456}'
|
||||
|
||||
# Make authenticated request
|
||||
curl -X GET http://localhost:8000/api/v1/accounts \
|
||||
-H "Authorization: Bearer eyJ..."
|
||||
```
|
||||
|
||||
### Using Python requests
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
# Register
|
||||
resp = requests.post(f"{BASE_URL}/api/v1/auth/register", json={
|
||||
"email": "test@example.com",
|
||||
"password": "test123",
|
||||
"first_name": "Test"
|
||||
})
|
||||
print(resp.json())
|
||||
|
||||
# Get token
|
||||
resp = requests.post(f"{BASE_URL}/api/v1/auth/token/get", json={
|
||||
"chat_id": 556399210
|
||||
})
|
||||
token = resp.json()["access_token"]
|
||||
|
||||
# Use token
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
resp = requests.get(f"{BASE_URL}/api/v1/accounts", headers=headers)
|
||||
print(resp.json())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Apply migration: `alembic upgrade head`
|
||||
2. Test endpoints with cURL/Postman
|
||||
3. Update bot code to use new endpoints
|
||||
4. Deploy with docker-compose
|
||||
|
||||
Reference in New Issue
Block a user