Skip to content
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5326c92
Migrate from better-sqlite3 to Drizzle ORM
rgilks Oct 18, 2025
efb0e99
Update documentation for Drizzle ORM migration
rgilks Oct 18, 2025
379e5c9
Remove backup .new.ts files causing TypeScript build failures
rgilks Oct 18, 2025
e1d8135
Fix Drizzle ORM production deployment issues
rgilks Oct 18, 2025
ea32504
Add @libsql/client dependency to fix Fly.io deployment
rgilks Oct 18, 2025
2be471a
Remove @libsql/client dependencies to fix Fly.io deployment
rgilks Oct 18, 2025
525eadf
Fix Next.js Server Actions to be async functions
rgilks Oct 18, 2025
eaf6543
Complete Cloudflare migration from Fly.io
rgilks Oct 18, 2025
8c53b6e
Complete Cloudflare migration with OpenNext and D1 database
rgilks Oct 18, 2025
015d676
Add Cloudflare branch-specific CI/CD workflow
rgilks Oct 18, 2025
671274b
Fix database context function signature
rgilks Oct 18, 2025
afedc74
Update documentation with branch-specific workflow details
rgilks Oct 18, 2025
63ddaca
Attempt to fix TypeScript errors with explicit function signatures
rgilks Oct 18, 2025
9227f38
Fix TypeScript errors by using any type for DatabaseInstance
rgilks Oct 18, 2025
814ee04
Fix Cloudflare build and database migration issues
rgilks Oct 18, 2025
4b38080
Remove e2e tests from Cloudflare CI/CD workflow
rgilks Oct 18, 2025
bc48fc1
Fix Cloudflare deployment workflow
rgilks Oct 18, 2025
fe8e240
Fix Cloudflare deployment: Remove invalid --json flag from wrangler d…
rgilks Oct 18, 2025
6c786e4
Fix YAML syntax error: Correct indentation in GitHub Actions workflow
rgilks Oct 18, 2025
377eb8a
Fix wrangler.toml: Correct D1 databases configuration format for prev…
rgilks Oct 18, 2025
3c979f5
Fix wrangler.toml: Update main entry point to .open-next/worker.js
rgilks Oct 18, 2025
3d97e05
Simplify deployment: Remove preview environment, keep only production…
rgilks Oct 19, 2025
5e4805e
Complete preview environment removal: Update all workflows, docs, and…
rgilks Oct 19, 2025
d7befd6
Fix YAML formatting: Use double quotes for consistency in GitHub work…
rgilks Oct 19, 2025
f9c1651
Configure Cloudflare deployment with D1 database
rgilks Oct 19, 2025
d7fae79
Add .wrangler/ to .gitignore and remove from tracking
rgilks Oct 19, 2025
5c12c0e
Fix static assets serving in Cloudflare Workers
rgilks Oct 19, 2025
2b0451f
Fix IP detection for Cloudflare Workers
rgilks Oct 19, 2025
c77889d
fix: Allow server actions to bypass CSRF validation
rgilks Oct 19, 2025
c1cec4b
Fix PR comment issues: Drizzle ORM methods, schema conflicts, and con…
rgilks Oct 25, 2025
9f882be
Fix D1 database configuration for Cloudflare deployment
rgilks Oct 25, 2025
d0022c7
Update documentation for successful Cloudflare D1 database configuration
rgilks Oct 25, 2025
7780918
Fix new Copilot AI review comments
rgilks Oct 25, 2025
6ec4e23
Fix CSP issues for Cloudflare Workers deployment
rgilks Oct 25, 2025
713d654
Fix CSP issues and NextAuth configuration for Cloudflare Workers
rgilks Oct 25, 2025
a7be3d5
Fix CSP and NextAuth errors
rgilks Oct 25, 2025
d6cfa1a
Fix CSP to allow unsafe-inline for Next.js scripts
rgilks Oct 25, 2025
717eca5
Fix NextAuth 500 errors by adding NEXTAUTH_URL environment variable
rgilks Oct 25, 2025
a712a60
Update documentation for NextAuth fix
rgilks Oct 25, 2025
257df73
Trigger deployment after adding NEXTAUTH_URL secret
rgilks Oct 25, 2025
f15aaa1
Remove NEXTAUTH_URL from GitHub Actions env vars - use wrangler.toml …
rgilks Oct 25, 2025
b71fab9
Add NEXTAUTH_URL as literal value in GitHub Actions env vars
rgilks Oct 25, 2025
829053e
Simplify secrets management - use only Cloudflare secrets
rgilks Oct 25, 2025
89cd8ff
Add NEXTAUTH_SECRET back to GitHub Actions for build process
rgilks Oct 25, 2025
b677cee
Trigger deployment with NEXTAUTH_SECRET GitHub secret
rgilks Oct 25, 2025
aeb47fa
Add essential secrets back to GitHub Actions for build process
rgilks Oct 25, 2025
4c012e2
Simplify to only essential secrets for build
rgilks Oct 25, 2025
09808bf
Add environment variables to build step
rgilks Oct 25, 2025
fdb03e1
Fix TypeScript error in authOptions - use only NEXTAUTH_SECRET
rgilks Oct 25, 2025
ced1d4a
Add all required secrets to build step
rgilks Oct 25, 2025
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
97 changes: 97 additions & 0 deletions .github/workflows/cloudflare-branch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: Cloudflare Branch CI/CD

