Skip to content
Merged

V2 #4

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# ClaimWise Deployment Guide

## 🚀 Backend Deployment (Render)

### Prerequisites
- Render account (https://render.com)
- GitHub repository with code pushed

### Step 1: Connect GitHub to Render
1. Go to https://dashboard.render.com
2. Click "New +" → "Web Service"
3. Connect your GitHub repository
4. Select the repository containing this code

### Step 2: Configure Render Web Service
**Service Name:** `claimwise-backend`
**Environment:** Python 3.11
**Build Command:**
```bash
pip install -r backend/requirements.txt
```

**Start Command:**
```bash
cd backend && uvicorn src.main:app --host 0.0.0.0 --port $PORT
```

### Step 3: Add Environment Variables
In Render Dashboard → Environment, add these variables:

```
SUPABASE_URL=<your_supabase_url>
SUPABASE_KEY=<your_supabase_anon_key>
SUPABASE_JWT_SECRET=<your_jwt_secret>
SUPABASE_SERVICE_KEY=<your_service_key>
GROQ_API_KEY=<your_groq_key>
GEMINI_API_KEY=<your_gemini_key>
FRONTEND_URL=https://claimwise.vercel.app
JWT_ALGORITHM=HS256
JWT_EXPIRATION_HOURS=24
SUPABASE_STORAGE_BUCKET=proeject
```

**Note:** Get these values from:
- **Supabase:** Settings → API → Project API Keys
- **Groq:** https://console.groq.com/keys
- **Gemini:** https://aistudio.google.com/app/apikey

### Step 4: Deploy
- Click "Create Web Service"
- Render will automatically deploy when you push to main branch
- Your backend URL will be: `https://claimwise-backend.onrender.com` (or similar)

### Step 5: Update Frontend
- Copy your Render backend URL
- Add to frontend `.env.production`:
```
NEXT_PUBLIC_API_URL=https://claimwise-backend.onrender.com
```

---

## 🌐 Frontend Deployment (Vercel)

### Prerequisites
- Vercel account (https://vercel.com)
- GitHub repository with code pushed

### Step 1: Import Project
1. Go to https://vercel.com/new
2. Select "Import Git Repository"
3. Select your GitHub repo

### Step 2: Configure Build Settings
**Framework:** Next.js
**Root Directory:** `frontend`

**Build Command:**
```bash
npm run build
```

**Install Command:**
```bash
npm install
```

**Start Command:**
```bash
npm start
```

### Step 3: Add Environment Variables
In Vercel Dashboard → Settings → Environment Variables, add:

```
NEXT_PUBLIC_SUPABASE_URL=https://pmsooebddaeddjyabghw.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=<your_anon_key>
NEXT_PUBLIC_API_URL=https://claimwise-backend.onrender.com
NEXT_PUBLIC_SUPABASE_REDIRECT_URL=https://claimwise.vercel.app/dashboard
```

### Step 4: Update Supabase OAuth
1. Go to Supabase Dashboard → Authentication → Providers → Google
2. Add these redirect URLs:
```
https://claimwise.vercel.app/auth/callback
https://claimwise.vercel.app/dashboard
```

### Step 5: Deploy
- Click "Deploy"
- Vercel will automatically redeploy on pushes to main
- Your frontend URL: `https://claimwise.vercel.app`

---

## ✅ Post-Deployment Checklist

- [ ] Backend is deployed on Render
- [ ] Frontend is deployed on Vercel
- [ ] Environment variables are set on both platforms
- [ ] CORS origins updated in backend (FRONTEND_URL env var)
- [ ] Supabase OAuth redirect URLs include new frontend URL
- [ ] Test login with Google OAuth
- [ ] Test policy upload
- [ ] Test dashboard metrics loading
- [ ] Check backend logs for errors
- [ ] Verify database operations work

---

## 🔍 Troubleshooting

### Backend won't start on Render
- Check build logs: Render Dashboard → Logs
- Ensure all environment variables are set
- Verify `requirements.txt` is in root of backend folder

### Frontend shows "Cannot reach API"
- Check `NEXT_PUBLIC_API_URL` is correct in Vercel
- Verify backend is running: `https://your-backend.onrender.com/healthz`
- Check browser console for CORS errors

### OAuth not working
- Verify redirect URLs in Supabase match your Vercel domain
- Check `NEXT_PUBLIC_SUPABASE_REDIRECT_URL` is correct
- Ensure Google OAuth credentials are configured

### Database connection fails
- Verify `SUPABASE_URL` and `SUPABASE_KEY` are correct
- Check Supabase project is not paused
- Verify network access (Render may need IP whitelisting)

---

## 📝 Notes

- Render free tier has limitations (sleeps after 15min inactivity, 100 hours/month)
- Consider upgrading to Starter ($7/month) for production
- Vercel free tier includes 100GB bandwidth/month
- Keep API keys secure - never commit to GitHub
- Use Render environment variables, not `.env` files in production
File renamed without changes.
26 changes: 26 additions & 0 deletions backend/sql/create_document_chunks_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- Create document_chunks table with policy_id FK
-- This table stores RAG chunks indexed from uploaded policies

-- Drop existing table if it exists (with wrong schema)
DROP TABLE IF EXISTS public.document_chunks CASCADE;

-- Create the corrected table
CREATE TABLE public.document_chunks (
id BIGSERIAL PRIMARY KEY,
policy_id UUID NOT NULL REFERENCES public.policies(id) ON DELETE CASCADE,
chunk_index INTEGER NOT NULL,
content TEXT NOT NULL,
embedding vector(384) DEFAULT NULL,
content_fingerprint TEXT,
embedding_cached_at TIMESTAMP DEFAULT now(),
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
CONSTRAINT document_chunks_unique_per_policy UNIQUE(policy_id, chunk_index)
);

-- Create indexes for faster queries
CREATE INDEX idx_document_chunks_policy_id ON public.document_chunks(policy_id);
CREATE INDEX idx_document_chunks_created_at ON public.document_chunks(created_at);

-- Enable vector similarity search
CREATE INDEX idx_document_chunks_embedding ON public.document_chunks
USING ivfflat (embedding vector_cosine_ops);
56 changes: 38 additions & 18 deletions backend/src/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
def decode_token(token: str) -> str:
"""
Decode and verify a Supabase-issued JWT token.
Supports tokens from email/password and social authentication.
Supports tokens from email/password and social authentication (Google, GitHub).

Args:
token (str): The JWT token to decode and verify.
Expand All @@ -39,44 +39,64 @@ def decode_token(token: str) -> str:
JWTError: if token is invalid or verification fails
"""
try:
# Debug logging for troubleshooting - don't log secrets
logger = logging.getLogger(__name__)
logger.debug("Decoding token")
logger.debug("[Auth] Decoding JWT token")
jwt_secret: str = SUPABASE_JWT_SECRET # type: ignore

# Validate token structure
if not token or token.count('.') != 2:
logger.warning("[Auth] Invalid token structure (not JWT)")
raise JWTError("Invalid token structure")

payload = jwt.decode(token, jwt_secret, algorithms=ALGORITHM, options={"verify_aud": False})
logger.debug("Decoded payload keys: %s", list(payload.keys()))
logger.debug("[Auth] Token decoded successfully. Payload keys: %s", list(payload.keys()))

user_id = payload.get("sub")
if user_id is None:
logger.debug("No 'sub' claim in payload")
logger.warning("[Auth] Missing 'sub' claim in JWT payload")
raise JWTError("Invalid authentication credentials: missing 'sub' claim")

logger.debug("[Auth] User ID extracted from token: %s", user_id)
return str(user_id)
except JWTError as e:
logging.getLogger(__name__).exception("JWTError: %s", str(e))
logging.getLogger(__name__).exception("[Auth] JWTError during token decode: %s", str(e))
raise

async def get_current_user(token: str = Depends(oauth2_scheme)) -> str:
"""
verify jwt token and extract user id
Verify JWT token and extract user ID.
Works with both email/password and OAuth (Google/GitHub) tokens.

args :
token (str) jwt from Authorization header
Args:
token (str): JWT from Authorization header

returins :
str : user id
Returns:
str: User ID

raised error : if token is invalid
Raises:
HTTPException: If token is invalid or verification fails
"""
try:
user_id=decode_token(token)
logging.getLogger(__name__).debug("get_current_user: user_id=%s", user_id)
logger = logging.getLogger(__name__)
logger.debug("[Auth] get_current_user called")

user_id = decode_token(token)
logger.debug("[Auth] User authenticated: %s", user_id)
return user_id
except JWTError as e:
logging.getLogger(__name__).exception("JWTError in get_current_user: %s", e)
import traceback
traceback.print_exc()
logger = logging.getLogger(__name__)
logger.warning("[Auth] JWT verification failed: %s", str(e))
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired authentication token",
headers={"WWW-Authenticate": "Bearer"},
)
except Exception as e:
logger = logging.getLogger(__name__)
logger.exception("[Auth] Unexpected error in get_current_user: %s", e)
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
detail="Authentication failed",
headers={"WWW-Authenticate": "Bearer"},
)

