A privacy-aware way to track and surface ghosting in hiring processes.
This project collects structured reports about ghosting during hiring processes (e.g. after an interview or take-home task) and aggregates them into statistics by company, country, stage, seniority, and position category. The goal is to make ghosting visible as a pattern rather than a collection of isolated stories.
Note: This project is community-driven and not affiliated with any company or employer.
Job applications, take-home assignments and interviews all cost time and emotional energy. When a company silently disappears, it does not just waste that effort — it also creates uncertainty, self-doubt and stress. Because ghosting is so common, many candidates have come to see it as “just how things are”.
Do Not Ghost Me tries to push back against that by:
- Giving candidates an easy way to document where and how they were ghosted.
- Aggregating reports into statistics that highlight patterns of behaviour.
- Helping people set realistic expectations before investing effort into a hiring process.
- Encouraging more respectful hiring practices by making silent drop-offs visible.
From a user’s perspective:
- You anonymously submit a ghosting report with:
- Company name and country
- Position category and level
- Stage where communication stopped
- Optional “days without reply”
- The platform:
- Normalizes company names and deduplicates records
- Stores the report in a PostgreSQL database
- Uses only salted hashes of IP addresses for rate-limiting (never raw IPs)
- Exposes only aggregated statistics publicly
From the UI perspective:
/— Home:- Short explanation of the project
- A “Submit a report” form
- A small stats panel with total reports and “most reported this week”
/companies— Ranking view:- Companies ranked by number of ghosting reports
- Filters for country, category, seniority and stage
/about— Project context:- More detail on the problem, goals and privacy approach
/admin— Moderation dashboard (protected):- List of latest reports with status
- Actions to flag, soft-delete or hard-delete reports
/api/health— Public healthcheck:- Returns
{ ok: true }style JSON for “process up” - Does not check the database
- Rate-limited per IP
- Returns
/api/public/company-intel— Public API (read-only):- Used by the browser extension
- Rate-limited per IP
- Supports k-anonymity: optionally returns
{ "status": "insufficient_data" }for companies with fewer thanKACTIVE reports (configurable via env).
The app is intentionally minimal but production-oriented:
- Framework: Next.js App Router (server components + route handlers)
- Language: TypeScript (strict), with shared JSDoc for utilities
- Database: PostgreSQL via Prisma ORM
- Styling: Tailwind CSS, custom design tokens and light/dark themes
- UI primitives: Radix UI based components, wrapped in project-specific
<Input>,<Select>,<Card>, etc. - Theming:
next-themeswith light / dark / system mode and a custom<ThemeToggle /> - Testing:
- Vitest for unit and integration tests
- Playwright for end-to-end (E2E) tests
- Containerized dev environment:
.devcontainerfor reproducible local development in VS Code - Docker Compose (optional):
compose.yamlfor a reproducible local app + Postgres stack without VS Code Dev Containers
Security and robustness:
- Admin area
- Password-based login with CSRF protection
- Signed, HttpOnly cookie stored as a short-lived admin session token
- Host allow-list (
ADMIN_ALLOWED_HOST) to avoid accidental exposure
- Rate limiting
- Report submission limits are implemented in
src/lib/rateLimit.ts(DB-backed, strict under concurrency). - Public read-only endpoints use an in-memory per-IP limiter in
src/lib/publicRateLimit.ts. - IPs are never stored in raw form — only salted hashes.
- Report submission limits are implemented in
- Public endpoints
/api/healthis intentionally public for uptime checks.- It returns process up only (no DB checks).
- It is protected with a per-IP in-memory rate limit (
src/lib/publicRateLimit.ts).
- Validation
- Zod-based schema for report payloads
- Honeypot field to silently drop basic bot submissions
- Logging
- Centralized
logInfo,logWarn,logErrorhelpers - Structured logs that avoid leaking sensitive data
- Centralized
- K-anonymity for extension data
- The public company intel endpoint can enforce a minimum sample size (
K) before returning aggregated results. - This is controlled via
COMPANY_INTEL_ENFORCE_K_ANONYMITYandCOMPANY_INTEL_K_ANONYMITY.
- The public company intel endpoint can enforce a minimum sample size (
You can run the project in three ways:
- Option A (recommended): VS Code Dev Container (reproducible dev environment, local Postgres runs inside the container)
- Option B: Docker Compose (app + Postgres in containers, no VS Code required)
- Option C: Run directly on your host (advanced)
For Option A (Dev Container):
- Docker (Desktop, Podman + Docker shim, or equivalent)
- Visual Studio Code
- Dev Containers extension (
ms-vscode-remote.remote-containers)
For Option B (Docker Compose):
- Docker with
docker composesupport
For Option C (Host):
- Node.js 24.x
- npm >= 11.7.0
- PostgreSQL
git clone https://github.com/necdetsanli/do-not-ghost-me.git
cd do-not-ghost-me- Open the folder in VS Code.
- When prompted, choose “Reopen in Container”.
- Alternatively, use the command palette:
Dev Containers: Reopen in Container.
The dev container installs dependencies automatically on first create. If you ever need to reinstall manually:
npm ci
npm run prisma:generateIf you see an
npm cierror aboutpackage.jsonandpackage-lock.jsonbeing out of sync, runnpm installonce to regenerate the lockfile, commit it, then usenpm ciagain.
Copy the example file and edit as needed:
cp .env.example .envAt minimum you should set:
DATABASE_URLADMIN_PASSWORDADMIN_SESSION_SECRETADMIN_CSRF_SECRETADMIN_ALLOWED_HOSTRATE_LIMIT_IP_SALT
Optional (public API / browser extension):
COMPANY_INTEL_ENFORCE_K_ANONYMITY(true/false)COMPANY_INTEL_K_ANONYMITY(number, default: 5)
Never point
DATABASE_URLat a production database while developing locally.
Inside the dev container, the helper script:
- Starts the local PostgreSQL service
- Creates a dev user and database (if missing)
- Grants privileges
- Applies Prisma migrations (
prisma migrate deploy)
Run it from the project root:
scripts/dev/setup-db.shTo stress-test /companies and see realistic distributions of reports:
npm run seed:dummynpm run devThen visit:
http://localhost:3000/– Home & report formhttp://localhost:3000/companies– Company ranking and filtershttp://localhost:3000/about– Project overviewhttp://localhost:3000/admin/login– Admin login (local)http://localhost:3000/api/health– Public healthcheck (process up)
To access the admin moderation dashboard:
- Ensure
ADMIN_ALLOWED_HOSTin.envmatches your local host (e.g.localhost:3000). - Ensure
ADMIN_PASSWORD,ADMIN_SESSION_SECRET, andADMIN_CSRF_SECRETare set. - Start the dev server (
npm run dev). - Visit:
http://localhost:3000/admin/login– sign in withADMIN_PASSWORD.http://localhost:3000/admin– once logged in, you’ll see the report table.
The admin session is stored as a signed, HttpOnly cookie. To log out, use the dedicated logout flow:
- Visit
/admin/logout(or click the logout button in the admin header). - This triggers
/api/admin/logout, which clears the cookie and redirects you back to/.
This repo includes a compose.yaml that starts:
- a local Postgres database
- the Next.js dev server
- Prisma client generation and migrations on startup
docker compose up --buildIn another terminal:
curl -I http://localhost:3000/
docker compose exec db psql -U ghostuser -d donotghostme -c "select count(*) from \"_prisma_migrations\";"docker compose down
# to wipe volumes (drops local DB data)
docker compose down -vIf you want to customize env values, edit the
compose.yamlenvironment block or use an override file likecompose.override.yaml. Do not commit real secrets.
If you prefer not to use the dev container or Docker Compose:
-
Install prerequisites
- Node.js 24.x
- npm >= 11.6.4
- PostgreSQL
-
Clone the repo and install dependencies
git clone https://github.com/necdetsanli/do-not-ghost-me.git
cd do-not-ghost-me
npm ci- Create
.env
cp .env.example .envSet DATABASE_URL, ADMIN_PASSWORD, ADMIN_SESSION_SECRET, ADMIN_CSRF_SECRET, and ADMIN_ALLOWED_HOST as in the dev container instructions.
- Set up PostgreSQL
- Create a user and database that match your
DATABASE_URL.
- Create a user and database that match your
Then, from the project root:
npm run prisma:generate
npm run prisma:migrate- (Optional) Seed dummy data
npm run seed:dummy- Start the dev server
npm run devThe app will be available at http://localhost:3000/.
For convenience, the project exposes a set of scripts in package.json:
Common workflows:
-
Local development
npm run dev
-
One-shot quality gate (local)
npm run verify # => lint + typecheck + unit/integration tests + e2e tests -
CI pipelines
npm run verify:ci # => non-watch tests and Playwright runs suitable for CI -
Database migrations
npm run prisma:generate npm run prisma:migrate
-
Formatting & linting
npm run format npm run lint
The test setup is intentionally close to what you’d expect in a production-grade Next.js app.
The project uses Vitest as the primary test runner for unit and integration tests.
-
Run the full suite:
npm run test -
Watch mode during development:
npm run test:watch
-
Coverage report:
npm run test:coverage
-
CI-optimized run (single process):
npm run test:ci
Vitest is used to test:
- Validation and parsing logic in
src/lib/**. - Rate limiting logic and helpers around Prisma queries.
- Pure utility functions (enums, formatting, URL builders).
- API route handlers in isolation (using mocks/in-memory DB where appropriate).
For full stack verification, Playwright is used:
-
Run all E2E tests:
npm run test:e2e
E2E coverage is focused on:
- Submitting a report from the home page and seeing success/error states.
- Top companies listing behaviour with filters and pagination.
- Admin login/logout flow and basic moderation actions.
In many cases, you’ll want to run
npm run devin one terminal andnpm run test:e2ein another, or rely onnpm run verify/npm run verify:cias a single gate.
Contributions, feedback and ideas are very welcome.
- If you discover a bug or have an idea for improvement, please open an issue.
- For pull requests:
- Try to keep changes reasonably focused.
- Run
npm run verifylocally before opening the PR. - Include tests where it makes sense (Vitest or Playwright).
For detailed guidelines (branch naming, commit style, code conventions), see:
Notable changes, new features and breaking changes are tracked in:
This is the best place to see what has changed between releases.
Do Not Ghost Me is free software licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later).
- For the full legal text, see the
LICENSEfile.
Privacy and data handling are documented separately and in more depth in:
The short version:
- Reports are stored without creating identifiable user accounts or profiles.
- The system is interested in aggregated company behaviour, not tracking individual candidates.
- IP addresses are never stored as raw values, only as salted hashes used for rate limiting and abuse detection.
Please refer to PRIVACY.md for the authoritative, up-to-date details.