on:
push:
branches:
- cloudflare
pull_request:
branches:
- cloudflare

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Run linting and type checking
run: npm run verify

- name: Run unit tests
run: npm run test:run

deploy-preview:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/cloudflare'
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Build with OpenNext
run: npm run build:cf

- name: Deploy to Cloudflare Workers (Preview)
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --env preview --minify
env:
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
GOOGLE_TRANSLATE_API_KEY: ${{ secrets.GOOGLE_TRANSLATE_API_KEY }}
ADMIN_EMAILS: ${{ secrets.ADMIN_EMAILS }}

deploy-production:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/cloudflare' && github.event_name == 'push'
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Build with OpenNext
run: npm run build:cf

- name: Deploy to Cloudflare Workers (Production)
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --minify
env:
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
GOOGLE_TRANSLATE_API_KEY: ${{ secrets.GOOGLE_TRANSLATE_API_KEY }}
ADMIN_EMAILS: ${{ secrets.ADMIN_EMAILS }}
40 changes: 40 additions & 0 deletions .github/workflows/cloudflare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Deploy to Cloudflare Workers

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm run test:run

- name: Build for Cloudflare
run: npm run build:cf

- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy
workingDirectory: .
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,4 @@ test/e2e/auth/*.storageState.json
# local env files
.pnp.*
.yarn/
.open-next/
57 changes: 51 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A multi-language reading comprehension practice tool powered by Next.js and Google Gemini.

[![CI/CD](https://github.com/rgilks/comprehendo/actions/workflows/fly.yml/badge.svg)](https://github.com/rgilks/comprehendo/actions/workflows/fly.yml)
[![CI/CD](https://github.com/rgilks/comprehendo/actions/workflows/cloudflare.yml/badge.svg)](https://github.com/rgilks/comprehendo/actions/workflows/cloudflare.yml)

![Comprehendo Screenshot](public/screenshot.png)

Expand Down Expand Up @@ -33,7 +33,7 @@ Comprehendo is an AI-powered language learning application designed to help user
- **Cost-Control System**: IP-based rate limiting, translation caching, daily AI API budgets, and database caching to manage API costs.
- **Robust Validation**: Uses Zod for request validation on API routes and environment variables.
- **Smooth Loading Experience**: Enhanced loading indicators and transitions.
- **Continuous Deployment**: Automatic deployment to Fly.io via GitHub Actions when code is pushed to the `main` branch.
- **Continuous Deployment**: Automatic deployment to Cloudflare Workers via GitHub Actions when code is pushed to the `main` branch.
- **Admin Panel**: A secure area for administrators to view application data (users, quizzes, feedback).
- **Internationalization (i18n)**: Full i18n support for UI elements using `i18next` and locale files in `public/locales/`.
- **PWA Support**: Progressive Web App features (e.g., installability) are enabled via `@serwist/next`, relying on browser/device native installation prompts.
Expand Down Expand Up @@ -191,6 +191,49 @@ Continuous Deployment is set up via GitHub Actions (`.github/workflows/fly.yml`)
fly deploy --app <your-app-name>
```

### Deploying to Cloudflare Workers

The application is configured to deploy to Cloudflare Workers using OpenNext and D1 database.

**First-Time Cloudflare Setup:**

1. **Install Wrangler CLI:** `npm install -g wrangler`
2. **Login:** `wrangler login`
3. **Create D1 Database:**
```bash
wrangler d1 create comprehendo-db
wrangler d1 create comprehendo-db-preview
```
4. **Update wrangler.toml:** Replace the database IDs in `wrangler.toml` with your actual database IDs
5. **Set Production Secrets:**
```bash
wrangler secret put NEXTAUTH_SECRET
wrangler secret put GOOGLE_CLIENT_ID
wrangler secret put GOOGLE_CLIENT_SECRET
wrangler secret put GOOGLE_TRANSLATE_API_KEY
wrangler secret put ADMIN_EMAILS
```
6. **Add GitHub Secrets:**
- `CLOUDFLARE_API_TOKEN`: Get from Cloudflare dashboard > My Profile > API Tokens
- `CLOUDFLARE_ACCOUNT_ID`: Get from Cloudflare dashboard > Right sidebar

**Deployment:**

- Push to `main`: `git push origin main`
- Monitor in GitHub Actions tab

**Manual Deployment:**

```bash
npm run deploy
```

**Database Migration:**

```bash
wrangler d1 execute comprehendo-db --file=./scripts/migrate-d1.js
```

## Development Workflow

Key scripts defined in `package.json`:
Expand Down Expand Up @@ -238,8 +281,8 @@ npm run cleanup-db:dry-run
- **Rate Limits**: Adjust `MAX_REQUESTS_PER_HOUR`, `MAX_TRANSLATION_REQUESTS_PER_HOUR`, and `MAX_DAILY_AI_REQUESTS` based on traffic and budget.
- **Database Maintenance**: Run `npm run cleanup-db` periodically to clean up old rate limits, translations, and usage records.
- **Security**: Review CORS, consider stricter input validation if needed.
- **Scaling**: Adjust Fly.io machine specs/count in `fly.toml`.
- **Database Backups**: Implement a backup strategy for the SQLite volume on Fly.io (e.g., using `litestream` or manual snapshots).
- **Scaling**: Cloudflare Workers automatically scale based on demand.
- **Database Backups**: Cloudflare D1 provides automatic backups and point-in-time recovery.

## Customization

Expand Down Expand Up @@ -375,7 +418,9 @@ Once both files are correctly populated, `npm run test:e2e` should now be able t

## Database

Uses SQLite via `better-sqlite3`. The database file is `data/comprehendo.sqlite` locally, and stored on a persistent volume (`/data/comprehendo.sqlite`) in production (Fly.io).
Uses SQLite via Drizzle ORM with `@libsql/client`. The database file is `data/comprehendo.sqlite` locally, and uses Cloudflare D1 in production.

The migration to Drizzle ORM makes it easy to switch to Cloudflare D1 in the future by simply changing the database connection configuration.

### SQLite Command Line (Local)

Expand All @@ -389,7 +434,7 @@ Useful commands: `.tables`, `SELECT * FROM users LIMIT 5;`, `.schema quiz`, `.qu
### Database Schema

```sql
-- From app/repo/db.ts initialization logic
-- From app/lib/db/migrations.ts initialization logic

CREATE TABLE IF NOT EXISTS quiz (
id INTEGER PRIMARY KEY AUTOINCREMENT,
Expand Down
15 changes: 8 additions & 7 deletions app/actions/exercise.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,14 @@ describe('exercise actions', () => {
});

it('should handle errors gracefully', async () => {
const { getRandomGoodQuestionResponse } = await import('app/actions/exercise');
const { getRandomGoodQuestion } = await import('app/repo/quizRepo');

// Mock getRandomGoodQuestion to throw an error
vi.mocked(getRandomGoodQuestion).mockImplementation(() => {
throw new Error('Database error');
});

const { getRandomGoodQuestionResponse } = await import('app/actions/exercise');
const { getServerSession } = await import('next-auth');
const { headers } = await import('next/headers');
const { findUserIdByProvider } = await import('app/repo/userRepo');
Expand All @@ -50,12 +56,7 @@ describe('exercise actions', () => {
user: { id: '123', provider: 'google' },
} as never);
vi.mocked(headers).mockResolvedValue(new Headers());
vi.mocked(findUserIdByProvider).mockReturnValue(1);

// Mock error in getRandomGoodQuestion
vi.mocked(getRandomGoodQuestion).mockImplementation(() => {
throw new Error('Database error');
});
vi.mocked(findUserIdByProvider).mockResolvedValue(1);

const result = await getRandomGoodQuestionResponse({
passageLanguage: 'es',
Expand Down
Loading
Loading