Expand Down
63 changes: 60 additions & 3 deletions backend/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,17 @@ def log_activity(user_id: str, activity_type: str, title: str, description: str,
"https://claimwise-fht9.vercel.app",
"https://claimwise-8eeg.vercel.app",
"http://localhost:3000",
"http://localhost:3001"
"http://localhost:3001",
"http://localhost:8000", # Local backend
# Render deployments (add your actual Render URL when deployed)
os.getenv("FRONTEND_URL", ""), # Dynamic frontend URL from env
]

# Filter out empty strings
origins = [url for url in origins if url]

logging.info(f"CORS origins configured: {origins}")

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
Expand Down Expand Up @@ -196,7 +204,34 @@ async def upload_policy(
try:
# Use service-role client for writes if available
svc = supabase_storage or supabase

# Verify that a corresponding user profile exists in the database to
# avoid foreign key constraint failures (gives a clearer error to client)
try:
user_check = svc.table("users").select("id").eq("id", user_id).execute()
if not (user_check and getattr(user_check, "data", None)):
logging.error("User id %s not found in users table before policy insert", user_id)
raise HTTPException(status_code=403, detail="User profile not found. Please re-authenticate.")
except HTTPException:
raise
except Exception:
# If the check itself failed (permissions/missing table), log and continue
logging.warning("Could not verify user existence prior to insert; continuing and relying on DB constraints")

response = svc.table("policies").insert(data).execute()

# Handle client-level errors returned by the Supabase client
resp_err = getattr(response, "error", None)
if resp_err:
logging.error("Supabase insert error: %s", resp_err)
err_msg = getattr(resp_err, "message", str(resp_err))
err_code = getattr(resp_err, "code", "")
# Detect foreign key violation (user missing)
if "foreign key" in str(err_msg).lower() or err_code == "23503":
raise HTTPException(status_code=403, detail="Invalid user or authentication state. Please re-authenticate.")
else:
raise HTTPException(status_code=500, detail="Failed to save policy.")

if not (response and getattr(response, "data", None)):
raise HTTPException(status_code=500, detail="Failed to save policy.")

Expand Down Expand Up @@ -241,8 +276,16 @@ async def upload_policy(
status="indexing_started",
indexing_mode=indexing_mode
)
except HTTPException:
# Re-raise HTTPExceptions we intentionally raised above
raise
except Exception as db_error:
raise HTTPException(status_code=500, detail=f"Database save failed: {str(db_error)}")
# Log full exception server-side but do not leak DB internals to clients
logging.exception("Database save error: %s", db_error)
err_str = str(db_error).lower()
if "foreign key" in err_str or "violates foreign key" in err_str or "23503" in err_str:
raise HTTPException(status_code=403, detail="Invalid user or authentication state. Please re-authenticate.")
raise HTTPException(status_code=500, detail="Database save failed.")
except HTTPException:
raise
except Exception as e:
Expand Down Expand Up @@ -290,7 +333,21 @@ def analyze(policy_id: str = Form(...), user_id: str = Depends(get_current_user)
metadata = {}
metadata['analysis_result'] = analysis

supabase.table("policies").update({"validation_metadata": metadata}).eq("id", policy_id).execute()
# Calculate validation score based on analysis
# Lower score = more risks, Higher score = fewer risks
gaps_count = len(analysis.get('gaps_and_risks', []))
exclusions_count = len(analysis.get('exclusions', []))
total_risks = gaps_count + exclusions_count

# Score formula: Start at 100%, deduct for each risk (max deduction 90%)
# This ensures at least 10% score even with many risks
risk_impact = min(total_risks * 0.5, 90) # Each risk = 0.5% deduction, capped at 90%
validation_score = max((100 - risk_impact) / 100, 0.1) # Score 0-1, min 0.1

supabase.table("policies").update({
"validation_metadata": metadata,
"validation_score": validation_score
}).eq("id", policy_id).execute()

# Log the analysis activity
log_activity(
Expand Down
Empty file removed backend/src/main_enhanced.py
Empty file.
1 change: 1 addition & 0 deletions frontend/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
shamefully-hoist=true
Loading