Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions .geminiignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
101 changes: 101 additions & 0 deletions DEPLOY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Deployment Guide - PYQ Solver App

This guide covers the steps to deploy the functional version of the PYQ Solver App (Phase 4 complete).

## 🚀 1. Prerequisites

- A VPS or Cloud Server (e.g., DigitalOcean Droplet, AWS EC2).
- **Docker** and **Docker Compose** installed.
- A **DigitalOcean Spaces** bucket (or any S3-compatible storage).
- An **OpenRouter API Key**.
- A **Google Cloud Project** (for Google OAuth/Login).

---

## 🔑 2. Environment Configuration

1. Copy the `.env.example` file to `.env`:
```bash
cp .env.example .env
```

2. Fill in the required secrets in `.env`:
- `OPENROUTER_API_KEY`: Get from [openrouter.ai](https://openrouter.ai/).
- `JWT_SECRET_KEY`: Generate a random secure string (`openssl rand -hex 32`).
- `SPACES_KEY` & `SPACES_SECRET`: From DigitalOcean API settings.
- `SPACES_BUCKET`: Your bucket name.
- `SPACES_REGION`: e.g., `sfo3`.
- `SPACES_PUBLIC_URL`: The CDN or direct URL of your bucket.
- `VITE_API_URL`: The public IP or domain of your backend (e.g., `http://your-ip:8001/api`).

---

## 🐳 3. Docker Deployment

### Backend Services
The current `docker-compose.yml` runs the API, Worker, Postgres, and Redis.

1. **Start the services**:
```bash
docker-compose up -d --build
```

2. **Run Database Migrations**:
Once the containers are up, apply the schema to the database:
```bash
docker-compose exec api alembic upgrade head
```

### Frontend Service
You have two options for the frontend:

#### Option A: Docker (Recommended for VPS)
I have created a `frontend/Dockerfile` and `frontend/nginx.conf`. To include the frontend in your deployment, add this block to your `docker-compose.yml`:

```yaml
frontend:
build:
context: ./frontend
args:
- VITE_API_URL=${VITE_API_URL}
ports:
- "80:80"
depends_on:
- api
```

*Note: Make sure `VITE_API_URL` is set in your `.env` before running `docker-compose up`.*

#### Option B: Static Hosting (Vercel/Netlify)
1. Build the frontend locally: `npm run build`
2. Upload the `dist/` folder to your provider.
3. Ensure `VITE_API_URL` points to your backend IP/Domain.

---

## 🛡 4. Production Security Checklist

1. **CORS Configuration**:
In `backend/app/main.py`, replace `allow_origins=["*"]` with your actual domain/frontend URL.

2. **Reverse Proxy (Nginx)**:
It is highly recommended to use Nginx in front of your Docker containers to handle SSL (HTTPS) via Let's Encrypt.

3. **Database Backups**:
Ensure you have a strategy to back up the `postgres_data` volume.

---

## 🎨 5. Post-Deployment: UI Revamp
As requested, once the app is running and verified:
1. We will merge the `main` branch state.
2. We will start the **UI Revamp** using `shadcn/ui`.
3. We will transform the current basic Tailwind UI into a premium workspace design.

---

## 🛠 Troubleshooting

- **OCR not working?** Check if `MAX_OCR_PAGES` is set correctly and the Worker container logs: `docker-compose logs -f worker`.
- **Backend connection error?** Ensure `VITE_API_URL` in the `.env` (used during frontend build) matches the backend's public address.
- **Spaces Access Denied?** Ensure your bucket has "File Listing" disabled but "Public Read" enabled for the files uploaded.
1 change: 1 addition & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.geminiignore
18 changes: 18 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Build stage
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL
RUN npm run build

# Production stage
FROM nginx:stable-alpine
COPY --from=build /app/dist /usr/share/nginx/html
# Default nginx config is usually fine for SPAs,
# but for React Router you might need a custom one to handle 404s
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Comment on lines +12 to +18
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify runtime user and privileged port usage in container config.
rg -n '^\s*FROM\s+nginx|^\s*USER\b|^\s*EXPOSE\b' frontend/Dockerfile
rg -n '^\s*listen\s+' frontend/nginx.conf

Repository: shubhamxdd/pyq-appl

Length of output: 121


🏁 Script executed:

# Check for deployment configs that might reference the frontend service
fd -e 'docker-compose.yml' -e 'docker-compose.yaml' -e '.yaml' -e '.yml' | head -20

Repository: shubhamxdd/pyq-appl

Length of output: 82


🏁 Script executed:

# Check if there are any other references to port 80 in frontend-related configs
rg -i 'port.*80|80.*port' --type yaml --type json frontend/ 2>/dev/null || rg -i 'port.*80|80.*port' . --type yaml --type json 2>/dev/null | head -20

Repository: shubhamxdd/pyq-appl

Length of output: 154


🏁 Script executed:

# Verify the nginx user exists in the alpine image (standard practice)
rg -n '^\s*USER\s' frontend/Dockerfile

Repository: shubhamxdd/pyq-appl

Length of output: 45


🏁 Script executed:

cat docker-compose.yml

Repository: shubhamxdd/pyq-appl

Length of output: 1460


🏁 Script executed:

# Check for any Kubernetes, Terraform, or other deployment configs
fd -type f \( -name '*.yaml' -o -name '*.yml' -o -name '*.tf' -o -name '*.json' \) | xargs rg -l 'frontend|nginx' 2>/dev/null | head -20

Repository: shubhamxdd/pyq-appl

Length of output: 486


🏁 Script executed:

# Verify nginx:stable-alpine image includes nginx user (standard in official images)
# and check frontend/nginx.conf for any other directives that might conflict
cat -n frontend/nginx.conf

Repository: shubhamxdd/pyq-appl

Length of output: 471


Run the runtime image as non-root.

Lines 12–18 leave the Nginx container running as root, which weakens container isolation in production.

Suggested hardening patch
 FROM nginx:stable-alpine
 COPY --from=build /app/dist /usr/share/nginx/html
 # Default nginx config is usually fine for SPAs, 
 # but for React Router you might need a custom one to handle 404s
 COPY nginx.conf /etc/nginx/conf.d/default.conf
-EXPOSE 80
+USER nginx
+EXPOSE 8080
 CMD ["nginx", "-g", "daemon off;"]

Also update frontend/nginx.conf:

listen 8080;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FROM nginx:stable-alpine
COPY --from=build /app/dist /usr/share/nginx/html
# Default nginx config is usually fine for SPAs,
# but for React Router you might need a custom one to handle 404s
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
FROM nginx:stable-alpine
COPY --from=build /app/dist /usr/share/nginx/html
# Default nginx config is usually fine for SPAs,
# but for React Router you might need a custom one to handle 404s
COPY nginx.conf /etc/nginx/conf.d/default.conf
USER nginx
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/Dockerfile` around lines 12 - 18, The container runs Nginx as root;
modify the Dockerfile to create or use a non-root user and switch to it (e.g.,
create a user/group or use the existing nginx user) after copying assets and set
proper ownership of /usr/share/nginx/html so Nginx can serve files without root;
also align the exposed port with the updated nginx.conf (change EXPOSE from 80
to 8080) and keep CMD ["nginx", "-g", "daemon off;"] (nginx.conf will bind to
8080), ensuring USER is set after the files are owned by the non-root user and
before the final CMD.

891 changes: 890 additions & 1 deletion frontend/bun.lock

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions frontend/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}
Binary file added frontend/i1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/i2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/i3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/i4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/i5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/i6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions frontend/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
server {
listen 80;
server_name localhost;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
2 changes: 1 addition & 1 deletion frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,30 @@
"preview": "vite preview"
},
"dependencies": {
"@fontsource-variable/geist": "^5.2.9",
"@hookform/resolvers": "^5.2.2",
"@tanstack/react-query": "^5.100.10",
"axios": "^1.16.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^1.16.0",
"radix-ui": "^1.4.3",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react-hook-form": "^7.76.0",
"react-hot-toast": "^2.6.0",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.15.1",
"shadcn": "^4.7.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether `shadcn` is referenced by runtime source files.
rg -nP --type=ts --type=tsx --type=js --type=jsx \
  '(from\s+["'"'"']shadcn["'"'"']|require\(["'"'"']shadcn["'"'"']\))' frontend/src frontend

Repository: shubhamxdd/pyq-appl

Length of output: 91


🏁 Script executed:

# Search for shadcn imports in a simpler way
rg -i 'shadcn' frontend/src frontend --max-count=20

Repository: shubhamxdd/pyq-appl

Length of output: 1951


🏁 Script executed:

# Check the frontend package.json for shadcn usage in scripts
cat -n frontend/package.json | head -50

Repository: shubhamxdd/pyq-appl

Length of output: 1780


Move shadcn to devDependencies—it's a CLI tool with no runtime imports.

shadcn is listed on line 27 as a runtime dependency but has no imports in application code. The bun.lock indicates it provides a CLI executable (bin: { "shadcn": "dist/index.js" }) used for component generation, not runtime functionality. Moving it to devDependencies reduces the production install surface without impact.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/package.json` at line 27, The package.json currently lists "shadcn"
as a runtime dependency; move it into devDependencies instead. Edit package.json
to remove the "shadcn": "^4.7.0" entry from the top-level "dependencies" object
and add the same "shadcn": "^4.7.0" entry under "devDependencies" so it remains
available for CLI/component generation but is not installed in production.

"tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0",
"zod": "^4.4.3",
"zustand": "^5.0.13"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@tailwindcss/vite": "^4.3.0",
"@types/node": "^24.12.3",
"@types/node": "^24.12.4",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
Expand Down
Loading