diff --git a/.env.example b/.env.example index 74d400de..6b587d22 100644 --- a/.env.example +++ b/.env.example @@ -44,6 +44,14 @@ S3_BUCKET=reqcore S3_REGION=us-east-1 S3_FORCE_PATH_STYLE=true +# ─── GDPR retention cleanup ───────────────────────────────────────────────── +# Master switch for automated, irreversible candidate erasure. Fail-closed: +# leave unset/false to guarantee NO automatic deletion. Set to true only after +# you have reviewed your retention policy and want the sweep to run. +GDPR_CLEANUP_ENABLED=false +# Optional: authenticates external schedulers calling /api/admin/retention-cleanup. +CRON_SECRET= + # ─── SEO ───────────────────────────────────────────────────────────────────── # Used by @nuxtjs/seo for sitemaps, canonical URLs, and OG tags NUXT_PUBLIC_SITE_URL=http://localhost:3000 @@ -78,6 +86,19 @@ NUXT_PUBLIC_SITE_URL=http://localhost:3000 # evaluates feature flags locally (no per-request HTTP round trip). # POSTHOG_FEATURE_FLAGS_KEY=phx_... +# ─── Optional: Platform AI gateway (OpenRouter) ───────────────────────────── +# When set, organizations that haven't added their own AI key fall back to this +# platform key, routed through OpenRouter for unified billing + analytics. +# Platform-paid runs are subject to the budget gate (server/utils/ai/budget.ts); +# bring-your-own-key (BYOK) orgs are never affected. Leave unset to stay BYOK-only. +# Get a key at https://openrouter.ai/keys +# OPENROUTER_API_KEY=sk-or-... +# Default model for platform-paid runs (OpenRouter-prefixed). Default: openai/gpt-5.4-mini +# OPENROUTER_MODEL=openai/gpt-5.4-mini +# Global platform-wide daily AI spend cap in USD — the runaway-loop kill-switch. +# Trips loudly long before a bug can empty your account. Default: 25 +# AI_DAILY_SPEND_CAP_USD=25 + # ─── Optional: Feature Flag Overrides (no PostHog required) ───────────────── # Force any flag on or off without running PostHog. The full list of available # flags lives in shared/feature-flags.ts. Variable name pattern: @@ -124,8 +145,21 @@ NUXT_PUBLIC_SITE_URL=http://localhost:3000 # SMTP_SECURE=false # true = implicit TLS (port 465), false = STARTTLS (port 587) # Option B: Resend (free tier: 3,000 emails/month — resend.com) +# A send-only key is sufficient for ordinary transactional email. # RESEND_API_KEY=re_xxxxxxxxxxxx # RESEND_FROM_EMAIL="Reqcore " +# Candidate messages use the recruiter's name as the display name while this +# setting supplies the mailbox and Reply-To routes each private conversation. +# RESEND_CANDIDATE_FROM_EMAIL="Reqcore Messages " +# Candidate messaging requires a dedicated receiving subdomain and a signed +# webhook subscribed to email.received, sent, delivered, delayed, bounced, +# failed, and complained events at /api/webhooks/resend. Add the Resend-provided +# MX record to the subdomain; do not use the domain of an existing mailbox. +# The Receiving API key must have Full access (a send-only key cannot retrieve +# inbound message bodies). +# RESEND_RECEIVING_API_KEY=re_xxxxxxxxxxxx +# RESEND_REPLY_DOMAIN=reply.yourcompany.com +# RESEND_WEBHOOK_SECRET=whsec_xxxxxxxxxxxx # ─── Optional: Social Sign-In (Google, GitHub, Microsoft) ──────────────────── # Enable social login buttons on the sign-in and sign-up pages. @@ -147,3 +181,28 @@ NUXT_PUBLIC_SITE_URL=http://localhost:3000 # AUTH_MICROSOFT_CLIENT_ID=your-microsoft-client-id # AUTH_MICROSOFT_CLIENT_SECRET=your-microsoft-client-secret # AUTH_MICROSOFT_TENANT_ID=common + +# ─── Optional: Stripe Billing (paid plans) ─────────────────────────────────── +# Enables self-serve subscription checkout for the Solo, Team, and Scale plans. +# Leave STRIPE_SECRET_KEY unset to disable billing entirely (self-hosters are +# unaffected). When STRIPE_SECRET_KEY is set, ALL variables below are required. +# +# Setup: +# 1. Create three Products in Stripe with recurring Prices: +# "Solo" ($79/mo + $790/yr), "Team" ($239/mo + $2,390/yr), +# and "Scale" ($599/mo + $5,990/yr). +# 2. Enable the Customer Portal: Stripe Dashboard → Settings → Billing → Customer portal. +# 3. Add a webhook endpoint → https://yourdomain.com/api/auth/stripe/webhook +# Events: checkout.session.completed, customer.subscription.created/updated/deleted. +# Copy the signing secret into STRIPE_WEBHOOK_SECRET. +# 4. Local dev: run `stripe listen --forward-to localhost:3000/api/auth/stripe/webhook` +# and use the whsec_… it prints. +# +# STRIPE_SECRET_KEY=sk_test_xxx +# STRIPE_WEBHOOK_SECRET=whsec_xxx +# STRIPE_PRICE_SOLO_MONTHLY=price_xxx +# STRIPE_PRICE_SOLO_ANNUAL=price_xxx +# STRIPE_PRICE_TEAM_MONTHLY=price_xxx +# STRIPE_PRICE_TEAM_ANNUAL=price_xxx +# STRIPE_PRICE_SCALE_MONTHLY=price_xxx +# STRIPE_PRICE_SCALE_ANNUAL=price_xxx diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 96f1cd94..0d1bebe1 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.3.0" + ".": "1.6.0" } diff --git a/.vscode/settings.json b/.vscode/settings.json index 57fd5905..7000964b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { "snyk.advanced.autoSelectOrganization": false -} \ No newline at end of file +} diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 00000000..eca40b66 --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,37 @@ +{ + "file_scan_exclusions": [ + "**/.git", + "**/.svn", + "**/.hg", + "**/.jj", + "**/CVS", + "**/.DS_Store", + "**/Thumbs.db", + "**/.classpath", + "**/.settings", + "**/node_modules", + "**/.nuxt", + "**/.output", + "**/dist", + "**/build", + "**/coverage", + "**/playwright-report", + "**/test-results" + ], + "lsp": { + "vtsls": { + "settings": { + "typescript": { + "tsserver": { + "maxTsServerMemory": 2048 + } + }, + "javascript": { + "tsserver": { + "maxTsServerMemory": 2048 + } + } + } + } + } +} diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 359c4063..6d79701d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -2,7 +2,7 @@ ## Overview -Reqcore is a **Nuxt 4** full-stack application following a monolithic architecture with clear separation between client (`app/`) and server (`server/`) code. The system supports both **managed deployment** on Railway and **self-hosted deployment** via Docker Compose. +Reqcore is a **Nuxt 4** full-stack application following a monolithic architecture with clear separation between client (`app/`) and server (`server/`) code. Reqcore is open-source and self-hostable; Docker Compose is the reference deployment path for running the app with Postgres and S3-compatible object storage. ## Technology Stack @@ -20,7 +20,7 @@ Reqcore is a **Nuxt 4** full-stack application following a monolithic architectu | SEO | `@nuxtjs/seo` (Sitemap, Robots, Schema.org, SEO Utils, Site Config) | Search engine optimization, structured data | | Content | `@nuxt/content` v3 | Markdown blog engine with typed collections | | Infrastructure | Docker Compose (local dev) | Local Postgres, MinIO, Adminer | -| Hosting | Railway | Managed platform (auto-build, auto-deploy) | +| Hosting | Docker Compose / Railway-compatible platforms | Self-hosted or managed deployment | | CDN | Cloudflare (Free) | DNS, DDoS protection, edge caching | ## Directory Structure @@ -80,6 +80,7 @@ reqcore/ │ ├── middleware/ # Global server middleware │ ├── plugins/ │ │ ├── migrations.ts # Auto-apply migrations on startup +│ │ ├── posthog.ts # PostHog server-side capture + filtered error hook │ │ └── s3-bucket.ts # Ensure S3 bucket exists + enforce private policy │ └── utils/ # Auto-imported server utilities │ ├── auth.ts # Better Auth instance @@ -88,7 +89,8 @@ reqcore/ │ ├── requireAuth.ts # Auth guard (throws 401/403) │ ├── s3.ts # S3/MinIO client, upload, delete, bucket policy │ ├── slugify.ts # URL slug generation for public job pages -│ ├── rateLimit.ts # IP-based sliding window rate limiter +│ ├── rateLimit.ts # IP-based sliding window rate limiter (in-memory, single-instance) +│ ├── pgDumpEnv.ts # Allowlist of env vars passed to pg_dump (no secret leak) │ └── schemas/ # Shared Zod validation schemas │ ├── document.ts # MIME types, file limits, sanitizeFilename() │ ├── job.ts # Job create/update schemas @@ -96,6 +98,7 @@ reqcore/ │ └── application.ts # Application schemas ├── content/ # Markdown content (@nuxt/content v3) │ └── blog/ # Blog articles (*.md with YAML frontmatter) +├── ee/ # Enterprise Edition — separate license (see ee/LICENSE), a Nuxt layer merged in via `extends` ├── public/ # Static assets ├── docker-compose.yml # Postgres + MinIO + Adminer ├── drizzle.config.ts # Drizzle Kit configuration @@ -163,7 +166,7 @@ Nitro auto-imports everything from `server/utils/`. The core utilities are alway | `auth` | Better Auth instance | | `env` | Zod-validated environment variables | | `generateJobSlug` | URL slug generation for public job pages | -| `createRateLimiter` | IP-based sliding window rate limiter | +| `createRateLimiter` | IP-based sliding window rate limiter (in-memory; for multi-instance setups, terminate at the reverse proxy / CDN) | | `uploadToS3`, `deleteFromS3` | S3/MinIO file operations | ### 3. Environment Validation @@ -262,40 +265,44 @@ Blog articles are Markdown files in `content/blog/` powered by `@nuxt/content` v | Environment secrets | Validated at startup, never exposed to client | ## Deployment Architecture -Reqcore runs on **Railway** with **Cloudflare** as CDN/DNS: +Reqcore can run as a Docker Compose stack or on a managed container platform. The app expects PostgreSQL and S3-compatible object storage. | Component | Role | |-----------|------| -| Cloudflare (Free) | DNS, DDoS protection, SSL edge termination, AI bot blocking | -| Railway Service | Nuxt SSR app (auto-built from GitHub via Nixpacks) | -| Railway PostgreSQL | Managed Postgres database with automatic backups | -| Railway Storage Bucket | S3-compatible object storage for documents | +| Reverse proxy / CDN | DNS, TLS termination, DDoS protection | +| Nuxt SSR app | Web UI and Nitro API server | +| PostgreSQL | Application database | +| S3-compatible storage | Uploaded resumes and documents | ### Deploy Workflow ```bash -# Push to main branch — Railway auto-builds and deploys -git push origin main +./setup.sh +docker compose -f docker-compose.production.yml up -d +``` + +Managed platforms such as Railway can also build from source: -# Build: npm run build (detected from package.json) -# Start: node .output/server/index.mjs +```bash +npm run build +node .output/server/index.mjs ``` -### Environment Variables on Railway +### Environment Variables -Variables are configured in the Railway dashboard or via `railway variables`. Service-to-service references use Railway's template syntax: +Configure these variables through `.env`, Docker Compose, or your hosting provider's environment-variable UI: | Variable | Source | |----------|--------| -| `DATABASE_URL` | `${{Postgres.DATABASE_URL}}` | -| `S3_ENDPOINT` | `${{Bucket.ENDPOINT}}` | -| `S3_ACCESS_KEY` | `${{Bucket.ACCESS_KEY_ID}}` | -| `S3_SECRET_KEY` | `${{Bucket.SECRET_ACCESS_KEY}}` | -| `S3_BUCKET` | `${{Bucket.BUCKET}}` | -| `S3_REGION` | `${{Bucket.REGION}}` | -| `S3_FORCE_PATH_STYLE` | `false` | -| `BETTER_AUTH_SECRET` | Manual (sealed) | -| `BETTER_AUTH_URL` | Production: `https://reqcore.com` · PR/preview: `https://${{RAILWAY_PUBLIC_DOMAIN}}` | +| `DATABASE_URL` | PostgreSQL connection string | +| `S3_ENDPOINT` | S3-compatible API endpoint | +| `S3_ACCESS_KEY` | S3 access key | +| `S3_SECRET_KEY` | S3 secret key | +| `S3_BUCKET` | Bucket name | +| `S3_REGION` | Bucket region | +| `S3_FORCE_PATH_STYLE` | `true` for MinIO, `false` for virtual-hosted providers | +| `BETTER_AUTH_SECRET` | Random secret, at least 32 characters | +| `BETTER_AUTH_URL` | Public URL of your deployment | For zero manual PR setup, define `BETTER_AUTH_URL` as `https://${{RAILWAY_PUBLIC_DOMAIN}}` in your Railway preview/PR environment (or shared variables scoped to previews). ## Local Development Services diff --git a/CHANGELOG.md b/CHANGELOG.md index 09ca799b..aa2f6067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,162 @@ Format follows [Keep a Changelog](https://keepachangelog.com). Categories: **Add ## Unreleased +### Changed + +* **licensing:** re-open Reqcore as open-core — AGPLv3 for the core app, with a new [`ee/`](ee) directory (Nuxt layer) for paid, cloud-only features under a separate commercial license. Self-hosting is supported again, best-effort and unsupported (see [SELF-HOSTING.md](SELF-HOSTING.md)). Moved the already plan-gated SSO (`ee/server/api/sso/`), org-wide audit log (`ee/server/api/activity-log/index.get.ts`), source-analytics (`ee/server/api/source-tracking/stats.get.ts`), and AI-analysis dashboard (`ee/server/api/ai-analysis/stats.get.ts`) endpoints out of the AGPL tree and into `ee/` so the license split matches what's actually gated; the underlying tables and the ungated candidate timeline/activity feed/tracking-link CRUD stay in core. + ### Added +* **GDPR retention:** add a shared candidate-retention runner with a daily Nitro task, external cron endpoint, instance-wide emergency switch, quarantine restoration on renewed public engagement, localized administration UI, and computed expiry visibility. +* **GDPR erasure:** remove application-linked comments, custom properties, and activity records in addition to the candidate database graph and S3 objects. * **blog:** add Cluster 8 career page articles — pillar (career-page-that-converts) and two supporting articles (career-page-seo, google-for-jobs-structured-data) * **blog:** add incoming links to career page content from how-applicant-tracking-systems-work, open-source-applicant-tracking-system, and self-hosted-vs-cloud-ats --- +## [1.6.0](https://github.com/reqcore-inc/reqcore/compare/v1.5.0...v1.6.0) (2026-07-18) + + +### ✨ Features + +* add ApplicationBuilderPreview and ApplicationFormBody components for candidate application preview and submission ([5118b6e](https://github.com/reqcore-inc/reqcore/commit/5118b6edb875296e5884f2eeed07a15c705b0857)) +* add branded career pages, ai config, and models ([7e9bcf6](https://github.com/reqcore-inc/reqcore/commit/7e9bcf627cd1ab66e972bfb431204611c2043847)) +* add end-to-end tests for privacy retention and GDPR compliance flows ([a0e2b58](https://github.com/reqcore-inc/reqcore/commit/a0e2b584cef92342fe46fe79daefb75ddc3c9272)) +* add public pricing section component and onboarding survey ([38e32a9](https://github.com/reqcore-inc/reqcore/commit/38e32a9e7b7a4cb23652cddebd9da32dda0cb197)) +* **ai:** implement budget management and observability for AI analysis runs ([f7a18f6](https://github.com/reqcore-inc/reqcore/commit/f7a18f6d70442931cb08749586f52bda34cb1abb)) +* **billing:** add billing plans and authorization logic ([86dd691](https://github.com/reqcore-inc/reqcore/commit/86dd6917befc8c2383872b6313ef0b6f95b3cbaa)) +* **billing:** add Stripe billing ([48a961c](https://github.com/reqcore-inc/reqcore/commit/48a961c8fe8c6a625cfc2dc423c25b1b3d96394a)) +* **billing:** enable Stripe promotion codes ([357c9b1](https://github.com/reqcore-inc/reqcore/commit/357c9b1ed61060570030670a50fe4ced54c8a9a8)) +* enable GDPR cleanup sweep in E2E tests and validate cleanup response ([dd16794](https://github.com/reqcore-inc/reqcore/commit/dd16794c4a8d720bc8ae196b650d178394212b5a)) +* enforce demo account isolation and organization access restrictions ([f47296e](https://github.com/reqcore-inc/reqcore/commit/f47296e91bcc3dad116d25152d8791a7d999c054)) +* enforce demo account isolation and organization access restrictions ([72433b9](https://github.com/reqcore-inc/reqcore/commit/72433b9c23bc0a9aaa28c03db36c97e86f01e0cf)) +* enhance GDPR retention and erasure processes with improved confirmation and settings ([6e110c3](https://github.com/reqcore-inc/reqcore/commit/6e110c36fe007b131080025a2bb4c3b147091370)) +* enhance job creation wizard with validation and schema updates; limit questions and criteria ([b128764](https://github.com/reqcore-inc/reqcore/commit/b12876452e1a35c4b9a7e578c6115379aebba03b)) +* enhance job question validation and update schemas; add question state validation and tests ([18bdd9f](https://github.com/reqcore-inc/reqcore/commit/18bdd9f7952bd69384f63223012b99c2fe4af10b)) +* handle organization update and delete errors with proper error handling ([989cca3](https://github.com/reqcore-inc/reqcore/commit/989cca3c21359e2dc788c96d91fa8596a9b17de5)) +* implement candidate erasure service and GDPR retention logic ([8692040](https://github.com/reqcore-inc/reqcore/commit/869204063470a88b060b03a33f353250545291ac)) +* implement candidate erasure service and GDPR retention logic ([b2910f8](https://github.com/reqcore-inc/reqcore/commit/b2910f8592b8a92936076c494f6710c5c11f7a79)) +* implement candidate retention and erasure processes ([7ee6ee3](https://github.com/reqcore-inc/reqcore/commit/7ee6ee315a1c9293e1777f8f1992b486fbda1085)) +* implement onboarding survey database storage ([ec6c3a0](https://github.com/reqcore-inc/reqcore/commit/ec6c3a0ee3d241e52e7f5bd4983530aa7f417904)) +* **jobs:** polish job creation wizard ([1926a8a](https://github.com/reqcore-inc/reqcore/commit/1926a8a3ff3d0a2fee39b4608e303dd48d899f0b)) +* Redesign application confirmation page ([0ae894f](https://github.com/reqcore-inc/reqcore/commit/0ae894fe1f84a2644def8d372ce576d55c72609f)) +* Refactor interview scheduling and add messaging ([88bdece](https://github.com/reqcore-inc/reqcore/commit/88bdece8b1b77e76954baafae3f9f1905b1f14fa)) +* transition to open-core model ([e0944b0](https://github.com/reqcore-inc/reqcore/commit/e0944b01461f80cdd0f899db7e54a839c5b85031)) +* update demo showcase to redirect to sign-in with prefilled demo credentials ([5625a56](https://github.com/reqcore-inc/reqcore/commit/5625a56aa57ac08a22fdeb943fe099b9f4e60934)) +* update dialog type for candidate erasure confirmation and refine legal hold handling in delete API ([bd2d156](https://github.com/reqcore-inc/reqcore/commit/bd2d156948545fd1b9e8367f35b2eec510aab7a1)) + + +### 🐛 Bug Fixes + +* align self-hosted billing copy ([b023b71](https://github.com/reqcore-inc/reqcore/commit/b023b71fd11036994d193ca7f642c97a06bd7d9d)) +* **e2e:** handle onboarding survey redirect after org creation ([3ed0630](https://github.com/reqcore-inc/reqcore/commit/3ed06307880b7d0cfb5bf5eff7b89d52bd5de450)) +* improve log handling in migration and S3 bucket readiness checks ([29fa699](https://github.com/reqcore-inc/reqcore/commit/29fa699bd64d8facb4828b668ddf1c4362ac9035)) +* refine rate limiting logic for production environment ([0d9c219](https://github.com/reqcore-inc/reqcore/commit/0d9c21917b10981788017940bc15eddb3a5b1eae)) +* sync npm lockfile ([e6d884e](https://github.com/reqcore-inc/reqcore/commit/e6d884e366a339edf1961976f1285d015d737306)) +* tolerate partial stripe billing env ([c9d702f](https://github.com/reqcore-inc/reqcore/commit/c9d702f20d2608d70918ac823d124c842d5040d6)) + + +### ♻️ Refactoring + +* remove JobQuestions component to streamline job wizard UI ([69ace61](https://github.com/reqcore-inc/reqcore/commit/69ace611d11ac8c557803b94e33d906cdbf16e11)) +* remove unused aiScoringChosen state and update form storage logic; add debug sign-in page script ([9b20abd](https://github.com/reqcore-inc/reqcore/commit/9b20abd91c3150596b1f882e80fc9cb1cfb7cae0)) +* update comments to clarify indexing rules for public marketing pages ([c919014](https://github.com/reqcore-inc/reqcore/commit/c919014db4ed60763802ab1577339893d341b396)) + + +### 🧪 Testing + +* enhance organization delete error handling in auth client tests ([d533052](https://github.com/reqcore-inc/reqcore/commit/d533052ec7d423310240d75bf7a4a7e54b4b5947)) + + +### 🏗️ Build & CI + +* wait for migration startup log ([24f5136](https://github.com/reqcore-inc/reqcore/commit/24f513685f9825040b60f9d3516689c80376523e)) + +## [1.5.0](https://github.com/reqcore-inc/reqcore/compare/v1.4.0...v1.5.0) (2026-05-17) + + +### ✨ Features + +* add pgDumpEnv utility to secure environment variable handling ([6fe4900](https://github.com/reqcore-inc/reqcore/commit/6fe490000487779ad008277ee650ded375bdbcf9)) +* enhance color mode functionality and improve UI responsiveness ([8068e4e](https://github.com/reqcore-inc/reqcore/commit/8068e4ec7eecb6c087d6ae45e6ce2a3e6c60374e)) +* implement nonce-based CSP middleware for enhanced security ([bfb4483](https://github.com/reqcore-inc/reqcore/commit/bfb44830d3205dc9e8c5392fdabdb8da4ed37a5e)) +* implement nonce-based CSP middleware for enhanced security ([6fe4900](https://github.com/reqcore-inc/reqcore/commit/6fe490000487779ad008277ee650ded375bdbcf9)) + + +### 🐛 Bug Fixes + +* enhance rate limiting logic and add tests ([6fe4900](https://github.com/reqcore-inc/reqcore/commit/6fe490000487779ad008277ee650ded375bdbcf9)) +* update comments for clarity and enhance rate limiting logic in production ([921ea39](https://github.com/reqcore-inc/reqcore/commit/921ea399bc35fbb006274d98faf7433fedf88aa5)) +* update overrides to resolve high-severity CVEs blocking dep PRs ([a1edd32](https://github.com/reqcore-inc/reqcore/commit/a1edd32486b91edc60dec80e95d74b8c6d24b877)) + + +### 🧪 Testing + +* add security tests for recent fixes ([6fe4900](https://github.com/reqcore-inc/reqcore/commit/6fe490000487779ad008277ee650ded375bdbcf9)) +* add unit tests for pgDumpEnv utility ([6fe4900](https://github.com/reqcore-inc/reqcore/commit/6fe490000487779ad008277ee650ded375bdbcf9)) + +## [1.4.0](https://github.com/reqcore-inc/reqcore/compare/v1.3.0...v1.4.0) (2026-04-30) + + +### ✨ Features + +* add AI chatbot feature with configuration, access control, and attachment management ([e139b72](https://github.com/reqcore-inc/reqcore/commit/e139b7296c1f3b0275ade32f5f44bac373559bf3)) +* add AI chatbot feature with configuration, access control, and attachment management ([912d55d](https://github.com/reqcore-inc/reqcore/commit/912d55d864efee44bf6f17c18c4dff77dfd0a86a)) +* add ApplicationDetailDrawer and CandidateDetailDrawer components ([1371e7d](https://github.com/reqcore-inc/reqcore/commit/1371e7ddfdefb09d152b3945951c5abbce068602)) +* add column visibility management to Applications and Candidates views ([a5237a5](https://github.com/reqcore-inc/reqcore/commit/a5237a54448cc5f6de88e2509d44ee3701e96975)) +* add docker entrypoint script to derive NUXT_PUBLIC_* flags from environment variables ([39e098e](https://github.com/reqcore-inc/reqcore/commit/39e098ece0e8823513be402a8d68636bd3ebea3d)) +* add Docker support with pre-built image instructions and CI workflow ([753b37e](https://github.com/reqcore-inc/reqcore/commit/753b37ea15eeb3c8ccbe6249d634d736574da13a)) +* add Docker support with pre-built image instructions and CI workflow ([6f9223d](https://github.com/reqcore-inc/reqcore/commit/6f9223d520baa5dada4379cd175c78738837d290)) +* add document re-parsing functionality and improve error handling in candidate analysis ([8842c6f](https://github.com/reqcore-inc/reqcore/commit/8842c6fb69b78b3f07326bba98c14032ff7a02e6)) +* add experience level and quick notes fields to job and candidate schemas ([d36b5a0](https://github.com/reqcore-inc/reqcore/commit/d36b5a07ae2aecb0ffc3faa52eabf5219f8da468)) +* add new migration entries for candidate demographics organization settings and salary negotiable ([36e3e81](https://github.com/reqcore-inc/reqcore/commit/36e3e8171fc367c89afe17c38522e0ea447e0911)) +* add Nitro plugin to recompute public auth-provider flags at server startup ([6b7b699](https://github.com/reqcore-inc/reqcore/commit/6b7b6999a6c12f21009f8bd9b474412fdf86c9fc)) +* add OIDC SSO environment validation and unit tests ([1b23af3](https://github.com/reqcore-inc/reqcore/commit/1b23af31b04d150e277701401e29424a07f9b8a8)) +* add organization localization settings and candidate demographics ([f828877](https://github.com/reqcore-inc/reqcore/commit/f828877ff1090cc9001ede9e5be3cfdfa26cec7f)) +* add property management utilities and schemas ([a62eea1](https://github.com/reqcore-inc/reqcore/commit/a62eea1f5644ba0cd4cd892cea14a376746994ce)) +* add property management utilities and schemas ([4dc5aad](https://github.com/reqcore-inc/reqcore/commit/4dc5aad0252a67306633b9f63e56d9d5737bce7d)) +* add raw tag support for Docker image publishing ([29775cb](https://github.com/reqcore-inc/reqcore/commit/29775cb1b17d560f76bfe2e73e5d5dc2c5d99a9c)) +* add salary input change handlers and update permissions for organization ([6c238c2](https://github.com/reqcore-inc/reqcore/commit/6c238c2fae2341639bde2f961ba1bbd36708044f)) +* add site origin computation for dynamic redirect URI in SSO setup ([9e5aa68](https://github.com/reqcore-inc/reqcore/commit/9e5aa688006e9254bc44f4c93c180c300ed9ad12)) +* add SSO provider schema and relations for better authentication integration ([62fdf39](https://github.com/reqcore-inc/reqcore/commit/62fdf399d79132e30889ded51b312642454de2f9)) +* **ai-config:** add connection test functionality and update AI settings UI ([c9f4afd](https://github.com/reqcore-inc/reqcore/commit/c9f4afd15b8787ce4c9414db2bde7a21ed3ffc10)) +* enhance authentication security with stricter password policy, email verification, and session management ([aaae17f](https://github.com/reqcore-inc/reqcore/commit/aaae17f66c6ee3f669843526c38d9f38983aa662)) +* enhance forgot password functionality and improve SSRF protection ([8e0abd6](https://github.com/reqcore-inc/reqcore/commit/8e0abd6efcc1b1ad8bceacd32491d46909fea46c)) +* enhance OIDC endpoint origin fetching to directly inject discovered origins into trusted-origins list ([ee34d86](https://github.com/reqcore-inc/reqcore/commit/ee34d86125e3de07b2ca0e200c52f94c4d8f87a2)) +* Enhance PostHog proxy handling with explicit header management and error handling ([8b9ea20](https://github.com/reqcore-inc/reqcore/commit/8b9ea205c32b86e43268d2ffb26cc6972a9855cb)) +* enhance property management with new color variables and update component interactions ([349ec6a](https://github.com/reqcore-inc/reqcore/commit/349ec6a76f2bec70a0b1410e1c8fdd990fa28600)) +* enhance PropertyFilterBar and PropertySchemaEditor with improved element references and state management ([cd7524e](https://github.com/reqcore-inc/reqcore/commit/cd7524e4b7d716dc4c732ee88ca60d4c66c91c7e)) +* enhance SSO sign-in and sign-up error handling, and enforce email requirement in profile mapping ([76c54b4](https://github.com/reqcore-inc/reqcore/commit/76c54b4026eb3de9e5aa6de57eaf682393f24a27)) +* enhance trusted origins resolution for CSRF checks and OIDC discovery ([3c24417](https://github.com/reqcore-inc/reqcore/commit/3c244175cd07e428624217a6d609bd5d3ae155a5)) +* enhance trusted origins resolution for SSO provider registration ([b5832b6](https://github.com/reqcore-inc/reqcore/commit/b5832b64c975c9dab88ba2a3b84208758bb1fbc9)) +* enhance workflows and documentation for release process, including PR title linting and release verification ([4785db5](https://github.com/reqcore-inc/reqcore/commit/4785db56bd7d282ce28f63a18f3687c976c525e0)) +* implement forgot password and reset password functionality ([aa00e89](https://github.com/reqcore-inc/reqcore/commit/aa00e8947d5c0b37410971624d3e036504ca8ceb)) +* implement forgot password and reset password functionality ([ad864ef](https://github.com/reqcore-inc/reqcore/commit/ad864efff2456ad08aa7038d7f1e9a312263d9a9)) +* implement OIDC endpoint origin prefetching for trusted origins resolution ([9c355ab](https://github.com/reqcore-inc/reqcore/commit/9c355abc6720fe129255107462472fada48ba76e)) +* implement social sign-in for Google, GitHub, and Microsoft with configuration support ([d4ceaf8](https://github.com/reqcore-inc/reqcore/commit/d4ceaf811134d881af5fe74d70db78d85717f802)) +* implement social sign-in for Google, GitHub, and Microsoft with configuration support ([0e4d4bd](https://github.com/reqcore-inc/reqcore/commit/0e4d4bd686c9c7014a149289f2e87b2c359c395d)) +* Implement two-tier consent model for PostHog analytics ([0d51cd5](https://github.com/reqcore-inc/reqcore/commit/0d51cd53dbae1c20267a04220f2b6bd42e3ae2c9)) +* Implement two-tier consent model for PostHog analytics ([ef7fee5](https://github.com/reqcore-inc/reqcore/commit/ef7fee50cfa5cf0fa079f264453cdba873fa97df)) +* implement unique default chatbot agent constraint and enhance related logic for attachment management ([f11a78f](https://github.com/reqcore-inc/reqcore/commit/f11a78fced7dcc82e1a98bce28b94f2010bfe705)) +* improve edit element reference handling in PropertyFilterBar ([486d0e1](https://github.com/reqcore-inc/reqcore/commit/486d0e148b7a10ba36d59931c776a26ea6b1ee77)) +* refactor authentication handling to use runtime-config for providers and remove entrypoint script ([ad91cc9](https://github.com/reqcore-inc/reqcore/commit/ad91cc9ae61ee7d30c95fed4bc52cf09596ada1e)) +* streamline authentication configuration by removing deprecated social sign-in options and enhancing OAuth token encryption ([b94ffd9](https://github.com/reqcore-inc/reqcore/commit/b94ffd925fc250c59aa397924a0e4b303406c342)) +* update button styles for social sign-in and sign-up to improve user interaction ([d8d0e6e](https://github.com/reqcore-inc/reqcore/commit/d8d0e6ebbcb6456797051f6baeb6bddaec43f033)) +* update color classes for property options to enhance visual consistency ([c827d56](https://github.com/reqcore-inc/reqcore/commit/c827d56f358dc18f0864444dc9ae051629f38d99)) +* Update PostHog consent model to use sessionStorage for cookieless tracking ([1368dbb](https://github.com/reqcore-inc/reqcore/commit/1368dbb4da7efa58ed18eb041fff605565d7da7d)) + + +### 🐛 Bug Fixes + +* address CodeRabbit review comments on PR [#166](https://github.com/reqcore-inc/reqcore/issues/166) ([3b9e52b](https://github.com/reqcore-inc/reqcore/commit/3b9e52bd33c597346b6defeb0ab1d4c068b03feb)) +* correct syntax error in prefetchOidcEndpointOrigins function ([3f6a56b](https://github.com/reqcore-inc/reqcore/commit/3f6a56bb21f3ca5648f1f8874c1579b07748bc7a)) +* register migrations 0023 and 0024 in drizzle journal ([93ed4b1](https://github.com/reqcore-inc/reqcore/commit/93ed4b1cd341e3f8cb7d541fd7dd595241dd618b)) +* remove orphaned code after </template> in candidates/new.vue ([a976d8d](https://github.com/reqcore-inc/reqcore/commit/a976d8d45292e051d9a51a48fd348024ef56c9ca)) +* resolve esbuild and typecheck errors in PR validation ([e3d9994](https://github.com/reqcore-inc/reqcore/commit/e3d9994ecc05cc03d4086443e97497e76156bc50)) +* Rewrite Host headers in proxyRequest to prevent Cloudflare errors ([fee0be6](https://github.com/reqcore-inc/reqcore/commit/fee0be64df209fee9cddc1844863a395460b3c31)) +* update token reference in release-please workflow to prioritize RELEASE_PLEASE_TOKEN ([7a57891](https://github.com/reqcore-inc/reqcore/commit/7a57891bcfae98080e9268a2d38bce5dec29c71d)) +* update token reference in release-please workflow to use GITHUB_TOKEN ([b2733f8](https://github.com/reqcore-inc/reqcore/commit/b2733f89c69f3dfff1005368f8a15d6e49081ecd)) + ## [1.3.0](https://github.com/reqcore-inc/reqcore/compare/v1.2.0...v1.3.0) (2026-04-03) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe3438ae..482b338c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,13 @@ # Contributing to Reqcore -Thanks for contributing to Reqcore. +Reqcore is open-source software. This guide explains how to set up a local development environment and contribute changes through GitHub. ## Before You Start - Read [PRODUCT.md](PRODUCT.md), [ARCHITECTURE.md](ARCHITECTURE.md), and [ROADMAP.md](ROADMAP.md) for product and technical context. - For bug reports and feature ideas, use GitHub Issues. - For security reports, do **not** open a public issue. Follow [SECURITY.md](SECURITY.md). +- Keep pull requests focused. Small, reviewable changes are more likely to be merged quickly. ## Development Setup diff --git a/DATA-RETENTION.md b/DATA-RETENTION.md new file mode 100644 index 00000000..59739da6 --- /dev/null +++ b/DATA-RETENTION.md @@ -0,0 +1,117 @@ +# Data Retention & GDPR + +Reqcore ships a free data-retention and erasure foundation for both hosted +and self-hosted deployments. No paid plan, license key, or quota gates any of it. + +> **Scope.** These tools help you operate a GDPR-aligned retention process; they +> do not by themselves make a deployment "GDPR-compliant." Compliance also +> depends on your lawful basis, privacy notices, processor agreements (incl. any +> AI providers), backup handling, and — for automated candidate scoring — a +> possible DPIA. Treat this as engineering support, not legal advice. + +## What it does + +- **Retention policy** (per organization): automatically delete candidate data a + configurable number of months (default **24**) after the end of the candidate's + **latest recruitment process** — not their upload date. +- **Quarantine window**: expired candidates first enter a recoverable quarantine + (default **30 days**) before permanent erasure, protecting against mistakes. + Quarantined candidates are hidden from normal candidate lists and cannot be + edited, receive internal applications, or receive new uploads. A fresh public + application counts as renewed engagement: it restores the candidate and resets + the retention clock before creating the application. +- **Recruiter "Delete" is a soft delete**: deleting a candidate from the candidate + page quarantines them — they are hidden from lists but **nothing is erased** and + they can be restored from *Settings → Privacy & Retention*. This makes an + accidental click harmless; permanent erasure is a separate, deliberate step. +- **Erasure**: a single erasure service removes the candidate's data graph in the + live system — DB records, applications, documents, **S3 objects**, AI results, + interviews, responses, custom properties, comments, and activity-log entries. + Permanent erasure is triggered explicitly from the retention review screen + (behind a type-the-name confirmation) and by the automated retention sweep; both + use the **same** path, so they produce identical results. (Backups are handled + separately — see below.) +- **Exemptions / legal holds**: candidates can be placed on a documented legal + hold (future expiry + required reason) that suppresses automated erasure *and* + blocks permanent manual erasure. Permanently erasing a held candidate requires an + explicit `override=true` to lift the hold. Restoring a candidate from quarantine + resets its retention clock, so it is not immediately re-quarantined on the next + sweep. +- **Data-subject support**: per-candidate JSON export (Art. 15 / 20) covering the + candidate, applications, responses, interviews, scores, AI analysis runs, + comments, custom properties, and activity log; uploaded-file *contents* are + retrieved via their individual download links. Corrections are made via the + normal candidate edit screens. +- **Privacy notice**: an org-configurable notice shown on the public application + form, with policy URL and contact email. +- **Privacy-safe audit**: every retention action writes a `retention_audit` row + containing **no** names, emails, filenames, resume content, or storage keys. + +Configure it under **Settings → Privacy & Retention**. + +## How retention is calculated + +``` +expiry = (latest application activity OR candidate creation) + retentionMonths +``` + +floored so that nothing expires until at least 30 days after an org first enables +retention. This gives admins a review window — existing data is **never** deleted +immediately on enabling the feature. Expiry is derived on each cron run (not +stored), so it self-heals when a candidate gets a new application or status change. + +## Running the cleanup job + +Reqcore includes a Nitro scheduled task that runs every day at **03:00 UTC**. +The task and the authenticated endpoint below call the same cleanup service: + +``` +POST /api/admin/retention-cleanup +Header: x-cron-secret: +Body (optional): { "dryRun": true, "batchSize": 200 } +``` + +The endpoint can be triggered interactively by an owner/admin +(`candidate:delete` permission). Set `CRON_SECRET` (min 16 chars) only when an +external scheduler needs to call it. + +Automated cleanup is **off by default**: `GDPR_CLEANUP_ENABLED` is fail-closed +and must be explicitly set to `true` for any sweep to run. Leaving it unset or +`false` guarantees no automatic deletion and pauses all cleanup runs at the +instance level without changing any organization's stored retention policy. + +- **Dry run**: `{ "dryRun": true }` reports what would be quarantined/erased and + mutates nothing. +- **Idempotent**: safe to run repeatedly. If an S3 object fails to delete, the + candidate is left intact and retried on the next run (the storage key is never + lost). + +### Hosted (Railway) + +The built-in Nitro task runs while the application process is continuously +running. If the service can sleep or scheduled Nitro tasks are unsupported, +configure a Railway cron that POSTs to `/api/admin/retention-cleanup`. + +### Self-hosted + +The built-in task is sufficient for continuously running Node deployments. +Alternatively, point any scheduler at the endpoint once a day: + +```cron +0 3 * * * curl -fsS -X POST https://your-host/api/admin/retention-cleanup \ + -H "x-cron-secret: $CRON_SECRET" -H "content-type: application/json" -d '{}' +``` + +## Backups + +Erasure removes live database rows and S3 objects immediately. Backups expire on +their normal rotation schedule rather than being purged on demand (the standard +GDPR posture). **After restoring any backup, re-run the cleanup job** so that +candidates past their purge date are erased again and not silently resurrected. + +## Controller / processor split + +The organization is the **data controller**. For data-subject access, erasure, or +correction requests, the org is responsible for verifying the requester's identity +before acting, then using the admin tools (export / erase / edit). Reqcore does +not expose a candidate-facing self-service portal. diff --git a/INTERVIEW-SCHEDULING.md b/INTERVIEW-SCHEDULING.md new file mode 100644 index 00000000..3c31c220 --- /dev/null +++ b/INTERVIEW-SCHEDULING.md @@ -0,0 +1,68 @@ +# Simplified Interview Scheduling + +## Goal + +Make interview scheduling a natural part of the existing candidate conversation. Recruiters should not have to choose between email, calendar notifications, templates, or delivery methods. + +## Core Approach + +- Send every interview proposal through the two-way Reqcore inbox. +- Use the recruiter as the visible sender and route candidate replies back to the existing conversation. +- Attach an ICS calendar invitation so candidates can add the interview to Google Calendar, Outlook, Apple Calendar, or another calendar without a Reqcore calendar integration. +- Keep Reqcore as the source of truth for the interview and its confirmation state. +- Remove the separate no-reply invitation flow. + +## Recruiter Experience + +The scheduling form should ask only for: + +- Date and time +- Duration +- Interview format or location +- Interviewers +- An optional personal note + +Reqcore should automatically apply the organization defaults, detect the timezone, generate the message, attach the ICS invitation, and send it through the candidate conversation. + +## Candidate Experience + +The candidate receives one email containing the interview details, calendar invitation, and clear actions: + +- Confirm +- Request another time +- Decline + +They can also reply normally. Replies and interview responses remain visible in the same Reqcore conversation. + +Reschedules and cancellations should be sent through the same thread with an updated ICS invitation, using the same event identifier so calendar applications update the existing event. + +## Recruiter Outcome Actions + +- Cancelling an interview is candidate-facing after a proposal has been sent. Reqcore sends a cancellation message and calendar cancellation through the existing conversation. +- Marking an interview completed or no-show is an internal recruiting outcome. It does not contact the candidate. +- Recruiter controls must state whether the candidate will be contacted before the action is confirmed. +- A status change and its candidate-message delivery are separate outcomes. If delivery fails, the status remains accurate while the failed message stays visible and retryable. +- Deleting an interview is administrative and never contacts the candidate. A scheduled interview with a sent proposal must be cancelled before it can be deleted. + +## Calendar Integrations + +Google Calendar integration should be optional. The ICS invitation covers the candidate-facing calendar experience without OAuth or calendar webhooks. + +A connected calendar may later synchronize recruiter availability and internal events, but candidate communication and confirmation should continue to flow through Reqcore. + +## Reliability + +Interview state, message delivery, and calendar delivery should be tracked separately. Reqcore should never report that a proposal was sent when only the interview record was created. + +Failed messages should remain visible and retryable. Calendar attachment or synchronization failures should not lose the interview or conversation. + +## Free Plan + +The free plan can limit the number of tracked candidate conversations or interview processes. When the limit is reached: + +- Make upgrading the primary action. +- Explain that upgrading preserves replies, confirmations, calendar updates, and shared history. +- Allow the recruiter to continue outside Reqcore through a less convenient manual fallback. +- Never hide existing replies or block critical updates to an interview already in progress. + +The upgrade should sell a coordinated and reliable workflow rather than create a dead end during active hiring. diff --git a/PRODUCT.md b/PRODUCT.md index 02747412..5841162b 100644 --- a/PRODUCT.md +++ b/PRODUCT.md @@ -1,46 +1,44 @@ # Reqcore — Product Vision & Goals -## The Developer-First ATS +## The ATS for High-Volume Applicant Flow -Reqcore is a lean, open-source Applicant Tracking System (ATS) designed for engineering teams and developer-led companies. Built by developers, for developers. +Reqcore is an open-source Applicant Tracking System (ATS) built for teams that receive a high volume of applications. It turns a flood of applicants into a trustworthy shortlist, fast. ## Problem Statement Modern ATS platforms suffer from three structural problems: -1. **Data Hostage**: Companies pay for *access* to their own candidate data. If the subscription lapses, the talent pool disappears. +1. **Applicant Overload**: Easy-Apply and AI-generated mass applications mean roles routinely draw hundreds to thousands of applicants. Most ATS platforms just store the pile — they don't help anyone get through it. 2. **Opaque AI**: Incumbent platforms use proprietary algorithms to rank candidates. Recruiters cannot see *why* a candidate was surfaced or rejected — creating legal and ethical liability. 3. **Per-Seat Tax**: Adding a hiring manager or recruiter to the platform increases the software bill, punishing growing teams. ## Unique Value Proposition (UVP) -### 1. Ownership over Access -You *own* the infrastructure (Postgres + MinIO). Your talent pool is a permanent asset — not a monthly subscription. Self-host on your own servers or use a managed deployment; either way, the data is yours. +### 1. Built for the Flood +Reqcore is engineered around one job: taking a high volume of inbound applicants and getting a hiring team to a trustworthy shortlist fast, without capping how many applicants they can receive. ### 2. Auditable Intelligence -The source code is public — anyone can read exactly how the system works. Planned AI features will expose ranking logic in a visible **Matching Logic** summary so recruiters can verify and override results. No secret algorithms. +Planned AI features will expose ranking logic in a visible **Matching Logic** summary so recruiters can verify and override results. No secret algorithms. ### 3. No Per-Seat Pricing Reqcore is designed to let companies scale their hiring teams without increasing their software bill. -### 4. Runs on Your Network -By supporting local-first storage (MinIO) and local AI models (Ollama), Reqcore is the only ATS where sensitive candidate PII never has to leave the company's private network. +### 4. Open Source and Self-Hostable +Reqcore is licensed under the AGPLv3 and can be self-hosted with Docker Compose (best-effort, unsupported — see [SELF-HOSTING.md](SELF-HOSTING.md)). A small set of paid, cloud-only features live under [`ee/`](ee) on a separate commercial license; the core hiring/scoring workflow never depends on it. ## Target Users | Persona | Description | Primary Need | |---------|-------------|--------------| -| **Engineering Manager / CTO** | Decides on tooling, deploys infrastructure | Simple self-hosting, Docker Compose, clear infra docs, extensibility | | **Recruiter** | Day-to-day user managing candidates and pipeline | Fast candidate pipeline, clean UI, minimal friction | | **Hiring Manager** | Reviews candidates, makes hiring decisions | Clear candidate comparisons, process visibility | -| **HR Administrator** | Manages org settings, team access, compliance | Multi-tenant control, data ownership, audit trails | +| **HR Administrator** | Manages org settings, team access, compliance | Multi-tenant control, audit trails | ### Who this is for -- **Startups and scale-ups** with engineering-led cultures who deploy their own tools -- **Dev agencies and consultancies** that hire technical talent regularly -- **Engineering-led orgs** that want to own their hiring data like they own their code -- **Anyone who deploys with Docker** and doesn't want to go through procurement for an ATS +- **Businesses with continuous high-volume hiring** — staffing/recruitment agencies, BPOs/call centers, home/healthcare staffing, multi-unit franchise groups +- **Any SMB drowning in applicants** for a role (remote/entry-level postings routinely draw 250–1,000+ applications) +- **Not for**: occasional/accidental hirers with low applicant volume, or teams that need a large enterprise suite before they need high-throughput applicant review ## Core Features (Current & Planned) @@ -57,7 +55,6 @@ By supporting local-first storage (MinIO) and local AI models (Ollama), Reqcore - [ ] Resume parsing (PDF → structured JSON) - [ ] AI candidate ranking with visible **Matching Logic** summary - [ ] Skill extraction and matching -- [ ] Local AI support via Ollama (privacy-first) ### Phase 3 — Collaboration - [ ] Team comments and notes on candidates @@ -76,5 +73,5 @@ By supporting local-first storage (MinIO) and local AI models (Ollama), Reqcore - **Time to first hire**: How quickly can a new org go from setup to first candidate hired? - **Transparency score**: % of AI decisions with visible matching logic -- **Self-hosting success rate**: % of deployments that complete without support tickets +- **Time to shortlist**: How quickly a high-volume applicant pool becomes a trustworthy shortlist - **Team adoption**: Number of users per org (validates anti-seat-pricing model) diff --git a/README.md b/README.md index 9a91c55e..d440c79f 100644 --- a/README.md +++ b/README.md @@ -2,48 +2,47 @@ # Reqcore -**The simple, open-source ATS. Self-hosted. No per-seat fees.** +**The open source ATS for teams drowning in applicants. AI shortlisting that shows its work.** + [![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](LICENSE) [![Docker Image](https://ghcr-badge.egpl.dev/reqcore-inc/reqcore/latest_tag?trim=major&label=docker)](https://github.com/reqcore-inc/reqcore/pkgs/container/reqcore) -[Live Demo](https://reqcore.com) · [Documentation](ARCHITECTURE.md) · [Roadmap](ROADMAP.md) · [Report Bug](https://github.com/reqcore-inc/reqcore/issues/new) - - -Reqcore badge -Deploy on Railway - +[Get Started](https://reqcore.com) · [Pricing](https://reqcore.com/pricing) · [Documentation](ARCHITECTURE.md) · [Roadmap](ROADMAP.md) · [Report Bug](https://github.com/reqcore-inc/reqcore/issues/new) --- -Hiring software shouldn't be complicated or expensive. Most applicant tracking systems charge per seat, lock your data in their cloud, and overwhelm you with features you don't need. Reqcore is a lightweight, open-source ATS you can self-host in minutes. No per-seat fees, no vendor lock-in, no bloat — just a clean tool that helps you hire. +A flood of applicants turns hiring into a full-time scroll. Reqcore takes every application on a role, runs it through an AI shortlist, and shows you exactly why each candidate was ranked the way they were — no black box. No per-seat fees, unlimited applicants on every plan. + +Reqcore is open-core: the full hiring workflow — jobs, pipeline, applications, documents, job board, and AI shortlisting — is AGPLv3 and lives in this repo. A small set of paid, cloud-only features (SSO/SAML, audit log, source analytics) live under [`ee/`](ee) on a separate commercial license. The core scoring and shortlist logic never depends on `ee/` — that's the part self-hosters and evaluators need to be able to trust and verify. -> **Early open-source release** — Reqcore is actively developed and improving every week. The foundation is solid (jobs, pipeline, applications, documents, job board), but some features are still on the roadmap. Check the [Roadmap](ROADMAP.md) for what's shipped and what's next. +> The fastest way to use Reqcore is the hosted product at [reqcore.com](https://reqcore.com) — free until your first shortlist, no card required. Prefer to run it yourself? See [Self-Hosting](#self-hosting) below. ## Why Reqcore? -*Simple hiring software you actually own.* +*Built for teams drowning in applicants.* | | **Reqcore** | Greenhouse | Lever | Ashby | OpenCATS | |---|:---:|:---:|:---:|:---:|:---:| -| **Open source** | ✅ | ❌ | ❌ | ❌ | ✅ | -| **Self-hosted** | ✅ | ❌ | ❌ | ❌ | ✅ | | **No per-seat pricing** | ✅ | ❌ | ❌ | ❌ | ✅ | -| **Own your data** | ✅ | ❌ | ❌ | ❌ | ✅ | -| **Transparent AI ranking** | 🔜 | ❌ | ❌ | ❌ | ❌ | +| **Unlimited applicants, every plan** | ✅ | ❌ | ❌ | ❌ | ✅ | +| **AI shortlist with visible scoring** | ✅ | ❌ | ❌ | ❌ | ❌ | +| **Resume parsing** | ✅ | ✅ | ✅ | ✅ | ❌ | +| **Bring your own AI key** | ✅ | ❌ | ❌ | ❌ | ❌ | +| **Open source** | ✅ AGPLv3 | ❌ | ❌ | ❌ | ✅ | | **Modern tech stack** | Nuxt 4 / Vue 3 | — | — | — | PHP 5 | | **Active development** | ✅ 2026 | ✅ | ✅ | ✅ | ❌ Stale | -| **Resume parsing** | 🔜 | ✅ | ✅ | ✅ | ❌ | | **Pipeline / Kanban** | ✅ | ✅ | ✅ | ✅ | ❌ | | **Public job board** | ✅ | ✅ | ✅ | ✅ | ❌ | -| **Document storage** | ✅ MinIO | ✅ | ✅ | ✅ | ✅ | -| **Custom application forms** | ✅ | ✅ | ✅ | ✅ | ❌ | -| **Local AI (privacy-first)** | 🔜 Ollama | ❌ | ❌ | ❌ | ❌ | ## Features +- **AI shortlisting** — Every application gets scored against the job with a visible breakdown of why, not a black-box rank +- **Resume parsing** — Structured data (contact, experience, education, skills) extracted from uploaded resumes automatically - **Job management** — Create, edit, and track jobs through draft → open → closed → archived - **Candidate pipeline** — Drag candidates through screening → interview → offer → hired with a Kanban board - **Public job board** — SEO-friendly job listings with custom slugs that applicants can browse and apply to @@ -51,18 +50,27 @@ Hiring software shouldn't be complicated or expensive. Most applicant tracking s - **Document storage** — Upload and manage resumes and cover letters via S3-compatible storage (MinIO) - **Multi-tenant organizations** — Isolated data per organization with role-based membership - **Recruiter dashboard** — At-a-glance stats, pipeline breakdown, recent applications, and top active jobs +- **GDPR tooling** — Candidate data retention windows, export, and erasure built in - **Secure document access** — Resumes are never exposed via public URLs; all access is authenticated and streamed - **Built-in rate limiting** — Protection against abuse on all endpoints out of the box -## Quick Start +## Pricing -> **Windows users:** Open [Git Bash](https://gitforwindows.org) and run all commands there instead of Command Prompt or PowerShell. +Reqcore is free to start and priced per active role, not per seat — invite your whole team without increasing the bill. ---- +| Plan | Price | Active roles | Highlights | +|------|-------|:---:|------------| +| **Free** | $0 | 1 | Unlimited applicants, one AI shortlist to try it, bring your own AI key | +| **Solo** | $79/mo | 2 | Unlimited AI shortlists, bring your own AI key, full shortlist workflow | +| **Team** | $239/mo | 8 | Bring your own AI key, deeper per-application analysis, custom domain, integrations | +| **Scale** | $599/mo | 24 | SSO/SAML/SCIM, audit log, DPA/SLA | +| **Agency** | Contact us | Unlimited | Custom contract | + +See the live [pricing page](https://reqcore.com/pricing) for full details. Self-hosted instances use the same plan gates — see [Licensing & self-hosting](#licensing--self-hosting). -### Option A — Use the pre-built image (fastest) +## Self-Hosting -No cloning, no building. Pull the official image and run: +Reqcore can be run on your own infrastructure with Docker Compose. This is a best-effort, DIY path without support or an SLA — Reqcore's own support and uptime commitments apply only to the hosted product at [reqcore.com](https://reqcore.com). ```bash mkdir reqcore && cd reqcore @@ -73,190 +81,11 @@ chmod +x setup.sh docker compose -f docker-compose.production.yml up -d ``` -Open **[http://localhost:3000](http://localhost:3000)** and sign up. That's it. - -To update: `docker compose -f docker-compose.production.yml pull app && docker compose -f docker-compose.production.yml up -d` - ---- - -### Option B — Build from source - ---- - -### Step 1 — Install Docker - -Docker packages the app, database, and file storage into containers so you don't have to install anything else manually. - -| Your OS | How to install | -|---------|---------------| -| **Mac** | [Download Docker Desktop for Mac](https://www.docker.com/products/docker-desktop/) → install → open it | -| **Windows** | [Download Docker Desktop for Windows](https://www.docker.com/products/docker-desktop/) → install → open it | -| **Linux** | Follow the [Docker Engine install guide](https://docs.docker.com/engine/install/) for your distro | - -Once installed, verify Docker is running: - -```bash -docker --version -``` - -You should see something like `Docker version 27.x.x`. If you get `command not found`, Docker isn't running yet — open Docker Desktop and try again. - ---- - -### Step 2 — Download Reqcore - -Clone the repository (this downloads the source code): - -```bash -git clone https://github.com/reqcore-inc/reqcore.git -cd reqcore -``` - -> Don't have `git`? [Download it here](https://git-scm.com/downloads), or [download a ZIP](https://github.com/reqcore-inc/reqcore/archive/refs/heads/main.zip) and unzip it manually. - ---- - -### Step 3 — Generate your secret keys - -This creates a `.env` file containing random passwords and secrets. You only run this once. - -```bash -./setup.sh -``` - -You'll see: `✅ .env generated with random secrets.` - -> **Windows CMD / PowerShell?** Run `cp .env.example .env` instead, then open `.env` and replace every placeholder value with a random string of your choice. - ---- - -### Step 4 — Start the app - -```bash -docker compose up -``` - -**The very first run takes 3–5 minutes** while Docker builds the app image and downloads dependencies. This is normal — you only wait this long once. Subsequent starts take seconds. - -When you see a line like: - -``` -app | Listening on http://[::]:3000 -``` - -...the app is ready. - ---- - -### Step 5 — Open Reqcore - -Go to **[http://localhost:3000](http://localhost:3000)** in your browser. - -Click **Sign up** to create your account and first organization. That's it — you're running your own ATS. - ---- +Open **[http://localhost:3000](http://localhost:3000)** and sign up. -### Optional: Load demo data +Code under [`ee/`](ee) gates itself behind the same plan checks as the hosted product, so it stays locked without your own billing configured — no separate license-key server needed. -Want to explore with pre-filled jobs, candidates, and a pipeline? Open a **new terminal window** while the app is running and run: - -```bash -docker compose exec app npm run db:seed -``` - -Then sign in with: -- **Email:** `demo@reqcore.com` -- **Password:** `demo1234` - ---- - -### Updating to a new release - -When a new version of Reqcore is released, follow these steps **in order** to update your instance. Your data is safe — updates never delete the database or your uploaded files. - -#### Pre-built image users - -```bash -docker compose -f docker-compose.production.yml pull app -docker compose -f docker-compose.production.yml up -d -``` - -#### Build from source users - -**Step 1 — Pull the latest code** - -```bash -git pull origin main -``` - -**Step 2 — Rebuild and restart the app** - -```bash -docker compose up --build -d -``` - -This rebuilds the app image with the new code, applies any new database migrations automatically on startup, and restarts in the background. The whole process typically takes under a minute. - -**Step 3 — Verify it's running** - -```bash -docker compose logs app --tail 20 -``` - -Look for `Listening on http://[::]:3000`. Then open [http://localhost:3000](http://localhost:3000) — you're on the latest version. - -> **Something wrong after an update?** Roll back by running `git checkout ` and then `docker compose up --build -d`. - -> **To find the latest release notes**, check the [CHANGELOG](CHANGELOG.md) or [GitHub Releases](https://github.com/reqcore-inc/reqcore/releases). - ---- - -### Managing your instance - -```bash -# Stop the app (your data is kept) -docker compose down - -# Start it again -docker compose up - -# Rebuild after pulling new code -docker compose up --build - -# Stop and delete ALL data (irreversible) -docker compose down -v -``` - ---- - -### What's running - -| Service | URL | Description | -|---------|-----|-------------| -| **App** | [localhost:3000](http://localhost:3000) | The Reqcore web UI | -| **MinIO Console** | [localhost:9001](http://localhost:9001) | File storage browser (S3-compatible) | -| **Adminer** | [localhost:8080](http://localhost:8080) | Database browser — only with `--profile tools` | - -To enable Adminer (a visual database browser): - -```bash -docker compose --profile tools up -# Open http://localhost:8080 -# System: PostgreSQL | Server: db | Username & Password: from your .env -``` - ---- - -### Troubleshooting - -| Problem | What to do | -|---------|-----------| -| `docker: command not found` | Docker isn't installed, or Docker Desktop isn't open yet | -| `permission denied: ./setup.sh` | Run `chmod +x setup.sh` first, then try again | -| App shows a connection error | The first build is still running — wait 30 seconds, then refresh | -| Port 3000 or 5432 already in use | Another app is using that port — stop it, or edit the port in `docker-compose.yml` | -| Upload / file errors | Run `docker compose logs minio` — MinIO may still be starting up | -| Need to rotate a secret | Edit `.env`, then run `docker compose up --build` | +Full setup instructions (building from source, updating, troubleshooting, managing your instance) live in **[SELF-HOSTING.md](SELF-HOSTING.md)**. ## Tech Stack @@ -265,8 +94,11 @@ docker compose --profile tools up | Framework | [Nuxt 4](https://nuxt.com) (Vue 3 + Nitro) | | Database | PostgreSQL 16 | | ORM | [Drizzle ORM](https://orm.drizzle.team) + postgres.js | -| Auth | [Better Auth](https://www.better-auth.com) with organization plugin | +| Auth | [Better Auth](https://www.better-auth.com) with organization + SSO + Stripe plugins | | Storage | [MinIO](https://min.io) (S3-compatible) | +| AI | [Vercel AI SDK](https://sdk.vercel.ai) via OpenRouter, or bring your own key (OpenAI-compatible: OpenAI, Anthropic, Google, Ollama, …) | +| Billing | [Stripe](https://stripe.com) | +| Analytics | [PostHog](https://posthog.com) | | Validation | [Zod v4](https://zod.dev) | | Styling | [Tailwind CSS v4](https://tailwindcss.com) | | Icons | [Lucide](https://lucide.dev) (tree-shakeable) | @@ -283,8 +115,9 @@ server/ # Backend (Nitro) api/ # REST API routes (authenticated + public) database/schema/ # Drizzle ORM table definitions database/migrations/ # Generated SQL migrations - utils/ # Auto-imported utilities (db, auth, env, s3) + utils/ # Auto-imported utilities (db, auth, env, s3, billing, ai) plugins/ # Startup plugins (migrations, S3 bucket) +ee/ # Nuxt layer — paid, cloud-only features (separate commercial license) Dockerfile # Multi-stage build for the app container docker-compose.yml # App + Postgres + MinIO (+ optional Adminer) setup.sh # One-time secret generator → writes .env @@ -322,23 +155,18 @@ GH_TOKEN=... ./release.sh v1.4.0 These scripts preserve the former workflows' recipes, but this fork's live publishing/release ownership has not been confirmed. The old Docker build used GitHub-hosted cache storage; the local publisher performs the same build without that Actions-only cache. -Reqcore is designed to run on a single VPS. The reference deployment uses: +### Reference deployment + +The hosted product runs on managed infrastructure. A typical self-hosted production deployment — which is what this fork runs, on a single VPS — uses: | Component | Role | |-----------|------| -| **Hetzner Cloud CX23** | 2 vCPU, 4GB RAM, Ubuntu 24.04 (~€5/mo) | +| **VPS or container host** | 2 vCPU, 4GB RAM recommended as a starting point | | **Caddy** | Reverse proxy with automatic HTTPS | | **Cloudflare** | DNS, DDoS protection, edge SSL (free tier) | | **Docker Compose** | Postgres + MinIO (localhost only) | | **systemd** | Process management with auto-restart | -### Deploy - -```bash -ssh deploy@your-server '~/deploy.sh' -# Pulls latest code, installs, builds, restarts — zero downtime -``` - See [ARCHITECTURE.md](ARCHITECTURE.md) for the full deployment architecture diagram. ## Scripts @@ -361,20 +189,20 @@ Implementation details and setup steps (including Crowdin native GitHub integrat ## Roadmap -Reqcore is actively developed. Here's what's next: +Reqcore is actively developed. Here's what's shipped and what's next: | Status | Milestone | |--------|-----------| -| ✅ Shipped | Jobs, Candidates, Applications, Pipeline, Documents, Dashboard, Public Job Board, Custom Forms | -| 🔨 Building | Resume parsing (PDF → structured data) | -| 🔮 Planned | AI candidate ranking (visible matching logic), team collaboration, email notifications, candidate portal | +| ✅ Shipped | Jobs, Candidates, Applications, Pipeline, Documents, Dashboard, Public Job Board, Custom Forms, Resume Parsing, AI Shortlisting, Billing & Plans, GDPR Retention/Erasure | +| 🔨 Building | Team collaboration (comments, activity log, role-based permissions) | +| 🔮 Planned | Interview scheduling, email notifications, candidate portal | See the full [Roadmap](ROADMAP.md) and [Product Vision](PRODUCT.md). ## Contributing -Reqcore is in early development and contributions are welcome. Check [CONTRIBUTING.md](CONTRIBUTING.md) for development workflow, DCO sign-off requirements, and submission guidelines. +Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening an issue or pull request. -## License +## Licensing & self-hosting -Licensed under the [GNU Affero General Public License v3.0 (AGPL-3.0)](LICENSE). +Reqcore is licensed under the [GNU Affero General Public License v3.0](LICENSE), with the exception of the [`ee/`](ee) directory, which contains paid, cloud-only features under a separate [commercial license](ee/LICENSE). See [SELF-HOSTING.md](SELF-HOSTING.md) for what that means if you run your own instance. diff --git a/ROADMAP.md b/ROADMAP.md index e2971563..2f99fbbc 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -267,12 +267,12 @@ Goal: Teams can work together on hiring decisions. ## Phase 4: Production Readiness -Goal: Ready for real teams to self-host in production. +Goal: Ready for real teams to use in production, whether self-hosted or deployed on managed infrastructure. ### Milestone 13: Hardening -- [x] Production deployment — Railway (managed Nuxt service, Railway PostgreSQL, Railway Storage Buckets) -- [x] HTTPS/TLS — Railway auto-TLS + Cloudflare CDN (Full strict SSL) +- [x] Production deployment — Docker Compose reference stack plus managed-platform support +- [x] HTTPS/TLS — reverse-proxy friendly deployment with Cloudflare/Railway-compatible configuration - [x] DNS + CDN — Cloudflare Free plan with DDoS protection and AI bot blocking - [ ] Backup & restore (Postgres + S3 bucket) - [x] Rate limiting — in-memory sliding window (`server/utils/rateLimit.ts`), applied to public apply endpoint @@ -301,7 +301,7 @@ Goal: Ready for real teams to self-host in production. - [x] Full OG + Twitter Card meta on all public pages (landing, job board, job detail, roadmap, blog) - [x] `noindex` on private pages (auth, onboarding, apply form, confirmation) - [x] ISR route rules — `/jobs/**` (3600s), prerender `/`, `/roadmap`, `/blog/**` -- [x] Landing page H1 + copy optimized for "open source ATS" / "applicant tracking system" keywords +- [x] Landing page H1 + copy optimized for "applicant tracking system" / high-applicant-volume keywords - [x] Blog seed article: "Self-Hosted vs Cloud ATS: Pros, Cons, and When to Switch" - [x] Blog listing + detail pages with dark theme, navigation links - [x] `@tailwindcss/typography` for styled `prose` content rendering diff --git a/SELF-HOSTING.md b/SELF-HOSTING.md index 2810a594..449598ab 100644 --- a/SELF-HOSTING.md +++ b/SELF-HOSTING.md @@ -1,881 +1,187 @@ -# Self-Hosting Reqcore — The Complete Guide +# Self-Hosting Reqcore -Everything you need to deploy, manage, and update your own Reqcore applicant tracking system. No technical background required. +Reqcore is open-source and self-hostable. This is a DIY path, provided best-effort and without support or an SLA — the Reqcore team's own support and uptime commitments apply only to the hosted cloud product at [reqcore.com](https://reqcore.com). The reference path below uses Docker Compose to run the app, PostgreSQL, and S3-compatible object storage together. ---- +Code under [`ee/`](ee) is licensed separately (see [`ee/LICENSE`](ee/LICENSE)) and gates itself behind the same plan checks as the hosted product; without your own billing configured, those features stay locked. -## Table of Contents +> **Windows users:** Open [Git Bash](https://gitforwindows.org) and run all commands there instead of Command Prompt or PowerShell. -1. [What is Self-Hosting?](#what-is-self-hosting) -2. [Why Self-Host Reqcore?](#why-self-host-reqcore) -3. [Requirements](#requirements) -4. [Quick Start — Pre-built Image (Fastest)](#quick-start--pre-built-image-fastest) -5. [Quick Start — Build from Source (5 Minutes)](#quick-start--build-from-source-5-minutes) -6. [Step-by-Step Installation](#step-by-step-installation) -7. [Updating Your Instance](#updating-your-instance) -8. [Backups & Data Safety](#backups--data-safety) -9. [Custom Domain & HTTPS](#custom-domain--https) -10. [Email Configuration](#email-configuration) -11. [Security Best Practices](#security-best-practices) -12. [Feature Flags](#feature-flags) -13. [Monitoring & Health Checks](#monitoring--health-checks) -14. [Troubleshooting](#troubleshooting) -15. [FAQ](#faq) +## Option A — Use the pre-built image (fastest) ---- - -## What is Self-Hosting? - -Self-hosting means running Reqcore on a server you control — your own computer, a rented server, or a cloud virtual machine — instead of using a service managed by someone else. Your recruitment data, candidate documents, and hiring pipeline stay entirely under your control. - -Think of it like the difference between renting an apartment and owning a house. With self-hosting, you hold the keys. Nobody else can access your data, change the terms of service, or shut down the platform on you. - -**What you get:** -- Complete ownership of all candidate data, documents, and hiring records -- No per-seat pricing — unlimited team members at zero marginal cost -- No data leaves your network unless you choose to integrate external services -- Full source code access — you can audit, modify, and extend everything - ---- - -## Why Self-Host Reqcore? - -### Data Sovereignty -Your candidate resumes, interview feedback, and hiring pipeline live on your infrastructure. This matters for organizations with data residency requirements (GDPR, industry regulations) or those who simply want the peace of mind that comes with data ownership. - -### Cost Predictability -A single $5–10/month VPS handles Reqcore for most teams. Compare that to cloud ATS platforms that charge $50–200 per seat per month. For a team of 10, that could mean saving $6,000+ annually. - -### Zero Vendor Lock-in -The database is standard PostgreSQL. Documents are stored in S3-compatible storage (MinIO). If you ever want to switch tools or export your data, standard database and S3 tools work out of the box. - -### Privacy by Default -No analytics, no tracking, no data sharing with third parties. The only telemetry is PostHog analytics, which is disabled by default in self-hosted mode and must be explicitly configured if desired. - ---- - -## Requirements - -### What You Need (The Minimum) - -| Requirement | Details | -|-------------|---------| -| **A computer or server** | Any modern Linux machine, Mac, or Windows PC with WSL2 | -| **Docker Desktop** | Free software that packages Reqcore and its dependencies together | -| **2 GB RAM** | The minimum. 4 GB is comfortable for teams with heavy usage | -| **10 GB disk space** | For the application, database, and uploaded documents | -| **Internet connection** | Only needed for initial setup and pulling updates | - -### Recommended Server Providers (If You Don't Have a Server) - -If you need to rent a server, these providers offer affordable options suitable for Reqcore: - -| Provider | Minimum Plan | Monthly Cost | Notes | -|----------|-------------|--------------|-------| -| **Hetzner** | CX22 (2 vCPU, 4 GB RAM) | ~€4/month | Best value. European data centers. | -| **DigitalOcean** | Basic Droplet (1 vCPU, 2 GB RAM) | $12/month | Beginner-friendly interface. | -| **Vultr** | Cloud Compute (1 vCPU, 2 GB RAM) | $12/month | Global data centers. | -| **Railway** | Hobby Plan | $5/month | One-click deploy. See README for details. | - -All of these providers offer one-click Docker installation when creating a server, which simplifies the setup further. - ---- - -## Quick Start — Pre-built Image (Fastest) - -Use the official pre-built Docker image from GitHub Container Registry. No cloning, no building — just pull and run. - -### Option A — Versioned release bundle (recommended) - -Every [GitHub Release](https://github.com/reqcore-inc/reqcore/releases/latest) ships with a `reqcore-.tar.gz` bundle that contains `setup.sh` and a `docker-compose.production.yml` with the image tag already pinned to that exact version. This is the most reliable way to install or upgrade. +No cloning, no building. Pull the official image and run: ```bash -# 1. Download and extract the latest release bundle -curl -fsSL -o reqcore.tar.gz https://github.com/reqcore-inc/reqcore/releases/latest/download/reqcore-$(curl -fsSL https://api.github.com/repos/reqcore-inc/reqcore/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/^v//').tar.gz -tar -xzf reqcore.tar.gz && cd reqcore-* - -# 2. Generate secure passwords (one-time) -./setup.sh - -# 3. Start everything -docker compose -f docker-compose.production.yml up -d - -# 4. Open your browser -# → http://localhost:3000 -``` - -To upgrade later, download the newer release bundle into a new directory, copy your existing `.env` over, and run `docker compose up -d`. - -### Option B — Pull straight from `main` - -```bash -# 1. Download just the files you need mkdir reqcore && cd reqcore curl -fsSLO https://raw.githubusercontent.com/reqcore-inc/reqcore/main/docker-compose.production.yml curl -fsSLO https://raw.githubusercontent.com/reqcore-inc/reqcore/main/setup.sh chmod +x setup.sh - -# 2. Generate secure passwords (one-time) ./setup.sh - -# 3. Start everything docker compose -f docker-compose.production.yml up -d - -# 4. Open your browser -# → http://localhost:3000 ``` -That's it. Sign up, create your organization, and start hiring. +Open **[http://localhost:3000](http://localhost:3000)** and sign up. That's it. -**Want to pin a specific version?** Edit `docker-compose.production.yml` and replace `latest` with a version tag (e.g., `1.3.0`): +To update: `docker compose -f docker-compose.production.yml pull app && docker compose -f docker-compose.production.yml up -d` -```yaml -app: - image: ghcr.io/reqcore-inc/reqcore:1.3.0 -``` +## Option B — Build from source -### Verifying image authenticity (optional) +### Step 1 — Install Docker -Every published image is signed with [cosign](https://github.com/sigstore/cosign) using GitHub's keyless OIDC. To verify the image you pulled was actually built by the official release workflow: +Docker packages the app, database, and file storage into containers so you don't have to install anything else manually. -```bash -cosign verify ghcr.io/reqcore-inc/reqcore: \ - --certificate-identity-regexp 'https://github.com/reqcore-inc/reqcore/.github/workflows/docker-publish.yml@.*' \ - --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' -``` - -A successful verification confirms the image is unmodified and was produced by the official CI pipeline. - ---- +| Your OS | How to install | +|---------|---------------| +| **Mac** | [Download Docker Desktop for Mac](https://www.docker.com/products/docker-desktop/) → install → open it | +| **Windows** | [Download Docker Desktop for Windows](https://www.docker.com/products/docker-desktop/) → install → open it | +| **Linux** | Follow the [Docker Engine install guide](https://docs.docker.com/engine/install/) for your distro | -## Quick Start — Build from Source (5 Minutes) - -If you prefer to build from source (useful for development or customization): +Once installed, verify Docker is running: ```bash -# 1. Download Reqcore -git clone https://github.com/reqcore-inc/reqcore.git -cd reqcore - -# 2. Generate secure passwords (one-time) -./setup.sh - -# 3. Start everything -docker compose up -d - -# 4. Open your browser -# → http://localhost:3000 -``` - -Sign up, create your organization, and start hiring. - -**Want demo data to explore first?** - -```bash -docker compose exec app npm run db:seed -``` - -Then sign in with `demo@reqcore.com` / `demo1234`. - ---- - -## Step-by-Step Installation - -### Step 1: Install Docker - -Docker packages Reqcore and all its dependencies (database, file storage) into isolated containers. You install Docker once, and everything else is handled automatically. - -**On Ubuntu/Debian (most common server OS):** - -```bash -# Install Docker -curl -fsSL https://get.docker.com | sh - -# Allow your user to run Docker without sudo -sudo usermod -aG docker $USER - -# Log out and back in for the group change to take effect -exit -# Then reconnect to your server +docker --version ``` -**On Mac:** -Download [Docker Desktop for Mac](https://www.docker.com/products/docker-desktop/) and follow the installer. +You should see something like `Docker version 27.x.x`. If you get `command not found`, Docker isn't running yet — open Docker Desktop and try again. -**On Windows:** -Download [Docker Desktop for Windows](https://www.docker.com/products/docker-desktop/). Ensure WSL2 is enabled (Docker Desktop will prompt you if it's not). +### Step 2 — Download Reqcore -**Verify it's working:** - -```bash -docker --version -# Should print something like: Docker version 24.x.x -``` - -### Step 2: Download Reqcore +Clone the repository (this downloads the source code): ```bash git clone https://github.com/reqcore-inc/reqcore.git cd reqcore ``` -If you don't have `git` installed: - -```bash -# Ubuntu/Debian -sudo apt install git - -# Mac (comes with Xcode Command Line Tools) -xcode-select --install -``` +> Don't have `git`? [Download it here](https://git-scm.com/downloads), or [download a ZIP](https://github.com/reqcore-inc/reqcore/archive/refs/heads/main.zip) and unzip it manually. -### Step 3: Generate Secure Credentials +### Step 3 — Generate your secret keys -Reqcore needs database passwords and authentication secrets. The setup script generates cryptographically random values for you: +This creates a `.env` file containing random passwords and secrets. You only run this once. ```bash ./setup.sh ``` -This creates a `.env` file with all necessary configuration. **Never share or commit this file** — it contains your database passwords and authentication secrets. +You'll see: `✅ .env generated with random secrets.` -### Step 4: Start Reqcore +> **Windows CMD / PowerShell?** Run `cp .env.example .env` instead, then open `.env` and replace every placeholder value with a random string of your choice. -```bash -docker compose up -d -``` - -The `-d` flag runs everything in the background. The first startup takes 2–5 minutes as Docker downloads the required images and builds the application. - -**What's happening behind the scenes:** -1. Docker starts a **PostgreSQL 16** database for your data -2. Docker starts **MinIO** (S3-compatible storage) for document uploads -3. Docker builds and starts the **Reqcore application** -4. Database migrations run automatically to create all required tables - -### Step 5: Create Your Account - -Open your browser and navigate to: - -``` -http://localhost:3000 -``` - -If you're running on a remote server, replace `localhost` with your server's IP address (e.g., `http://203.0.113.42:3000`). - -1. Click **Sign Up** -2. Enter your name, email, and a strong password -3. Create your organization (e.g., your company name) -4. You're ready to start posting jobs and tracking candidates - ---- - -## Updating Your Instance - -### Method 1: Update from the UI (Recommended) - -Reqcore includes a built-in update system accessible from the Settings panel. No command line needed. - -1. Sign in to your Reqcore instance -2. Go to **Settings → Updates** -3. The page automatically checks for new versions -4. If an update is available, click **"Create backup first"** (recommended) -5. Click **"Update to vX.Y.Z"** and confirm -6. Wait for the update to complete (usually under 2 minutes) -7. Refresh the page - -The UI shows the progress of each update step and clearly indicates success or failure. Your data is always preserved — database migrations run automatically. - -### Method 2: Update from the Command Line (Pre-built Image) - -If you're using the pre-built image (`docker-compose.production.yml`): +### Step 4 — Start the app ```bash -# Navigate to your Reqcore directory -cd /path/to/reqcore - -# Pull the latest image and restart -docker compose -f docker-compose.production.yml pull app -docker compose -f docker-compose.production.yml up -d +docker compose up ``` -To update to a specific version, edit `docker-compose.production.yml` and change the image tag: - -```yaml -app: - image: ghcr.io/reqcore-inc/reqcore:1.4.0 -``` - -Then run `docker compose -f docker-compose.production.yml up -d`. - -### Method 3: Update from the Command Line (Build from Source) - -If you cloned the repository and build locally: - -```bash -# Navigate to your Reqcore directory -cd /path/to/reqcore +**The very first run takes 3–5 minutes** while Docker builds the app image and downloads dependencies. This is normal — you only wait this long once. Subsequent starts take seconds. -# Pull the latest version -git pull origin main +When you see a line like: -# Rebuild and restart -docker compose up --build -d ``` - -The entire process takes 2–5 minutes depending on your server's speed. There's about 30 seconds of downtime while the new container starts. - -### What Happens During an Update - -1. **Code pull**: The latest version is downloaded from GitHub -2. **Docker rebuild**: A new container image is built with the updated code -3. **Container restart**: The old container is replaced with the new one -4. **Migrations**: Database schema changes are applied automatically -5. **Ready**: The application is available at the same URL - -**Your data is never lost during updates.** The database and uploaded files live in Docker volumes that persist across container rebuilds. - -### Update Notifications - -The Settings → Updates page automatically checks whether a new version is available by comparing your installed version against the latest GitHub release. No data is sent to any external service — only a single API call to GitHub's public releases endpoint. - ---- - -## Backups & Data Safety - -### Automatic Pre-Update Backups - -Before applying any update through the UI, you can click the **"Create backup first"** button. This creates a full PostgreSQL dump that you can restore from if anything goes wrong. - -### Manual Database Backup - -```bash -# Create a backup -docker compose exec db pg_dump -U reqcore reqcore > backup-$(date +%Y%m%d).sql - -# Restore from a backup (⚠️ this replaces all current data) -cat backup-20260315.sql | docker compose exec -T db psql -U reqcore reqcore +app | Listening on http://[::]:3000 ``` -### Automated Daily Backups (Optional) +...the app is ready. -Add this to your server's crontab (`crontab -e`) to create daily backups: +### Step 5 — Open Reqcore -```bash -# Daily backup at 2 AM, keep last 30 days -0 2 * * * cd /path/to/reqcore && docker compose exec -T db pg_dump -U reqcore reqcore > /path/to/backups/reqcore-$(date +\%Y\%m\%d).sql && find /path/to/backups -name "reqcore-*.sql" -mtime +30 -delete -``` +Go to **[http://localhost:3000](http://localhost:3000)** in your browser. -### Document Backups +Click **Sign up** to create your account and first organization. That's it — you're running your own ATS. -Uploaded documents (resumes, cover letters) are stored in MinIO. To back them up: +### Optional: Load demo data -```bash -# Copy MinIO data to a local directory -docker cp reqcore_minio:/data ./minio-backup-$(date +%Y%m%d) -``` - -### Full Instance Backup - -For a complete backup of everything (database + documents + configuration): +Want to explore with pre-filled jobs, candidates, and a pipeline? Open a **new terminal window** while the app is running and run: ```bash -# Stop the instance briefly -docker compose stop - -# Backup Docker volumes -docker run --rm -v reqcore_postgres_data:/data -v $(pwd):/backup alpine tar czf /backup/postgres-backup.tar.gz -C /data . -docker run --rm -v reqcore_minio_data:/data -v $(pwd):/backup alpine tar czf /backup/minio-backup.tar.gz -C /data . - -# Copy your .env file (contains passwords) -cp .env .env.backup - -# Restart -docker compose up -d -``` - ---- - -## Custom Domain & HTTPS - -### Using a Reverse Proxy (Recommended) - -For production deployments, place a reverse proxy (Caddy, Nginx, or Traefik) in front of Reqcore to handle HTTPS certificates automatically. - -**Option A: Caddy (Simplest — Automatic HTTPS)** - -Caddy automatically obtains and renews Let's Encrypt certificates. Install Caddy on your server, then create a `Caddyfile`: - -``` -ats.yourcompany.com { - reverse_proxy localhost:3000 -} +docker compose exec app npm run db:seed ``` -Start Caddy: +Then sign in with: +- **Email:** `demo@reqcore.com` +- **Password:** `demo1234` -```bash -caddy start -``` +## Updating to a new release -That's it. Caddy handles HTTPS certificates automatically. +When a new version of Reqcore is released, follow these steps **in order** to update your instance. Your data is safe — updates never delete the database or your uploaded files. -**Option B: Nginx + Certbot** +#### Pre-built image users ```bash -# Install Nginx and Certbot -sudo apt install nginx certbot python3-certbot-nginx - -# Configure Nginx -sudo tee /etc/nginx/sites-available/reqcore < For Gmail, generate an [App Password](https://support.google.com/accounts/answer/185833) — your regular Gmail password will not work. - -### Option B: Resend - -1. Sign up at [resend.com](https://resend.com) (free tier: 3,000 emails/month) -2. Verify your sending domain -3. Create an API key -4. Add to your `.env` file: - -```bash -RESEND_API_KEY=re_xxxxxxxxxxxx -RESEND_FROM_EMAIL="Reqcore " -``` - -5. Restart: `docker compose up --build -d` - ---- - -## Security Best Practices - -### What's Already Secured For You - -Reqcore ships with security defaults that require no configuration: - -- **All services are localhost-bound** — PostgreSQL, MinIO, and Adminer are never exposed to the internet. Only the application port (3000) is accessible externally. -- **Automatic CSRF protection** via Better Auth -- **Encrypted OAuth tokens** with AES-256-GCM -- **Rate limiting** on sensitive endpoints -- **Security headers** — `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`, `Permissions-Policy` restricting camera/microphone/geolocation -- **File upload validation** — MIME type verification, file size limits, filename sanitization -- **Server-proxied downloads** — uploaded files are never served directly from storage; they pass through the application server, which enforces authentication and authorization -- **Deny-by-default access control** — every API endpoint checks org membership and role permissions - -### Additional Recommendations - -**Use a firewall:** - -```bash -# Ubuntu/Debian — only allow SSH, HTTP, HTTPS -sudo ufw allow 22/tcp -sudo ufw allow 80/tcp -sudo ufw allow 443/tcp -sudo ufw enable -``` - -**Keep Docker updated:** +This rebuilds the app image with the new code, applies any new database migrations automatically on startup, and restarts in the background. The whole process typically takes under a minute. -```bash -sudo apt update && sudo apt upgrade docker-ce docker-ce-cli containerd.io -``` - -**Use strong passwords:** The `setup.sh` script generates cryptographically random passwords. Don't replace them with weak alternatives. - -**Enable automatic security updates:** +**Step 3 — Verify it's running** ```bash -sudo apt install unattended-upgrades -sudo dpkg-reconfigure -plow unattended-upgrades +docker compose logs app --tail 20 ``` ---- - -## OIDC Single Sign-On (SSO) - -Reqcore supports Single Sign-On via any OIDC-compliant identity provider — Keycloak, Authentik, Authelia, Okta, Azure AD, and more. When configured, a "Sign in with SSO" button appears on the login and registration pages. +Look for `Listening on http://[::]:3000`. Then open [http://localhost:3000](http://localhost:3000) — you're on the latest version. -### Why SSO? +> **Something wrong after an update?** Roll back by running `git checkout ` and then `docker compose up --build -d`. -- **Centralized identity** — users sign in once across all internal tools -- **Zero-friction onboarding** — new hires get instant access, leavers are cut off centrally -- **Enterprise security** — MFA, session policies, and brute-force protection managed in one place +> **To find the latest release notes**, check the [CHANGELOG](CHANGELOG.md) or [GitHub Releases](https://github.com/reqcore-inc/reqcore/releases). -### Setup +Updates keep your database volume and uploaded files intact. Always back up the Postgres and MinIO volumes before major upgrades. -**1. Create an OIDC client in your identity provider:** - -| Setting | Value | -|---|---| -| Client type | OpenID Connect (confidential) | -| Client ID | Any name (e.g., `reqcore`) | -| Client authentication | ON (confidential/secret) | -| Valid redirect URI | `https://your-reqcore-domain.com/api/auth/oauth2/callback/oidc` | -| Valid post-logout redirect URI | `https://your-reqcore-domain.com/*` | -| Scopes | `openid`, `email`, `profile` | - -**2. Set environment variables:** +## Managing your instance ```bash -# All three are required to activate SSO -OIDC_CLIENT_ID=reqcore -OIDC_CLIENT_SECRET=your-client-secret-from-provider -OIDC_DISCOVERY_URL=https://keycloak.example.com/realms/master/.well-known/openid-configuration +# Stop the app (your data is kept) +docker compose down -# Optional: customize the button label (default: "SSO") -OIDC_PROVIDER_NAME=Company SSO -``` +# Start it again +docker compose up -**3. Restart Reqcore:** +# Rebuild after pulling new code +docker compose up --build -```bash -docker compose down && docker compose up -d -``` - -The SSO button appears automatically on the sign-in and sign-up pages. - -### Provider-Specific Discovery URLs - -| Provider | Discovery URL format | -|---|---| -| Keycloak | `https://keycloak.example.com/realms/YOUR_REALM/.well-known/openid-configuration` | -| Authentik | `https://authentik.example.com/application/o/YOUR_APP/.well-known/openid-configuration` | -| Authelia | `https://authelia.example.com/.well-known/openid-configuration` | -| Okta | `https://YOUR_ORG.okta.com/.well-known/openid-configuration` | -| Azure AD | `https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0/.well-known/openid-configuration` | - -### Security - -- **PKCE** (Proof Key for Code Exchange) is enabled by default for protection against authorization code interception -- **Issuer validation** (RFC 9207) is enforced to prevent OAuth mix-up attacks -- **OIDC discovery** automatically fetches and validates all provider endpoints -- SSO is **completely opt-in** — it has zero impact when the environment variables are not set - ---- - -## Feature Flags - -Reqcore ships some features behind **feature flags** so they can be tested in production before being released to everyone. The full list of flags lives in [`shared/feature-flags.ts`](shared/feature-flags.ts). - -### How it works for self-hosters - -Every flag has a safe **default value** baked into the code. You get that default automatically — **no PostHog account or external service required**. - -If you want to opt into an experimental feature (or disable a stable one), set an environment variable matching the pattern: - -```bash -FEATURE_FLAG_=true -``` - -Examples: - -```bash -# Enable the new chatbot experience for everyone on this instance -FEATURE_FLAG_CHATBOT_EXPERIENCE=true - -# Force-disable a flag that defaults to on -FEATURE_FLAG_SOMETHING_ELSE=false +# Stop and delete ALL data (irreversible) +docker compose down -v ``` -Restart the container after editing `.env`. Env-var overrides win over any PostHog rollout, so this is the authoritative knob for self-hosters. - -### Resolution order - -1. URL query string (e.g. `?ff_chatbot-experience=true`) — handy for QA -2. Env var override (`FEATURE_FLAG_*`) — what you'll use 99% of the time -3. PostHog rollout — only applies when `POSTHOG_PUBLIC_KEY` is set -4. Registry default from `shared/feature-flags.ts` - -### I want to use PostHog for gradual rollout - -Optional. Set `POSTHOG_PUBLIC_KEY` and `POSTHOG_HOST` in `.env`, then create a flag in your PostHog project with a key matching the registry (e.g. `chatbot-experience`). For server-side flags without per-request HTTP calls, also set `POSTHOG_FEATURE_FLAGS_KEY` to a personal API key with the **Feature Flags: read** scope. - ---- - -## Monitoring & Health Checks - -### Built-in System Health Dashboard - -Navigate to **Settings → Updates** in your Reqcore instance to view: +### What's running -- **Service status** — Real-time health of the application, database, and file storage -- **System resources** — Memory usage, uptime, deployment method -- **Version info** — Current version and available updates -- **Changelog** — What changed in each version +| Service | URL | Description | +|---------|-----|-------------| +| **App** | [localhost:3000](http://localhost:3000) | The Reqcore web UI | +| **MinIO Console** | [localhost:9001](http://localhost:9001) | File storage browser (S3-compatible) | +| **Adminer** | [localhost:8080](http://localhost:8080) | Database browser — only with `--profile tools` | -### Docker Health Checks - -All services include built-in health checks. Check their status: +To enable Adminer (a visual database browser): ```bash -# View service health -docker compose ps - -# View logs for a specific service -docker compose logs app # Application logs -docker compose logs db # Database logs -docker compose logs minio # Storage logs +docker compose --profile tools up +# Open http://localhost:8080 +# System: PostgreSQL | Server: db | Username & Password: from your .env ``` -### Uptime Monitoring (Optional) - -For production instances, consider using a free uptime monitoring service: - -- [UptimeRobot](https://uptimerobot.com) — Free tier: 50 monitors, 5-minute checks -- [Healthchecks.io](https://healthchecks.io) — Free tier: cron job monitoring - -Point the monitor at your Reqcore URL (e.g., `https://ats.yourcompany.com`) and get notified if your instance goes down. - ---- - ## Troubleshooting -### "Cannot connect to the Docker daemon" - -Docker isn't running. Start it: - -```bash -# Linux -sudo systemctl start docker - -# Mac/Windows -# Open Docker Desktop application -``` - -### "Port 3000 is already in use" - -Another application is using port 3000. Either stop that application or change Reqcore's port: - -```bash -# In docker-compose.yml, change the ports line for the app service: -ports: - - "8080:3000" # Access Reqcore on port 8080 instead -``` - -### "Database connection refused" - -The database container might still be starting. Wait 30 seconds and try again: - -```bash -# Check if all services are healthy -docker compose ps - -# Restart everything -docker compose down && docker compose up -d -``` - -### "Permission denied" running setup.sh - -```bash -chmod +x setup.sh -./setup.sh -``` - -### Container keeps restarting - -Check the logs to see what's wrong: - -```bash -docker compose logs app --tail 50 -``` - -Common causes: -- Missing environment variables — re-run `./setup.sh` -- Database not ready yet — wait 30 seconds and check again - -### Update failed - -If an update fails mid-way, your previous version is still running safely. To manually recover: - -```bash -# Check what went wrong -docker compose logs app --tail 100 - -# Rebuild from current state -docker compose up --build -d -``` - -### Need to start fresh - -```bash -# ⚠️ This deletes ALL data (database, uploaded files, configuration) -docker compose down -v -rm .env -./setup.sh -docker compose up -d -``` - ---- - -## FAQ - -### How much does self-hosting cost? - -The software is completely free. Your only costs are server hosting ($5–15/month for most teams) and a domain name (~$12/year, optional). - -### Can I run Reqcore on my laptop? - -Yes. Docker Desktop runs on Mac, Windows, and Linux. Reqcore works fine on a laptop for small teams or evaluation purposes. For production use with a team, a dedicated server is recommended so the system stays online when your laptop is off. - -### How many team members can I have? - -Unlimited. There are no per-seat limits in self-hosted Reqcore. - -### Is my data backed up automatically? - -Not by default. See the [Backups & Data Safety](#backups--data-safety) section for automated backup instructions. The UI provides a one-click backup button before updates. - -### Can I migrate from another ATS? - -Reqcore uses standard PostgreSQL. If you can export your data as CSV or JSON from your current ATS, you can import it using standard database tools. We're working on import wizards for popular ATS platforms. - -### How do I know when an update is available? - -Go to **Settings → Updates** in your Reqcore dashboard. The page automatically checks for new versions and shows you exactly what changed, with a one-click update button. - -### What happens if an update breaks something? - -Updates are designed to be safe — database migrations are tested before release, and the update process is designed to fail gracefully. If something does go wrong: -1. Your previous data is always preserved in Docker volumes -2. You can restore from a backup (create one before updating) -3. You can roll back: `git checkout v1.0.0 && docker compose up --build -d` - -### Can I move my instance to a different server? - -Yes. Back up your data (database dump + MinIO files + `.env`), install Docker on the new server, clone Reqcore, restore your backups, and start the containers. All your data moves with you. - -### Do I need to know Linux/command line? - -For initial setup: basic familiarity with opening a terminal and copying commands is helpful. After that, day-to-day management (including updates) can be done entirely from the web UI through **Settings → Updates**. - -### Can I run multiple instances? - -Yes. Each instance needs its own directory, `.env` file, and ports. Change the port mapping in `docker-compose.yml` (e.g., `8080:3000` for the second instance). - -### How do I get help? - -- **GitHub Issues**: [github.com/reqcore-inc/reqcore/issues](https://github.com/reqcore-inc/reqcore/issues) -- **Discussions**: [github.com/reqcore-inc/reqcore/discussions](https://github.com/reqcore-inc/reqcore/discussions) -- **Documentation**: This guide and the project README - ---- - -## Architecture Overview - -For those interested in what's running under the hood: - -``` -┌─────────────────────────────────────────────────┐ -│ Your Server │ -│ │ -│ ┌────────────┐ ┌──────────┐ ┌─────────────┐ │ -│ │ Reqcore │ │PostgreSQL│ │ MinIO │ │ -│ │ App │ │ 16 │ │ (S3 Storage)│ │ -│ │ :3000 │ │ :5432 │ │ :9000/:9001│ │ -│ └─────┬──────┘ └────┬─────┘ └──────┬──────┘ │ -│ │ │ │ │ -│ └──────────────┴───────────────┘ │ -│ Docker Network (internal) │ -│ │ -│ Only port 3000 is exposed externally │ -└─────────────────────────────────────────────────┘ -``` - -| Component | Purpose | Storage | -|-----------|---------|---------| -| **Reqcore App** | Web application (Nuxt 4, Node.js) | Stateless (rebuilt on update) | -| **PostgreSQL 16** | All application data (jobs, candidates, pipeline) | `postgres_data` Docker volume | -| **MinIO** | Uploaded documents (resumes, cover letters) | `minio_data` Docker volume | - -Data lives in Docker volumes, which persist across container restarts and rebuilds. The application container is stateless and can be rebuilt at any time without data loss. - ---- - -## Summary - -| Task | How | Difficulty | -|------|-----|------------| -| Install | Clone + `./setup.sh` + `docker compose up` | Easy (5 min) | -| Update (UI) | Settings → Updates → Click "Update" | Easy (2 min) | -| Update (CLI) | `git pull` + `docker compose up --build -d` | Easy (2 min) | -| Backup | Settings → Updates → "Create backup" | Easy (1 click) | -| Custom domain | Add reverse proxy (Caddy recommended) | Medium (15 min) | -| Email | Add Resend API key to `.env` | Easy (5 min) | -| Monitor | Settings → Updates → System Health | Built-in | +| Problem | What to do | +|---------|-----------| +| `docker: command not found` | Docker isn't installed, or Docker Desktop isn't open yet | +| `permission denied: ./setup.sh` | Run `chmod +x setup.sh` first, then try again | +| App shows a connection error | The first build is still running — wait 30 seconds, then refresh | +| Port 3000 or 5432 already in use | Another app is using that port — stop it, or edit the port in `docker-compose.yml` | +| Upload / file errors | Run `docker compose logs minio` — MinIO may still be starting up | +| Need to rotate a secret | Edit `.env`, then run `docker compose up --build` | -Self-hosting Reqcore is designed to be straightforward for anyone comfortable with downloading software and opening a web browser. The built-in update system, backup tools, and health dashboard mean you rarely need to touch the command line after initial setup. +For architecture and deployment details, see [ARCHITECTURE.md](ARCHITECTURE.md). diff --git a/app/app.vue b/app/app.vue index 53391da0..3e920499 100644 --- a/app/app.vue +++ b/app/app.vue @@ -3,12 +3,44 @@ const i18nHead = useLocaleHead({ seo: true, }) +// Job listings/detail and branded career pages serve recruiter-authored, +// single-language content under every locale prefix, so their localized +// variants are noindex (see nuxt.config routeRules + the pages' robots meta). +// Strip the auto-generated hreflang alternates on those routes: advertising +// alternates that point at noindex URLs claims translations that don't exist, +// and Google drops hreflang clusters whose members aren't indexable. Every +// other route (marketing, /pricing) keeps its alternates; the canonical link +// and og:locale meta are left untouched. +const route = useRoute() +const isSingleLocaleUgc = computed(() => + /^\/(?:[a-z]{2}\/)?(?:jobs|career)(?:\/|$)/.test(route.path)) +const i18nLinks = computed(() => + isSingleLocaleUgc.value + ? i18nHead.value.link.filter((l) => !('hreflang' in l)) + : i18nHead.value.link) + useHead(() => ({ htmlAttrs: i18nHead.value.htmlAttrs, - link: i18nHead.value.link, + link: i18nLinks.value, meta: i18nHead.value.meta, })) +// Blocking inline script to apply dark mode before first paint (prevents white +// flash). The nonce attribute is required by the nonce-based CSP set in +// server/middleware/csp.ts — without it the script would be blocked by the +// Content Security Policy (CSP). +const _nonce = import.meta.server ? (useRequestEvent()?.context?.nonce ?? '') : '' +useHead({ + script: [ + { + key: 'dark-mode-init', + innerHTML: '(function(){try{var s=localStorage.getItem("reqcore-color-mode");var m=s||(window.matchMedia("(prefers-color-scheme:dark)").matches?"dark":"light");document.documentElement.classList.toggle("dark",m==="dark");document.documentElement.style.colorScheme=m}catch(e){}})()', + tagPosition: 'head', + ...(_nonce ? { nonce: _nonce } : {}), + }, + ], +}) + // Sync Better Auth session → PostHog identity & org group await usePostHogIdentity() diff --git a/app/assets/css/main.css b/app/assets/css/main.css index d75417e6..f0721a2a 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -1,5 +1,7 @@ @import "tailwindcss"; @plugin "@tailwindcss/typography"; +@source "../../../shared/properties.ts"; +@source "../../../ee/app/**/*.{vue,ts}"; /* ───────────────────────────────────────────────────────── Reqcore — Design System @@ -121,6 +123,8 @@ --color-info-800: oklch(44.3% 0.110 241); --color-info-900: oklch(39.1% 0.090 241); --color-info-950: oklch(18.0% 0.035 243); + + } /* ── Base reset & defaults ──────────────────────────────── */ @@ -234,9 +238,15 @@ /* ── Bento card subtle border-glow (Supabase-style) ───── */ .bento-card { + background: oklch(98.5% 0.002 264); + border: 1px solid oklch(93.0% 0.006 264); +} + +.dark .bento-card { background: linear-gradient(180deg, rgba(255,255,255,0.02) 0%, rgba(255,255,255,0) 100%), #0c0c0f; + border: none; } /* ── Tech stack cards (Linear-style premium) ──────────── */ @@ -286,6 +296,10 @@ } .bento-card::before { + content: none; /* hidden in light mode */ +} + +.dark .bento-card::before { content: ''; position: absolute; inset: 0; @@ -306,6 +320,11 @@ } .bento-card:hover { + background: oklch(96.5% 0.004 264); + border-color: oklch(87.0% 0.008 264); +} + +.dark .bento-card:hover { background: linear-gradient(180deg, rgba(255,255,255,0.035) 0%, rgba(255,255,255,0.005) 100%), #0c0c0f; diff --git a/app/components/AiConfigForm.vue b/app/components/AiConfigForm.vue index 54d826f9..f4baf04f 100644 --- a/app/components/AiConfigForm.vue +++ b/app/components/AiConfigForm.vue @@ -44,6 +44,7 @@ interface AiConfigRow { isDefaultChatbot: boolean isDefaultAnalysis: boolean hasApiKey: boolean + source?: 'byok' | 'platform' } const props = defineProps<{ @@ -263,7 +264,7 @@ const badgeLabel = (badge?: ModelInfo['badge']) => {