Skip to content
Merged
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
5 changes: 2 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Database — SQLite for local; switch prisma provider to postgresql for production
DATABASE_URL="file:./dev.db"
# DATABASE_URL="postgresql://user:pass@host:5432/applypilot?sslmode=require"
# Database — PostgreSQL (local docker-compose or managed Neon/Supabase/RDS)
DATABASE_URL="postgresql://applypilot:applypilot@localhost:5432/applypilot?schema=public"

# Auth — required in production (32+ random chars)
AUTH_SECRET="replace-with-long-random-secret"
Expand Down
63 changes: 63 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: CI

on:
push:
branches: [main, "cursor/**"]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: applypilot
POSTGRES_PASSWORD: applypilot
POSTGRES_DB: applypilot
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U applypilot -d applypilot"
--health-interval 5s
--health-timeout 5s
--health-retries 10
env:
DATABASE_URL: postgresql://applypilot:applypilot@localhost:5432/applypilot?schema=public
AUTH_SECRET: ci-test-secret-not-for-production-use
NEXT_PUBLIC_APP_URL: http://localhost:3000
ALLOW_DEMO_LOGIN: "true"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npx prisma migrate deploy
- run: npm run db:seed
- run: npm run build

deploy:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- name: Deploy to Vercel (optional)
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
run: |
if [ -z "$VERCEL_TOKEN" ] || [ -z "$VERCEL_ORG_ID" ] || [ -z "$VERCEL_PROJECT_ID" ]; then
echo "Skipping deploy. Add repo secrets VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID."
exit 0
fi
npm i -g vercel@39
vercel pull --yes --environment=production --token "$VERCEL_TOKEN"
vercel build --prod --token "$VERCEL_TOKEN"
vercel deploy --prebuilt --prod --token "$VERCEL_TOKEN"
80 changes: 37 additions & 43 deletions DEPLOY.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,47 @@
# Production launch

ApplyPilot ships as a Next.js app. Local default uses **SQLite**. Production should use **Postgres**.
ApplyPilot is a Next.js app on **PostgreSQL**.

## 1. Database

Local:
### Local (Docker)

```bash
docker compose up -d
cp .env.example .env
# DATABASE_URL="file:./dev.db"
npm run db:reset
npm install
npx prisma migrate deploy
npm run db:seed
npm run dev
```

Production (Neon / Supabase / RDS):
### Local (Postgres already installed)

1. In `prisma/schema.prisma`, change:

```prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
```bash
# DATABASE_URL=postgresql://applypilot:applypilot@localhost:5432/applypilot?schema=public
npx prisma migrate deploy && npm run db:seed
```

2. Set `DATABASE_URL` to your Postgres connection string.
3. Run:
### Production (Neon / Supabase / RDS)

```bash
npx prisma db push
# or: npx prisma migrate deploy
npm run db:seed # optional — skip demo user in prod if desired
```
1. Create a Postgres database.
2. Set `DATABASE_URL` (use pooled URL + `?sslmode=require` as required by your host).
3. Deploy — `npm run build` runs `prisma migrate deploy` automatically.

## 2. Environment

Required for production:

| Variable | Purpose |
|----------|---------|
| `DATABASE_URL` | Postgres connection string |
| `AUTH_SECRET` | HMAC session signing (32+ chars) |
| `DATABASE_URL` | Postgres URL |
| `NEXT_PUBLIC_APP_URL` | Canonical site URL |
| `NEXT_PUBLIC_APP_URL` | Canonical site URL (`https://…`) |
| `OPENAI_API_KEY` | Live AI |
| `REQUIRE_OPENAI=true` | Refuse heuristic AI fallbacks |
| `REQUIRE_OPENAI` | `true` in production |
| `STRIPE_SECRET_KEY` | Billing |
| `STRIPE_WEBHOOK_SECRET` | Checkout fulfillment |
| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Stripe.js (optional UI) |
| `STRIPE_PRICE_PRO_MONTHLY` | Pro price id |
| `STRIPE_PRICE_CREDITS_*` | Credit pack price ids |
| `STRIPE_PRICE_CREDITS_25/50/100/250` | Credit pack price ids |
| `RESEND_API_KEY` | Transactional email |
| `EMAIL_FROM` | From address |
| `CRON_SECRET` | Protect `/api/cron` |
Expand All @@ -55,38 +50,37 @@ Demo login is **disabled** when `NODE_ENV=production`.

## 3. Stripe

1. Create products: Pro monthly subscription + one-time credit packs (25/50/100/250).
1. Create products: Pro monthly + one-time credit packs (25/50/100/250).
2. Put price IDs in env.
3. Webhook endpoint: `POST /api/billing/webhook`
3. Webhook: `POST /api/billing/webhook`
Events: `checkout.session.completed`, `customer.subscription.deleted`.

## 4. Cron

`vercel.json` schedules:
`vercel.json` schedules job sync, apply-package queue, alerts, and follow-ups.

- Job sync every 6h
- Apply-package queue every 15m
- Job alerts daily
- Follow-up reminders daily

Authorize with `Authorization: Bearer $CRON_SECRET` (Vercel Cron sends this when configured) or `?secret=`.

Manual:
Authorize with `Authorization: Bearer $CRON_SECRET`.

```bash
curl -X POST "$APP_URL/api/cron?job=all" -H "Authorization: Bearer $CRON_SECRET"
curl "$APP_URL/api/health"
```

## 5. Deploy
## 5. Deploy (Vercel)

