Commit 118c3180 authored by Hoanganhvu123's avatar Hoanganhvu123

update

parent 1732c42d
VITE_CLERK_PUBLISHABLE_KEY=pk_test_Y29tbXVuYWwtc3VuYmVhbS0wLmNsZXJrLmFjY291bnRzLmRldiQ
CLERK_SECRET_KEY=sk_test_ek7ozVR80Qi9UdvhGaTmlXovS16GDuBDlDrpH1rkyQ
\ No newline at end of file
...@@ -120,8 +120,8 @@ async def get_user_openai_key( ...@@ -120,8 +120,8 @@ async def get_user_openai_key(
@router.put("/{user_id}/openai-key", summary="Update user's OpenAI API key") @router.put("/{user_id}/openai-key", summary="Update user's OpenAI API key")
async def update_user_openai_key( async def update_user_openai_key(
user_id: str, user_id: str,
payload: dict = Body(...),
request: Request, request: Request,
payload: dict = Body(...),
settings_service=Depends(get_user_settings_service), settings_service=Depends(get_user_settings_service),
): ):
""" """
......
...@@ -39,7 +39,28 @@ class AuthService: ...@@ -39,7 +39,28 @@ class AuthService:
async def get_me(self, token: str | None = None) -> schemas.AuthMeResponse: async def get_me(self, token: str | None = None) -> schemas.AuthMeResponse:
if not token: if not token:
raise ValueError("Missing authentication token") raise ValueError("Missing authentication token")
return schemas.AuthMeResponse(id="1", email="demo@example.com")
# Verify Clerk JWT token
import logging
try:
from config import CLERK_JWKS_URL, CLERK_ISSUER
# Only verify if Clerk is configured
if CLERK_JWKS_URL and CLERK_ISSUER:
from common.clerk_auth import verify_clerk_jwt
payload = verify_clerk_jwt(token)
# Extract user info from Clerk token
user_id = payload.get("sub") or payload.get("user_id") or "1"
email = payload.get("email") or "demo@example.com"
logging.info(f"✅ Clerk token verified for user: {user_id}")
return schemas.AuthMeResponse(id=user_id, email=email)
else:
# Clerk not configured - accept any token (dev mode)
logging.warning("⚠️ Clerk not configured, accepting token without verification")
return schemas.AuthMeResponse(id="1", email="demo@example.com")
except Exception as e:
# If Clerk verification fails, log and raise
logging.error(f"❌ Clerk token verification failed: {e}")
raise ValueError(f"Invalid authentication token: {str(e)}") from e
class UserService: class UserService:
......
...@@ -3,14 +3,15 @@ set -e ...@@ -3,14 +3,15 @@ set -e
echo "🚀 Starting CuCu Note Backend..." echo "🚀 Starting CuCu Note Backend..."
# Wait for MongoDB to be ready # Try to connect to MongoDB (non-blocking, app will retry on startup)
if [ -n "$MONGODB_URI" ]; then if [ -n "$MONGODB_URI" ]; then
echo "⏳ Waiting for MongoDB..." echo "⏳ Testing MongoDB connection..."
until python -c "import motor.motor_asyncio; import asyncio; asyncio.run(motor.motor_asyncio.AsyncIOMotorClient('$MONGODB_URI').admin.command('ping'))" 2>/dev/null; do if python -c "import motor.motor_asyncio; import asyncio; asyncio.run(motor.motor_asyncio.AsyncIOMotorClient('$MONGODB_URI', serverSelectionTimeoutMS=3000).admin.command('ping'))" 2>/dev/null; then
echo "MongoDB is unavailable - sleeping" echo "✅ MongoDB connection successful!"
sleep 2 else
done echo "⚠️ MongoDB connection failed. App will start and retry on first request."
echo "✅ MongoDB is ready!" echo "⚠️ Please ensure Network Access in MongoDB Atlas allows your IP (or 0.0.0.0/0 for testing)."
fi
fi fi
# Wait for Redis to be ready (if configured) # Wait for Redis to be ready (if configured)
...@@ -23,23 +24,33 @@ if [ -n "$REDIS_HOST" ]; then ...@@ -23,23 +24,33 @@ if [ -n "$REDIS_HOST" ]; then
echo "✅ Redis is ready!" echo "✅ Redis is ready!"
fi fi
# Run database migrations/indexes (if needed) # Run database migrations/indexes (if needed) - skip if MongoDB not available
echo "📊 Setting up database indexes..." echo "📊 Setting up database indexes..."
python -c " python -c "
import asyncio import asyncio
import sys
from common.mongo_client import mongodb_client from common.mongo_client import mongodb_client
async def setup(): async def setup():
await mongodb_client.connect() try:
print('✅ Database indexes ready') await mongodb_client.connect()
print('✅ Database indexes ready')
except Exception as e:
print(f'⚠️ Could not set up indexes: {e}')
sys.exit(0) # Don't fail startup
asyncio.run(setup()) asyncio.run(setup())
" || echo "⚠️ Could not set up indexes (may already exist)" " || echo "⚠️ Could not set up indexes (will retry on first request)"
# Start the server # Start the server
echo "🌟 Starting Gunicorn server..." echo "🌟 Starting Gunicorn server..."
# Allow overriding number of workers via env, default to 1 for simplicity
WORKERS="${GUNICORN_WORKERS:-1}"
echo "🔧 Using Gunicorn workers: $WORKERS"
exec gunicorn \ exec gunicorn \
--workers 4 \ --workers "$WORKERS" \
--worker-class uvicorn.workers.UvicornWorker \ --worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:5000 \ --bind 0.0.0.0:5000 \
--timeout 120 \ --timeout 120 \
......
...@@ -135,4 +135,3 @@ zope.event==6.1 ...@@ -135,4 +135,3 @@ zope.event==6.1
zope.interface==8.1.1 zope.interface==8.1.1
zstandard==0.25.0 zstandard==0.25.0
gunicorn==23.0.0 gunicorn==23.0.0
copilotkit
version: '3.8' version: '3.8'
services: services:
# MongoDB Database
mongodb:
image: mongo:7.0
container_name: cuccu_mongodb
restart: unless-stopped
ports:
- "27017:27017"
volumes:
- mongodb_data:/data/db
- mongodb_config:/data/configdb
environment:
MONGO_INITDB_DATABASE: cucu_note
networks:
- cuccu_network
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
# Redis Cache
redis:
image: redis:7-alpine
container_name: cuccu_redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
networks:
- cuccu_network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# Backend API # Backend API
backend: backend:
build: build:
...@@ -50,14 +12,13 @@ services: ...@@ -50,14 +12,13 @@ services:
- "5000:5000" - "5000:5000"
env_file: env_file:
- ./backend/.env - ./backend/.env
environment:
# MongoDB connection string from Atlas
MONGODB_URI: "mongodb+srv://20010841:vuhoanganh1704@cluster0.h6qro.mongodb.net/cucu_note?retryWrites=true&w=majority&appName=Cluster0"
MONGODB_DB_NAME: "cucu_note"
volumes: volumes:
- ./backend:/app - ./backend:/app
- backend_data:/app/data - backend_data:/app/data
depends_on:
mongodb:
condition: service_healthy
redis:
condition: service_healthy
networks: networks:
- cuccu_network - cuccu_network
healthcheck: healthcheck:
...@@ -80,6 +41,11 @@ services: ...@@ -80,6 +41,11 @@ services:
build: build:
context: ./frontend context: ./frontend
dockerfile: Dockerfile.prod dockerfile: Dockerfile.prod
args:
# Build-time envs for Vite
# Browser (người dùng) gọi trực tiếp vào host, nên dùng localhost:5000
VITE_API_BASE_URL: "http://localhost:5000"
VITE_CLERK_PUBLISHABLE_KEY: ${VITE_CLERK_PUBLISHABLE_KEY}
container_name: cuccu_frontend container_name: cuccu_frontend
restart: unless-stopped restart: unless-stopped
ports: ports:
...@@ -97,12 +63,6 @@ services: ...@@ -97,12 +63,6 @@ services:
retries: 3 retries: 3
volumes: volumes:
mongodb_data:
driver: local
mongodb_config:
driver: local
redis_data:
driver: local
backend_data: backend_data:
driver: local driver: local
......
...@@ -20,10 +20,6 @@ build/ ...@@ -20,10 +20,6 @@ build/
*.swp *.swp
*.swo *.swo
# Environment
.env
.env.local
# Testing # Testing
tests/ tests/
coverage/ coverage/
......
...@@ -7,9 +7,10 @@ WORKDIR /app ...@@ -7,9 +7,10 @@ WORKDIR /app
COPY package*.json ./ COPY package*.json ./
COPY pnpm-lock.yaml* ./ COPY pnpm-lock.yaml* ./
# Install pnpm if pnpm-lock.yaml exists, otherwise use npm # Install dependencies
# Use pnpm if lockfile exists, but allow it to update (no --frozen-lockfile to avoid ERR_PNPM_OUTDATED_LOCKFILE)
RUN if [ -f pnpm-lock.yaml ]; then \ RUN if [ -f pnpm-lock.yaml ]; then \
npm install -g pnpm && pnpm install --frozen-lockfile; \ npm install -g pnpm && pnpm install --no-frozen-lockfile; \
else \ else \
npm ci; \ npm ci; \
fi fi
...@@ -21,6 +22,10 @@ COPY . . ...@@ -21,6 +22,10 @@ COPY . .
ARG VITE_API_BASE_URL=http://localhost:5000 ARG VITE_API_BASE_URL=http://localhost:5000
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
# Clerk publishable key for frontend (must start with VITE_ to be exposed)
ARG VITE_CLERK_PUBLISHABLE_KEY
ENV VITE_CLERK_PUBLISHABLE_KEY=$VITE_CLERK_PUBLISHABLE_KEY
RUN if [ -f pnpm-lock.yaml ]; then \ RUN if [ -f pnpm-lock.yaml ]; then \
pnpm build; \ pnpm build; \
else \ else \
......
VITE_CLERK_PUBLISHABLE_KEY=pk_test_Y29tbXVuYWwtc3VuYmVhbS0wLmNsZXJrLmFjY291bnRzLmRldiQ
CLERK_SECRET_KEY=sk_test_ek7ozVR80Qi9UdvhGaTmlXovS16GDuBDlDrpH1rkyQ
\ No newline at end of file
...@@ -54,6 +54,24 @@ export function AuthProvider({ children }: { children: ReactNode }) { ...@@ -54,6 +54,24 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const initialize = useCallback(async () => { const initialize = useCallback(async () => {
setState((prev) => ({ ...prev, isLoading: true })); setState((prev) => ({ ...prev, isLoading: true }));
try { try {
// Check if we have a token/session before making API call
// This prevents unnecessary 401 errors when user is not logged in
const hasToken = localStorage.getItem("accessToken") || sessionStorage.getItem("accessToken");
if (!hasToken) {
// No token means user is not logged in - skip API call
clearAccessToken();
setState({
currentUser: undefined,
userGeneralSetting: undefined,
userWebhooksSetting: undefined,
shortcuts: [],
isInitialized: true,
isLoading: false,
});
return;
}
const { user: currentUser } = await authServiceClient.getCurrentUser({}); const { user: currentUser } = await authServiceClient.getCurrentUser({});
if (!currentUser) { if (!currentUser) {
...@@ -82,12 +100,21 @@ export function AuthProvider({ children }: { children: ReactNode }) { ...@@ -82,12 +100,21 @@ export function AuthProvider({ children }: { children: ReactNode }) {
queryClient.setQueryData(userKeys.currentUser(), currentUser); queryClient.setQueryData(userKeys.currentUser(), currentUser);
queryClient.setQueryData(userKeys.detail(currentUser.name), currentUser); queryClient.setQueryData(userKeys.detail(currentUser.name), currentUser);
} catch (error) { } catch (error) {
// Silently handle 401/403 - user is just not logged in
const errorMessage = error instanceof Error ? error.message : String(error);
const isUnauthorized = errorMessage.includes("401") ||
errorMessage.includes("403") ||
errorMessage.includes("Unauthorized") ||
errorMessage.includes("Missing authentication token");
if (!isUnauthorized) {
// Only log/show errors for actual network issues
console.error("Failed to initialize auth:", error); console.error("Failed to initialize auth:", error);
const errorMessage = error instanceof Error ? error.message : "Không thể khởi tạo xác thực";
// Only show toast for network errors, not for 401/403 (user just not logged in)
if (errorMessage.includes("NetworkError") || errorMessage.includes("kết nối")) { if (errorMessage.includes("NetworkError") || errorMessage.includes("kết nối")) {
toast.error(errorMessage); toast.error(errorMessage);
}
} }
clearAccessToken(); clearAccessToken();
setState({ setState({
currentUser: undefined, currentUser: undefined,
......
...@@ -100,12 +100,20 @@ export function InstanceProvider({ children }: { children: ReactNode }) { ...@@ -100,12 +100,20 @@ export function InstanceProvider({ children }: { children: ReactNode }) {
isLoading: false, isLoading: false,
}); });
} catch (error) { } catch (error) {
// Silently handle errors - instance initialization failure shouldn't block app
const errorMessage = error instanceof Error ? error.message : String(error);
// Only log/show errors for actual network issues, not auth errors
const isAuthError = errorMessage.includes("401") ||
errorMessage.includes("403") ||
errorMessage.includes("Unauthorized");
if (!isAuthError) {
console.error("Failed to initialize instance:", error); console.error("Failed to initialize instance:", error);
const errorMessage = error instanceof Error ? error.message : "Không thể khởi tạo instance";
// Show toast for network errors
if (errorMessage.includes("NetworkError") || errorMessage.includes("kết nối")) { if (errorMessage.includes("NetworkError") || errorMessage.includes("kết nối")) {
toast.error(errorMessage); toast.error(errorMessage);
}
} }
setState((prev) => ({ setState((prev) => ({
...prev, ...prev,
isInitialized: true, isInitialized: true,
......
...@@ -25,6 +25,8 @@ export function useInstanceProfile() { ...@@ -25,6 +25,8 @@ export function useInstanceProfile() {
return profile; return profile;
}, },
staleTime: 1000 * 60 * 10, // 10 minutes - instance profile rarely changes staleTime: 1000 * 60 * 10, // 10 minutes - instance profile rarely changes
refetchOnWindowFocus: false, // Don't refetch on window focus - instance rarely changes
refetchOnReconnect: false, // Don't refetch on reconnect - instance rarely changes
}); });
} }
......
...@@ -31,7 +31,10 @@ export function useCurrentUserQuery() { ...@@ -31,7 +31,10 @@ export function useCurrentUserQuery() {
} catch (error) { } catch (error) {
// 401 is expected when not logged in - return undefined instead of throwing // 401 is expected when not logged in - return undefined instead of throwing
const errorMessage = error instanceof Error ? error.message : String(error); const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes("401") || errorMessage.includes("Unauthorized")) { if (errorMessage.includes("401") ||
errorMessage.includes("403") ||
errorMessage.includes("Unauthorized") ||
errorMessage.includes("Missing authentication token")) {
return undefined; return undefined;
} }
throw error; throw error;
...@@ -39,6 +42,8 @@ export function useCurrentUserQuery() { ...@@ -39,6 +42,8 @@ export function useCurrentUserQuery() {
}, },
staleTime: 1000 * 60 * 5, // 5 minutes - auth doesn't change often staleTime: 1000 * 60 * 5, // 5 minutes - auth doesn't change often
retry: false, // Don't retry on 401 - user is simply not logged in retry: false, // Don't retry on 401 - user is simply not logged in
refetchOnWindowFocus: false, // Don't refetch on window focus - reduces lag
refetchOnReconnect: false, // Don't refetch on reconnect - reduces unnecessary requests
}); });
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment