{"id":50720161,"url":"https://github.com/thebuggybug/scorereviewer","last_synced_at":"2026-06-09T23:01:55.301Z","repository":{"id":362661804,"uuid":"1260243948","full_name":"thebuggybug/ScoreReviewer","owner":"thebuggybug","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-05T10:17:15.000Z","size":43,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-05T12:15:08.917Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thebuggybug.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2026-06-05T09:41:22.000Z","updated_at":"2026-06-05T10:17:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/thebuggybug/ScoreReviewer","commit_stats":null,"previous_names":["thebuggybug/scorereviewer"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/thebuggybug/ScoreReviewer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thebuggybug%2FScoreReviewer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thebuggybug%2FScoreReviewer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thebuggybug%2FScoreReviewer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thebuggybug%2FScoreReviewer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thebuggybug","download_url":"https://codeload.github.com/thebuggybug/ScoreReviewer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thebuggybug%2FScoreReviewer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34129072,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2026-06-09T23:01:54.338Z","updated_at":"2026-06-09T23:01:55.291Z","avatar_url":"https://github.com/thebuggybug.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TechKraft — Candidate Assessment Tool\n\nA full-stack app for TechKraft’s recruitment team to review candidates, submit category scores, and generate mock AI summaries. Built with **FastAPI** (backend) and **React + Vite** (frontend).\n\n| Service  | URL |\n|----------|-----|\n| Frontend | http://localhost:5173 |\n| Backend  | http://localhost:8000 |\n| API docs | http://localhost:8000/docs |\n\n---\n\n## Getting started (after cloning from GitHub)\n\n### Prerequisites\n\n- [Docker](https://docs.docker.com/get-docker/) and Docker Compose (recommended), **or**\n- Python 3.12+, Node.js 20+ (for local run without Docker)\n\n### 1. Clone the repository\n\n```bash\ngit clone https://github.com/thebuggybug/ScoreReviewer\ncd ScoreReviewer\n```\n\n### 2. Configure environment variables\n\nAll secrets live in a `.env` file you create locally.\n\n```bash\ncp .env.example .env\n```\n\nEdit `.env` and set your values :\n\n| Variable | Description |\n|----------|-------------|\n| `DATABASE_URL` | SQLite path. Local default: `sqlite:///./database.db`. Docker overrides via `docker-compose.yml` |\n| `JWT_SECRET` | Secret for signing JWT tokens (**required**) |\n| `JWT_EXPIRE_MINUTES` | Token lifetime in minutes |\n| `ADMIN_EMAIL` / `ADMIN_PASSWORD` | Seeded admin account (first startup) |\n| `REVIEWER_EMAIL` / `REVIEWER_PASSWORD` | Seeded reviewer account (first startup) |\n\n\n\n### 3. Run with Docker (recommended)\n\n```bash\ndocker compose up --build\n```\n\n- **Frontend:** http://localhost:5173  \n- **Backend / Swagger:** http://localhost:8000/docs  \n\nLog in with the `ADMIN_*` or `REVIEWER_*` credentials from your `.env` file.\n\n**Stop:**\n\n```bash\ndocker compose down\n```\n\n**What Docker starts:**\n\n| Container | Port | Role |\n|-----------|------|------|\n| `backend` | 8000 | FastAPI + SQLite (persisted in `backend-data` volume) |\n| `frontend` | 5173 | Vite dev server; proxies `/api` → backend |\n\n### 4. Run locally (without Docker)\n\n**Backend** (port **8000**):\n\n```bash\ncp .env.example .env          # from project root\ncd backend\npython3 -m venv .fastapi\nsource .fastapi/bin/activate\npip install -r requirements.txt\nuvicorn app.main:app --reload --port 8000\n```\n\n`main.py` loads `.env` from the project root via `python-dotenv`.\n\n**Frontend** (port **5173**):\n\n```bash\ncd frontend\nnpm install\nnpm run dev\n```\n\nOpen http://localhost:5173\n\n---\n\n## What the app does\n\n- **Reviewers** log in, filter candidates, submit scores (1–5 per category), trigger a mock AI summary (~2s delay).\n- **Admins** see all reviewers’ scores and can view/edit internal notes.\n- **Registration** always creates a `reviewer` — role is never accepted from the client.\n\nDemo candidate data is loaded from `backend/dummyData.json` on first startup.\n\n---\n\n## Project structure\n\n```\nTECHKRAFT/\n├── docker-compose.yml\n├── .env.example\n├── README.md\n├── backend/\n│   ├── Dockerfile\n│   ├── dummyData.json       # seed candidates + scores\n│   ├── requirements.txt\n│   └── app/\n│       ├── main.py\n│       ├── auth.py\n│       ├── models.py\n│       ├── database.py\n│       ├── seed_data.py\n│       ├── routers/         # auth, candidates\n│       └── services/\n└── frontend/\n    ├── Dockerfile\n    ├── vite.config.js\n    └── src/\n        ├── api/             # fetch client + JWT\n        ├── pages/           # Login, List, Detail\n        └── context/         # AuthProvider\n```\n\n---\n\n## Architecture Decision Record (ADR)\n\n### ADR 1 — FastAPI over Express / Django\n\n**Decision:** Use **FastAPI** required by job description which pushed me to learn and implement\n\n**Trade-off:** Steeper initial learning curve than staying in Node/Express\n\n---\n\n### ADR 2 — SQLite + separate `candidates` / `scores` tables\n\n\n**Decision:** **SQLite** via SQLAlchemy with two main tables: `candidates` (profile, skills JSON, `internal_notes`, `ai_summary`) and `scores` (linked by `candidate_id` and `reviewer_id`). Indexes on `status`, `role_applied`, and `candidate_id`.\n\n**Trade-off:** SQLite is a single file — simple to run and demo, but not best for high concurrency deployments without moving to Postgres. Seed data lives in `dummyData.json` and credentials live only in `.env`.\n\n---\n\n### ADR 3 — JWT auth with role-based access in the service layer\n\n\n**Decision:** **JWT** (email + password login) with `Authorization: Bearer \u003ctoken\u003e`. `get_current_user` dependency on protected routes. RBAC enforced in `candidate_service.py`: filter scores by `reviewer_id`, strip `internal_notes` for non-admins. `reviewer_id` on new scores always comes from the token, never the request body.\n\n**Trade-off:** JWTs are stateless and easy to explain in an interview, but revocation and session invalidation are harder than server-side sessions. Acceptable for this scope; production might add refresh tokens or a blocklist.\n\n---\n\n## Known limitations\n\n- **Automated tests are not included** in this submission. I understand their importance but ran out of time while learning the FastAPI stack.\n- **SSE `/stream`** is a basic one-shot stream, not full real-time score updates.\n- **Docker frontend** runs Vite dev server, not a production nginx build.\n- **AI tools** (e.g. Cursor) were used while building this project — mainly to speed up boilerplate, debug errors, and understand FastAPI patterns I hadn’t used before. I reviewed and tried to understand the code I kept.\n\n---\n\n## Learning reflection\n\nI’m mainly a **frontend developer** with basic **Express** experience. **FastAPI was new to me**, but the job description asked for it, so I went for it anyway. \n\nI used **AI tools** to help me move faster and learn along the way not to skip understanding. Going through FastAPI was hardest parts for me.\n\n**Tests aren’t in this repo yet** — In future learn properly pytest, API tests for login and reviewer score isolation.\n\nIf I had more time, I’d add those tests, and get deep understanding about FASTAPI\n\nI’m eager to keep learning the backend side — this project was my first real step into that.\n\n---\n\n## API overview\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `POST` | `/auth/register` | Register (always `reviewer`) |\n| `POST` | `/auth/login` | Returns JWT |\n| `GET` | `/candidates` | List with filters + offset pagination (default 20, max 50) |\n| `GET` | `/candidates/{id}` | Detail + scores + summary |\n| `POST` | `/candidates/{id}/scores` | Submit a score |\n| `POST` | `/candidates/{id}/summary` | Mock AI summary (2s delay) |\n| `GET` | `/candidates/{id}/stream` | SSE stream of scores (stretch goal) |\n\n### Example API calls (curl)\n\nBackend must be running on http://localhost:8000. Use credentials from your `.env` file.\n\n**1. Login**\n\n```bash\ncurl -s -X POST http://localhost:8000/auth/login \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\":\"\u003cuseReviewerEmail\u003e\",\"password\":\"\u003cpassword\u003e\"}'\n```\n\n**2. List candidates (with filters + pagination)**\n\n```bash\ncurl -s \"http://localhost:8000/candidates?status=new\u0026offset=0\u0026limit=20\" \\\n  -H \"Authorization: Bearer $TOKEN\"\n```\n\n**3. Submit a score**\n\n```bash\ncurl -s -X POST http://localhost:8000/candidates/1/scores \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"category\":\"Technical\",\"score\":4,\"note\":\"Solid skills\"}'\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthebuggybug%2Fscorereviewer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthebuggybug%2Fscorereviewer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthebuggybug%2Fscorereviewer/lists"}