1. Import `letslego/applypilot` in Vercel.
2. Add all env vars (including `DATABASE_URL` to a Neon/Supabase DB).
3. Set build command to default `npm run build`.
4. Add GitHub secrets for CI auto-deploy (optional):
- `VERCEL_TOKEN`
- `VERCEL_ORG_ID`
- `VERCEL_PROJECT_ID`
5. After first deploy, seed if needed:

```bash
npm run build
npm start
# or: vercel --prod
DATABASE_URL=... npm run db:seed
```

Health checks: `/` · `/api/jobs/sync` (GET) · Stripe webhook · signup → verify email → checkout.

## Ethical boundary

Auto-Apply creates **packages** (tailored resume, cover letter, answer bank snapshot, employer `applyUrl`). Users confirm submission on the employer site. LinkedIn/Indeed/Glassdoor scraping and stealth auto-submit are intentionally out of scope.
Auto-Apply creates **packages** (tailored resume, cover letter, answer bank, employer `applyUrl`). Users confirm submission on the employer site. LinkedIn/Indeed scraping and stealth auto-submit are out of scope. LinkedIn **paste import** only parses text the user pastes.
60 changes: 20 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,65 +1,45 @@
# ApplyPilot

AI job-application co-pilot inspired by [AIApply](https://aiapply.co/) — with feature parity plus extras from Teal, Jobscan, LoopCV, and Simplify.
AI job-application co-pilot inspired by [AIApply](https://aiapply.co/).

**Repo:** https://github.com/letslego/applypilot

## Quick start (local)
## Quick start

```bash
docker compose up -d # Postgres
cp .env.example .env
npm install
npm run db:reset
npx prisma migrate deploy
npm run db:seed
npm run dev
```

Open http://localhost:3000
Open http://localhost:3000 — demo login (dev only): `demo@applypilot.com` / `demo1234`

**Demo login (non-production only):** `demo@applypilot.com` / `demo1234`
## Production

## Production launch
See **[DEPLOY.md](./DEPLOY.md)** for Postgres, Stripe, Resend, OpenAI, cron, and Vercel.

See **[DEPLOY.md](./DEPLOY.md)** for Postgres, Stripe, Resend, OpenAI, cron, and env setup.

```bash
npm run build && npm start
```
Health check: `GET /api/health`

## Features

| Area | Capability |
|------|------------|
| Marketing | Brand-first landing, pricing, features, FAQ |
| Auth | HMAC-signed sessions, email verify, password reset |
| Billing | Stripe Checkout (Pro + credit packs) + webhooks |
| Documents | AI resume builder, tailor, translate, DOCX/PDF export |
| Cover letters | Per-job generation |
| ATS scanner | Score, keyword heatmap, rewrite tips |
| Job board | Seeded roles + live ATS/public feed sync |
| Auto-Apply | Prefs, hybrid/auto modes, credit wallet, **employer apply packages** |
| Tracker | Kanban pipeline + follow-up reminders |
| Interviews | Mock interview + Interview Buddy (practice coach) |
| Alerts | Job match emails + in-app notifications |
| Auth | Signed sessions, email verify, password reset |
| Billing | Stripe Checkout + webhooks |
| Documents | Resume builder, LinkedIn paste import, DOCX/PDF export |
| ATS | Score, keyword heatmap, rewrite tips |
| Jobs | Live Greenhouse/Ashby/Remotive/RemoteOK/Arbeitnow sync |
| Auto-Apply | Credit packages + employer apply packages |
| Alerts | Job match + follow-up emails |
| Legal | Privacy, Terms, AI disclosure |

Auto-Apply builds tailored packages and employer apply links — you confirm submission. Optional live LLM via `OPENAI_API_KEY`. Set `REQUIRE_OPENAI=true` in production.

## Job data sources

Legal ingest (no LinkedIn/Indeed/Glassdoor scraping):

| Source | Endpoint style |
|--------|----------------|
| Greenhouse | `boards-api.greenhouse.io/v1/boards/{token}/jobs` |
| Ashby | `api.ashbyhq.com/posting-api/job-board/{org}` |
| Remotive | `remotive.com/api/remote-jobs` |
| RemoteOK | `remoteok.com/api` |
| Arbeitnow | `arbeitnow.com/api/job-board-api` |
## Scripts

```bash
npm run jobs:sync:quick # fast subset
npm run jobs:sync # fuller pull
npm run cron:all # sync + queue + alerts + follow-ups
npm run jobs:sync:quick
npm run cron:all
npm run build
```

Or click **Sync live jobs** on `/app/jobs` while logged in.
20 changes: 20 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: applypilot
POSTGRES_PASSWORD: applypilot
POSTGRES_DB: applypilot
ports:
- "5432:5432"
volumes:
- applypilot_pg:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U applypilot -d applypilot"]
interval: 5s
timeout: 5s
retries: 10

volumes:
applypilot_pg:
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
"private": true,
"scripts": {
"dev": "next dev",
"build": "prisma generate && next build",
"build": "prisma generate && prisma migrate deploy && next build",
"start": "next start",
"lint": "next lint",
"db:push": "prisma db push",
"db:migrate": "prisma migrate deploy",
"db:seed": "tsx prisma/seed.ts",
"db:reset": "prisma db push --force-reset && tsx prisma/seed.ts",
"db:reset": "prisma migrate reset --force",
"jobs:sync": "tsx scripts/sync-jobs.ts",
"jobs:sync:quick": "tsx scripts/sync-jobs.ts --quick",
"cron:all": "tsx scripts/run-cron.ts",
Expand Down
Loading
Loading