{"id":34497383,"url":"https://github.com/necdetsanli/do-not-ghost-me","last_synced_at":"2026-01-16T03:28:08.447Z","repository":{"id":328181509,"uuid":"1109769721","full_name":"necdetsanli/do-not-ghost-me","owner":"necdetsanli","description":"Anonymous reports and stats about recruitment ghosting. Next.js + PostgreSQL, privacy-first and open source.","archived":false,"fork":false,"pushed_at":"2025-12-27T05:06:06.000Z","size":1641,"stargazers_count":66,"open_issues_count":17,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-31T17:52:38.499Z","etag":null,"topics":["ghosting","good-first-issue","good-first-pr","hacktoberfest","hiring","jobs","jobsearch","nextjs","postgresql","react","recruitment","typescript"],"latest_commit_sha":null,"homepage":"https://www.donotghostme.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/necdetsanli.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-04T09:01:29.000Z","updated_at":"2025-12-29T19:17:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/necdetsanli/do-not-ghost-me","commit_stats":null,"previous_names":["necdetsanli/do-not-ghost-me"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/necdetsanli/do-not-ghost-me","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/necdetsanli%2Fdo-not-ghost-me","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/necdetsanli%2Fdo-not-ghost-me/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/necdetsanli%2Fdo-not-ghost-me/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/necdetsanli%2Fdo-not-ghost-me/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/necdetsanli","download_url":"https://codeload.github.com/necdetsanli/do-not-ghost-me/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/necdetsanli%2Fdo-not-ghost-me/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28477204,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T03:13:13.607Z","status":"ssl_error","status_checked_at":"2026-01-16T03:11:47.863Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ghosting","good-first-issue","good-first-pr","hacktoberfest","hiring","jobs","jobsearch","nextjs","postgresql","react","recruitment","typescript"],"created_at":"2025-12-24T01:00:50.343Z","updated_at":"2026-01-16T03:28:08.435Z","avatar_url":"https://github.com/necdetsanli.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# Do Not Ghost Me\n\n\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"public/favicon-dark.png\" /\u003e\n  \u003cimg alt=\"Do Not Ghost Me Logo\" src=\"public/favicon-light.png\" /\u003e\n\u003c/picture\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"GitHub package.json version\" src=\"https://img.shields.io/github/package-json/v/necdetsanli/do-not-ghost-me\"\u003e\n  \u003ca href=\"https://www.donotghostme.com\"\u003e\n    \u003cimg alt=\"GitHub deployments\" src=\"https://img.shields.io/github/deployments/necdetsanli/do-not-ghost-me/production\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/necdetsanli/do-not-ghost-me\" \u003e\n    \u003cimg src=\"https://codecov.io/gh/necdetsanli/do-not-ghost-me/branch/main/graph/badge.svg?token=MDWHKIGFDV\"/\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\n    \u003cimg alt=\"License: AGPL-3.0-or-later\" src=\"https://img.shields.io/badge/license-AGPL--3.0--or--later-blue.svg\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/necdetsanli/do-not-ghost-me/pulls\"\u003e\n    \u003cimg alt=\"PRs Welcome\" src=\"https://img.shields.io/badge/PRs-welcome-brightgreen.svg\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/necdetsanli/do-not-ghost-me/issues\"\u003e\n    \u003cimg alt=\"GitHub Issues or Pull Requests\" src=\"https://img.shields.io/github/issues/necdetsanli/do-not-ghost-me\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/necdetsanli/do-not-ghost-me/stargazers\"\u003e\n    \u003cimg alt=\"GitHub Repo stars\" src=\"https://img.shields.io/github/stars/necdetsanli/do-not-ghost-me?style=social\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/necdetsanli/do-not-ghost-me/forks?include=active%2Carchived%2Cinactive%2Cnetwork\u0026page=1\u0026period=2y\u0026sort_by=stargazer_counts\"\u003e\n    \u003cimg alt=\"GitHub forks\" src=\"https://img.shields.io/github/forks/necdetsanli/do-not-ghost-me\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nA privacy-aware way to track and surface ghosting in hiring processes.\n\nThis 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.\n\n\u003e **Note:** This project is community-driven and not affiliated with any company or employer.\n\n---\n\n## Why this exists\n\nJob 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”.\n\n**Do Not Ghost Me** tries to push back against that by:\n\n- Giving candidates an easy way to document _where_ and _how_ they were ghosted.\n- Aggregating reports into statistics that highlight patterns of behaviour.\n- Helping people set realistic expectations before investing effort into a hiring process.\n- Encouraging more respectful hiring practices by making silent drop-offs visible.\n\n---\n\n## What the platform does\n\nFrom a user’s perspective:\n\n- You anonymously submit a **ghosting report** with:\n  - Company name and country\n  - Position category and level\n  - Stage where communication stopped\n  - Optional “days without reply”\n- The platform:\n  - Normalizes company names and deduplicates records\n  - Stores the report in a PostgreSQL database\n  - Uses **only salted hashes of IP addresses** for rate-limiting (never raw IPs)\n  - Exposes only **aggregated statistics** publicly\n\nFrom the UI perspective:\n\n- `/` — **Home**:\n  - Short explanation of the project\n  - A “Submit a report” form\n  - A small stats panel with total reports and “most reported this week”\n- `/companies` — **Ranking view**:\n  - Companies ranked by number of ghosting reports\n  - Filters for country, category, seniority and stage\n- `/about` — **Project context**:\n  - More detail on the problem, goals and privacy approach\n- `/admin` — **Moderation dashboard** (protected):\n  - List of latest reports with status\n  - Actions to flag, soft-delete or hard-delete reports\n- `/api/health` — **Public healthcheck**:\n  - Returns `{ ok: true }` style JSON for “process up”\n  - Does **not** check the database\n  - Rate-limited per IP\n- `/api/public/company-intel` — **Public API (read-only)**:\n  - Used by the browser extension\n  - Rate-limited per IP\n  - Supports **k-anonymity**: optionally returns `{ \"status\": \"insufficient_data\" }` for companies with fewer than `K` ACTIVE reports (configurable via env).\n\n---\n\n## Tech stack \u0026 architecture\n\nThe app is intentionally minimal but production-oriented:\n\n- **Framework:** Next.js App Router (server components + route handlers)\n- **Language:** TypeScript (strict), with shared JSDoc for utilities\n- **Database:** PostgreSQL via Prisma ORM\n- **Styling:** Tailwind CSS, custom design tokens and light/dark themes\n- **UI primitives:** Radix UI based components, wrapped in project-specific `\u003cInput\u003e`, `\u003cSelect\u003e`, `\u003cCard\u003e`, etc.\n- **Theming:** `next-themes` with light / dark / system mode and a custom `\u003cThemeToggle /\u003e`\n- **Testing:**\n  - **Vitest** for unit and integration tests\n  - **Playwright** for end-to-end (E2E) tests\n- **Containerized dev environment:** `.devcontainer` for reproducible local development in VS Code\n- **Docker Compose (optional):** `compose.yaml` for a reproducible local app + Postgres stack without VS Code Dev Containers\n\nSecurity and robustness:\n\n- **Admin area**\n  - Password-based login with CSRF protection\n  - Signed, HttpOnly cookie stored as a short-lived admin session token\n  - Host allow-list (`ADMIN_ALLOWED_HOST`) to avoid accidental exposure\n- **Rate limiting**\n  - Report submission limits are implemented in `src/lib/rateLimit.ts` (DB-backed, strict under concurrency).\n  - Public read-only endpoints use an in-memory per-IP limiter in `src/lib/publicRateLimit.ts`.\n  - IPs are **never stored in raw form** — only salted hashes.\n- **Public endpoints**\n  - `/api/health` is intentionally **public** for uptime checks.\n  - It returns **process up** only (no DB checks).\n  - It is protected with a per-IP **in-memory** rate limit (`src/lib/publicRateLimit.ts`).\n- **Validation**\n  - Zod-based schema for report payloads\n  - Honeypot field to silently drop basic bot submissions\n- **Logging**\n  - Centralized `logInfo`, `logWarn`, `logError` helpers\n  - Structured logs that avoid leaking sensitive data\n- **K-anonymity for extension data**\n  - The public company intel endpoint can enforce a minimum sample size (`K`) before returning aggregated results.\n  - This is controlled via `COMPANY_INTEL_ENFORCE_K_ANONYMITY` and `COMPANY_INTEL_K_ANONYMITY`.\n\n---\n\n## Running the project locally\n\nYou can run the project in three ways:\n\n- **Option A (recommended):** VS Code Dev Container (reproducible dev environment, local Postgres runs inside the container)\n- **Option B:** Docker Compose (app + Postgres in containers, no VS Code required)\n- **Option C:** Run directly on your host (advanced)\n\n### Prerequisites\n\nFor Option A (Dev Container):\n\n- Docker (Desktop, Podman + Docker shim, or equivalent)\n- Visual Studio Code\n- Dev Containers extension (`ms-vscode-remote.remote-containers`)\n\nFor Option B (Docker Compose):\n\n- Docker with `docker compose` support\n\nFor Option C (Host):\n\n- Node.js 24.x\n- npm \u003e= 11.7.0\n- PostgreSQL\n\n### Option A – VS Code Dev Container (recommended)\n\n#### 1. Clone the repository\n\n```bash\ngit clone https://github.com/necdetsanli/do-not-ghost-me.git\ncd do-not-ghost-me\n```\n\n#### 2. Open in the dev container\n\n1. Open the folder in VS Code.\n2. When prompted, choose **“Reopen in Container”**.\n3. Alternatively, use the command palette:\n   `Dev Containers: Reopen in Container`.\n\n#### 3. Dependencies\n\nThe dev container installs dependencies automatically on first create.\nIf you ever need to reinstall manually:\n\n```bash\nnpm ci\nnpm run prisma:generate\n```\n\n\u003e If you see an `npm ci` error about `package.json` and `package-lock.json` being out of sync,\n\u003e run `npm install` once to regenerate the lockfile, commit it, then use `npm ci` again.\n\n#### 4. Configure environment variables\n\nCopy the example file and edit as needed:\n\n```bash\ncp .env.example .env\n```\n\nAt minimum you should set:\n\n- `DATABASE_URL`\n- `ADMIN_PASSWORD`\n- `ADMIN_SESSION_SECRET`\n- `ADMIN_CSRF_SECRET`\n- `ADMIN_ALLOWED_HOST`\n- `RATE_LIMIT_IP_SALT`\n\nOptional (public API / browser extension):\n\n- `COMPANY_INTEL_ENFORCE_K_ANONYMITY` (true/false)\n- `COMPANY_INTEL_K_ANONYMITY` (number, default: 5)\n\nOptional (rate limiting configuration):\n\n- `RATE_LIMIT_COMPANY_SEARCH_MAX_REQUESTS` (number, default: 60 req/min)\n- `RATE_LIMIT_COMPANY_SEARCH_WINDOW_MS` (number, default: 60000ms)\n- `RATE_LIMIT_REPORTS_STATS_MAX_REQUESTS` (number, default: 30 req/min)\n- `RATE_LIMIT_REPORTS_STATS_WINDOW_MS` (number, default: 60000ms)\n\n\u003e Never point `DATABASE_URL` at a production database while developing locally.\n\n#### 5. Set up the local PostgreSQL database\n\nInside the dev container, the helper script:\n\n- Starts the local PostgreSQL service\n- Creates a dev user and database (if missing)\n- Grants privileges\n- Applies Prisma migrations (`prisma migrate deploy`)\n\nRun it from the project root:\n\n```bash\nscripts/dev/setup-db.sh\n```\n\n#### 6. (Optional) Seed dummy data\n\nTo stress-test `/companies` and see realistic distributions of reports:\n\n```bash\nnpm run seed:dummy\n```\n\n#### 7. Start the dev server\n\n```bash\nnpm run dev\n```\n\nThen visit:\n\n- `http://localhost:3000/` – Home \u0026 report form\n- `http://localhost:3000/companies` – Company ranking and filters\n- `http://localhost:3000/about` – Project overview\n- `http://localhost:3000/admin/login` – Admin login (local)\n- `http://localhost:3000/api/health` – Public healthcheck (process up)\n\n#### 8. Admin dashboard (local)\n\nTo access the admin moderation dashboard:\n\n1. Ensure `ADMIN_ALLOWED_HOST` in `.env` matches your local host (e.g. `localhost:3000`).\n2. Ensure `ADMIN_PASSWORD`, `ADMIN_SESSION_SECRET`, and `ADMIN_CSRF_SECRET` are set.\n3. Start the dev server (`npm run dev`).\n4. Visit:\n   - `http://localhost:3000/admin/login` – sign in with `ADMIN_PASSWORD`.\n   - `http://localhost:3000/admin` – once logged in, you’ll see the report table.\n\nThe admin session is stored as a **signed, HttpOnly cookie**. To log out, use the dedicated logout flow:\n\n- Visit `/admin/logout` (or click the logout button in the admin header).\n- This triggers `/api/admin/logout`, which clears the cookie and redirects you back to `/`.\n\n---\n\n### Option B – Docker Compose (app + Postgres)\n\nThis repo includes a `compose.yaml` that starts:\n\n- a local Postgres database\n- the Next.js dev server\n- Prisma client generation and migrations on startup\n\n#### 1. Start the stack\n\n```bash\ndocker compose up --build\n```\n\n#### 2. Verify it is running\n\nIn another terminal:\n\n```bash\ncurl -I http://localhost:3000/\ndocker compose exec db psql -U ghostuser -d donotghostme -c \"select count(*) from \\\"_prisma_migrations\\\";\"\n```\n\n#### 3. Stop (and optionally reset)\n\n```bash\ndocker compose down\n# to wipe volumes (drops local DB data)\ndocker compose down -v\n```\n\n\u003e If you want to customize env values, edit the `compose.yaml` environment block or use an override file like `compose.override.yaml`. Do not commit real secrets.\n\n---\n\n### Option C – Run directly on your host (advanced)\n\nIf you prefer not to use the dev container or Docker Compose:\n\n1. **Install prerequisites**\n   - Node.js 24.x\n   - npm \u003e= 11.6.4\n   - PostgreSQL\n\n2. **Clone the repo and install dependencies**\n\n```bash\ngit clone https://github.com/necdetsanli/do-not-ghost-me.git\ncd do-not-ghost-me\nnpm ci\n```\n\n3. **Create `.env`**\n\n```bash\ncp .env.example .env\n```\n\nSet `DATABASE_URL`, `ADMIN_PASSWORD`, `ADMIN_SESSION_SECRET`, `ADMIN_CSRF_SECRET`, and `ADMIN_ALLOWED_HOST` as in the dev container instructions.\n\n4. **Set up PostgreSQL**\n   - Create a user and database that match your `DATABASE_URL`.\n\nThen, from the project root:\n\n```bash\nnpm run prisma:generate\nnpm run prisma:migrate\n```\n\n5. **(Optional) Seed dummy data**\n\n```bash\nnpm run seed:dummy\n```\n\n6. **Start the dev server**\n\n```bash\nnpm run dev\n```\n\nThe app will be available at `http://localhost:3000/`.\n\n---\n\n## NPM scripts\n\nFor convenience, the project exposes a set of scripts in `package.json`:\n\nCommon workflows:\n\n- **Local development**\n\n  ```bash\n  npm run dev\n  ```\n\n- **One-shot quality gate (local)**\n\n  ```bash\n  npm run verify\n  # =\u003e lint + typecheck + unit/integration tests + e2e tests\n  ```\n\n- **CI pipelines**\n\n  ```bash\n  npm run verify:ci\n  # =\u003e non-watch tests and Playwright runs suitable for CI\n  ```\n\n- **Database migrations**\n\n  ```bash\n  npm run prisma:generate\n  npm run prisma:migrate\n  ```\n\n- **Formatting \u0026 linting**\n\n  ```bash\n  npm run format\n  npm run lint\n  ```\n\n---\n\n## Testing\n\nThe test setup is intentionally close to what you’d expect in a production-grade Next.js app.\n\n### Unit \u0026 integration tests (Vitest)\n\nThe project uses **Vitest** as the primary test runner for unit and integration tests.\n\n- Run the full suite:\n\n  ```bash\n  npm run test\n  ```\n\n- Watch mode during development:\n\n  ```bash\n  npm run test:watch\n  ```\n\n- Coverage report:\n\n  ```bash\n  npm run test:coverage\n  ```\n\n- CI-optimized run (single process):\n\n  ```bash\n  npm run test:ci\n  ```\n\nVitest is used to test:\n\n- Validation and parsing logic in `src/lib/**`.\n- Rate limiting logic and helpers around Prisma queries.\n- Pure utility functions (enums, formatting, URL builders).\n- API route handlers in isolation (using mocks/in-memory DB where appropriate).\n\n### End-to-end tests (Playwright)\n\nFor full stack verification, **Playwright** is used:\n\n- Run all E2E tests:\n\n  ```bash\n  npm run test:e2e\n  ```\n\nE2E coverage is focused on:\n\n- Submitting a report from the home page and seeing success/error states.\n- Top companies listing behaviour with filters and pagination.\n- Admin login/logout flow and basic moderation actions.\n\n\u003e In many cases, you’ll want to run `npm run dev` in one terminal and `npm run test:e2e` in another, or rely on `npm run verify` / `npm run verify:ci` as a single gate.\n\n---\n\n## Contributing\n\nContributions, feedback and ideas are very welcome.\n\n- If you discover a bug or have an idea for improvement, please open an **issue**.\n- For pull requests:\n  - Try to keep changes reasonably focused.\n  - Run `npm run verify` locally before opening the PR.\n  - Include tests where it makes sense (Vitest or Playwright).\n\nFor detailed guidelines (branch naming, commit style, code conventions), see:\n\n- [`CONTRIBUTING.md`](CONTRIBUTING.md)\n\n---\n\n## Changelog\n\nNotable changes, new features and breaking changes are tracked in:\n\n- [`CHANGELOG.md`](CHANGELOG.md)\n\nThis is the best place to see what has changed between releases.\n\n---\n\n## License\n\nDo Not Ghost Me is free software licensed under the **GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)**.\n\n- For the full legal text, see the [`LICENSE`](LICENSE) file.\n\n---\n\n## Privacy\n\nPrivacy and data handling are documented separately and in more depth in:\n\n- [`PRIVACY.md`](PRIVACY.md)\n\nThe short version:\n\n- Reports are stored without creating identifiable user accounts or profiles.\n- The system is interested in **aggregated company behaviour**, not tracking individual candidates.\n- IP addresses are never stored as raw values, only as salted hashes used for rate limiting and abuse detection.\n\nPlease refer to `PRIVACY.md` for the authoritative, up-to-date details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnecdetsanli%2Fdo-not-ghost-me","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnecdetsanli%2Fdo-not-ghost-me","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnecdetsanli%2Fdo-not-ghost-me/lists"}