Base code

This commit is contained in:
Kunthawat Greethong
2026-01-08 22:39:53 +07:00
parent 697115c61a
commit c35fa52117
2169 changed files with 626670 additions and 0 deletions

View File

@@ -0,0 +1,349 @@
# API Key Management Architecture
## Overview
ALwrity supports two deployment modes with different API key management strategies:
1. **Local Development**: API keys stored in `.env` files for convenience
2. **Production (Vercel + Render)**: User-specific API keys stored in database with full user isolation
## Architecture
### 🏠 **Local Development Mode**
**Detection:**
- `DEBUG=true` in environment variables, OR
- `DEPLOY_ENV` is not set
**API Key Storage:**
- **Backend**: `backend/.env` file
- **Frontend**: `frontend/.env` file
- **Database**: Also saved for consistency
**Flow:**
```
User completes onboarding
API keys saved to database (user-isolated)
API keys ALSO saved to .env files (for convenience)
Backend services read from .env file
Single developer, single set of keys
```
**Advantages:**
- ✅ Quick setup for developers
- ✅ No need to configure environment for every user
- ✅ Keys persist across server restarts
---
### 🌐 **Production Mode (Vercel + Render)**
**Detection:**
- `DEBUG=false` or not set, AND
- `DEPLOY_ENV` is set (e.g., `DEPLOY_ENV=render`)
**API Key Storage:**
- **Backend**: PostgreSQL database (user-isolated)
- **Frontend**: `localStorage` (runtime only)
- **NOT in .env files**
**Flow:**
```
Alpha Tester A completes onboarding
API keys saved to database with user_id_A
Backend services fetch keys from database when user_id_A makes requests
Multiple users, each with their own keys
Alpha Tester B completes onboarding
API keys saved to database with user_id_B
Backend services fetch keys from database when user_id_B makes requests
```
**Advantages:**
-**Complete user isolation** - User A's keys never conflict with User B's keys
-**Zero cost for you** - Each alpha tester uses their own API keys
-**Secure** - Keys stored encrypted in database
-**Scalable** - Unlimited alpha testers, each with their own keys
---
## Implementation
### **1. Backend: User API Key Context**
The `UserAPIKeyContext` class provides user-specific API keys to backend services:
```python
from services.user_api_key_context import user_api_keys
# In your backend service
async def generate_content(user_id: str, prompt: str):
# Get user-specific API keys
with user_api_keys(user_id) as keys:
gemini_key = keys.get('gemini')
exa_key = keys.get('exa')
# Use keys for this specific user
response = await call_gemini_api(gemini_key, prompt)
return response
```
**How it works:**
- **Development**: Reads from `backend/.env`
- **Production**: Fetches from database for the specific `user_id`
### **2. Frontend: CopilotKit Key Management**
```typescript
// Frontend automatically handles this:
// 1. Saves to localStorage (for runtime use)
// 2. In dev: Also saves to frontend/.env
// 3. In prod: Only uses localStorage
const copilotApiKey = localStorage.getItem('copilotkit_api_key');
```
### **3. Environment Variable Detection**
**Backend (`backend/.env`):**
```bash
# Development
DEBUG=true
# Production
DEBUG=false
DEPLOY_ENV=render # or 'railway', 'heroku', etc.
```
**Render Dashboard:**
```
DEBUG=false
DEPLOY_ENV=render
```
**Vercel Dashboard:**
```
REACT_APP_API_URL=https://alwrity.onrender.com
REACT_APP_BACKEND_URL=https://alwrity.onrender.com
```
---
## Use Cases
### **Use Case 1: You (Developer) - Local Development**
**Setup:**
```bash
# backend/.env
DEBUG=true
GEMINI_API_KEY=your_personal_key
EXA_API_KEY=your_personal_key
COPILOTKIT_API_KEY=your_personal_key
```
**Behavior:**
- You complete onboarding once
- Keys saved to both database AND `.env` files
- All your local testing uses these keys
- No need to re-enter keys
---
### **Use Case 2: Alpha Tester A - Production**
**Setup:**
- Alpha Tester A visits `https://alwrity-ai.vercel.app`
- Goes through onboarding
- Enters their own API keys:
- `GEMINI_API_KEY=tester_a_gemini_key`
- `EXA_API_KEY=tester_a_exa_key`
- `COPILOTKIT_API_KEY=tester_a_copilot_key`
**Behavior:**
- Keys saved to database with `user_id=tester_a_clerk_id`
- When Tester A generates content:
- Backend fetches `tester_a_gemini_key` from database
- Uses Tester A's Gemini quota
- All costs charged to Tester A's Gemini account
---
### **Use Case 3: Alpha Tester B - Production (Same Time)**
**Setup:**
- Alpha Tester B visits `https://alwrity-ai.vercel.app`
- Goes through onboarding
- Enters their own API keys:
- `GEMINI_API_KEY=tester_b_gemini_key`
- `EXA_API_KEY=tester_b_exa_key`
- `COPILOTKIT_API_KEY=tester_b_copilot_key`
**Behavior:**
- Keys saved to database with `user_id=tester_b_clerk_id`
- When Tester B generates content:
- Backend fetches `tester_b_gemini_key` from database
- Uses Tester B's Gemini quota
- All costs charged to Tester B's Gemini account
- **Tester A and Tester B completely isolated** ✅
---
## Database Schema
```sql
-- OnboardingSession: One per user
CREATE TABLE onboarding_sessions (
id SERIAL PRIMARY KEY,
user_id VARCHAR(255) UNIQUE NOT NULL, -- Clerk user ID
current_step INTEGER DEFAULT 1,
progress FLOAT DEFAULT 0.0,
started_at TIMESTAMP DEFAULT NOW(),
completed_at TIMESTAMP
);
-- APIKey: Multiple per user (one per provider)
CREATE TABLE api_keys (
id SERIAL PRIMARY KEY,
session_id INTEGER REFERENCES onboarding_sessions(id),
provider VARCHAR(50) NOT NULL, -- 'gemini', 'exa', 'copilotkit'
key TEXT NOT NULL, -- Encrypted in production
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(session_id, provider) -- One key per provider per user
);
```
**Isolation:**
- Each user has their own `onboarding_session`
- Each session has its own set of `api_keys`
- Query: `SELECT key FROM api_keys WHERE session_id = (SELECT id FROM onboarding_sessions WHERE user_id = ?)`
---
## Migration Path
### **Current State:**
- ❌ All users' keys overwrite the same `.env` file
- ❌ Last user's keys are used for all users
### **New State:**
- ✅ Development: `.env` file for convenience
- ✅ Production: Database per user
- ✅ Complete user isolation
### **Code Changes Required:**
**Before (BAD - uses global .env):**
```python
import os
def generate_content(prompt: str):
gemini_key = os.getenv('GEMINI_API_KEY') # Same for all users!
response = call_gemini_api(gemini_key, prompt)
return response
```
**After (GOOD - uses user-specific keys):**
```python
from services.user_api_key_context import user_api_keys
def generate_content(user_id: str, prompt: str):
with user_api_keys(user_id) as keys:
gemini_key = keys.get('gemini') # User-specific key!
response = call_gemini_api(gemini_key, prompt)
return response
```
---
## Testing
### **Test Local Development:**
1. Set `DEBUG=true` in `backend/.env`
2. Complete onboarding with test keys
3. Check `backend/.env` - should contain keys ✅
4. Generate content - should use keys from `.env`
### **Test Production:**
1. Set `DEBUG=false` and `DEPLOY_ENV=render` on Render
2. User A completes onboarding with keys A
3. User B completes onboarding with keys B
4. User A generates content - uses keys A ✅
5. User B generates content - uses keys B ✅
6. Check database:
```sql
SELECT user_id, provider, key FROM api_keys
JOIN onboarding_sessions ON api_keys.session_id = onboarding_sessions.id;
```
Should show separate keys for User A and User B ✅
---
## Security Considerations
### **Production Enhancements (Future):**
1. **Encrypt API keys** in database using application secret
2. **Rate limiting** per user to prevent abuse
3. **Key validation** before saving
4. **Audit logging** of API key usage
5. **Key rotation** support
### **Current Implementation:**
- ✅ Keys stored in database (not in code)
- ✅ User isolation via `user_id`
- ✅ HTTPS encryption in transit
- ⚠️ Keys not encrypted at rest (TODO)
---
## Troubleshooting
### **Issue: "No API key found"**
- **Development**: Check `backend/.env` file exists and has keys
- **Production**: Check database has keys for this user:
```sql
SELECT * FROM api_keys
WHERE session_id = (SELECT id FROM onboarding_sessions WHERE user_id = 'user_xxx');
```
### **Issue: "Wrong user's keys being used"**
- **Cause**: Service not using `UserAPIKeyContext`
- **Fix**: Update service to use `user_api_keys(user_id)` context manager
### **Issue: "Keys not saving to .env in development"**
- **Cause**: `DEBUG` not set to `true`
- **Fix**: Set `DEBUG=true` in `backend/.env`
---
## Summary
| Feature | Local Development | Production |
|---------|------------------|------------|
| **Key Storage** | `.env` files + Database | Database only |
| **User Isolation** | Not needed (single user) | Full isolation |
| **Cost** | Your API keys | Each user's API keys |
| **Convenience** | High (keys persist) | Medium (enter once) |
| **Scalability** | 1 developer | Unlimited users |
| **Detection** | `DEBUG=true` | `DEPLOY_ENV` set |
**Bottom Line:**
- 🏠 **Local**: Quick setup, your keys, `.env` convenience
- 🌐 **Production**: User isolation, their keys, zero cost for you
This architecture ensures:
1. ✅ You can develop locally with convenience
2. ✅ Alpha testers use their own keys (no cost to you)
3. ✅ Complete user isolation in production
4. ✅ Seamless transition between environments