{"id":51088690,"url":"https://github.com/bit-web24/alkye_assignment","last_synced_at":"2026-06-23T23:32:44.630Z","repository":{"id":363494594,"uuid":"1263592405","full_name":"bit-web24/alkye_assignment","owner":"bit-web24","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-09T05:33:15.000Z","size":183,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-09T07:23:58.695Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","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/bit-web24.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-09T05:06:40.000Z","updated_at":"2026-06-09T05:33:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bit-web24/alkye_assignment","commit_stats":null,"previous_names":["bit-web24/alkye_assignment"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/bit-web24/alkye_assignment","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bit-web24%2Falkye_assignment","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bit-web24%2Falkye_assignment/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bit-web24%2Falkye_assignment/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bit-web24%2Falkye_assignment/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bit-web24","download_url":"https://codeload.github.com/bit-web24/alkye_assignment/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bit-web24%2Falkye_assignment/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34711176,"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-23T02:00:07.161Z","response_time":65,"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-23T23:32:43.859Z","updated_at":"2026-06-23T23:32:44.625Z","avatar_url":"https://github.com/bit-web24.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Alkye Assignment — Task Management API\n\nA REST API built with **Rust + Axum + PostgreSQL** that implements user seeding, two-factor email authentication, JWT session management, and task assignment with role-based access control.\n\n---\n\n## Tech Stack\n\n| Layer | Technology |\n|---|---|\n| Language | Rust (edition 2024) |\n| Web framework | Axum 0.8 |\n| Database | PostgreSQL 17 (via Docker) |\n| Database driver | sqlx 0.9 (async, compile-time checked) |\n| Authentication | Two-factor code + JWT (Bearer token) |\n| Environment | dotenvy |\n\n---\n\n## Prerequisites\n\n- [Rust](https://rustup.rs/) — stable toolchain (`rustup update stable`)\n- [Docker + Docker Compose](https://docs.docker.com/compose/install/)\n- `cargo` available in `PATH`\n\n---\n\n## Setup\n\n### 1. Clone the repository\n\n```bash\ngit clone \u003crepo-url\u003e\ncd alkye_assignment\n```\n\n### 2. Configure environment variables\n\nCopy the example environment file and adjust values if needed:\n\n```bash\ncp .env .env.local   # optional — .env already contains working defaults\n```\n\nThe defaults in `.env` work out of the box with the Docker Compose setup:\n\n```env\nPOSTGRES_USER=bittu\nPOSTGRES_PASSWORD=bittu\nPOSTGRES_DB=alkye_assignment\nDATABASE_URL=postgres://bittu:bittu@localhost:5432/alkye_assignment\nJWT_SECRET=local-development-secret-change-me\n```\n\n\u003e **Note:** Change `JWT_SECRET` to a long random string in any non-local environment.\n\n---\n\n## Migration\n\nThe schema is applied automatically when the PostgreSQL container first starts.  \nThe migration file is [`postgres/init.sql`](postgres/init.sql) and is mounted into the container at `docker-entrypoint-initdb.d/`.\n\n### Start the database\n\n```bash\ndocker compose up -d\n```\n\nWait for the healthcheck to pass (usually 5–10 seconds):\n\n```bash\ndocker compose ps        # Status should show \"healthy\"\n```\n\nThe following tables are created:\n\n| Table | Purpose |\n|---|---|\n| `users` | Registered users with roles (`admin` / `user`) |\n| `two_factor_codes` | Time-limited 2FA codes tied to a login challenge |\n| `sessions` | JWT tokens with 24-hour expiry |\n| `email_logs` | Development log of every \"email\" sent (2FA codes) |\n| `tasks` | Tasks with title, description, priority, status, and assignment |\n\nTo reset the database and re-run the migration from scratch:\n\n```bash\ndocker compose down -v   # removes the named volume\ndocker compose up -d\n```\n\n---\n\n## Run\n\n```bash\ncargo run\n```\n\nThe server starts on `http://127.0.0.1:8000`.\n\nFor a faster incremental build during development:\n\n```bash\ncargo build \u0026\u0026 ./target/debug/alkye_assignment\n```\n\n---\n\n## Seed\n\nThe database starts empty. Seed it with two pre-defined users before doing anything else:\n\n```bash\ncurl -s -X POST http://127.0.0.1:8000/seed/users | jq\n```\n\n**Response (201 Created):**\n\n```json\n{\n  \"message\": \"users seeded\",\n  \"users\": [\n    { \"id\": 1, \"name\": \"Admin\",      \"email\": \"admin@example.com\", \"role\": \"admin\" },\n    { \"id\": 2, \"name\": \"James Bond\", \"email\": \"user@example.com\",  \"role\": \"user\"  }\n  ],\n  \"development_credentials\": [\n    { \"email\": \"admin@example.com\", \"password\": \"admin123\"     },\n    { \"email\": \"user@example.com\",  \"password\": \"jamesbond123\" }\n  ]\n}\n```\n\nCalling `/seed/users` a second time returns **409 Conflict** — seeding is intentionally idempotent.\n\n---\n\n## Validation\n\nEvery endpoint enforces the following rules. Violations return a JSON error body with the appropriate HTTP status code.\n\n### `POST /auth/login`\n\n| Field | Rule |\n|---|---|\n| `email` | Required. Matched case-insensitively after trimming whitespace. |\n| `password` | Required. Must match the stored password exactly. |\n\nReturns **401 Unauthorized** if the credentials do not match any user.\n\n### `POST /auth/verify-2fa`\n\n| Field | Rule |\n|---|---|\n| `login_challenge_id` | Required. Must match an active, unexpired challenge. |\n| `code` | Required. Must be the 6-digit code issued during login. |\n\n- Codes expire **5 minutes** after they are issued.\n- A code is single-use; it is marked `used_at` immediately on success.\n- Returns **401 Unauthorized** for any invalid, expired, or already-used combination.\n\n### `POST /tasks`\n\n| Field | Rule |\n|---|---|\n| `title` | Required; non-empty after trimming. |\n| `description` | Optional string. |\n| Authorization | Bearer token required. Caller must have role `admin`. |\n\nReturns **400 Bad Request** for a blank title, **401** for a missing/invalid token, **403 Forbidden** for a non-admin caller.\n\n### `POST /tasks/assign`\n\n| Field | Rule |\n|---|---|\n| `task_id` | Required. Task must exist and have been created by the calling admin. |\n| `user_id` | Required. Target user must exist in the database. |\n| Authorization | Bearer token required. Caller must have role `admin`. |\n\n- Returns **404 Not Found** if the task or the target user does not exist.\n- Returns **403 Forbidden** if the calling admin did not create the task.\n\n### `GET /tasks/view-my-tasks`\n\n| Rule |\n|---|\n| Bearer token required. Returns tasks assigned to the authenticated user. |\n\n### `GET /dev/email-logs/latest`\n\nNo authentication required. Returns the most recently logged 2FA email or **404** if none exist.\n\n---\n\n## API Reference\n\nAll requests and responses use `Content-Type: application/json`.  \nProtected endpoints require `Authorization: Bearer \u003ctoken\u003e` in the request header.\n\n### `POST /seed/users`\n\nSeeds the two development users. Safe to call only once.\n\n---\n\n### `POST /auth/login`\n\n**Request:**\n```json\n{ \"email\": \"admin@example.com\", \"password\": \"admin123\" }\n```\n\n**Response (200):**\n```json\n{\n  \"message\": \"verification code sent\",\n  \"requires_2fa\": true,\n  \"login_challenge_id\": \"challenge-\u003chex-timestamp\u003e-\u003chex-seq\u003e\"\n}\n```\n\n---\n\n### `GET /dev/email-logs/latest`\n\n**Response (200):**\n```json\n{\n  \"to\": \"admin@example.com\",\n  \"subject\": \"Your two-factor authentication code\",\n  \"body\": \"Your verification code is 000001\",\n  \"code\": \"000001\",\n  \"login_challenge_id\": \"challenge-...\",\n  \"created_at\": 1749430800\n}\n```\n\n---\n\n### `POST /auth/verify-2fa`\n\n**Request:**\n```json\n{\n  \"login_challenge_id\": \"challenge-...\",\n  \"code\": \"000001\"\n}\n```\n\n**Response (200):**\n```json\n{ \"token\": \"\u003cjwt\u003e\", \"token_type\": \"Bearer\" }\n```\n\n---\n\n### `POST /tasks`\n\n**Headers:** `Authorization: Bearer \u003ctoken\u003e`\n\n**Request:**\n```json\n{ \"title\": \"Fix login bug\", \"description\": \"Investigate and resolve the login issue\" }\n```\n\n**Response (201):**\n```json\n{\n  \"id\": 1,\n  \"title\": \"Fix login bug\",\n  \"description\": \"Investigate and resolve the login issue\",\n  \"priority\": null,\n  \"status\": \"pending\",\n  \"created_by_id\": 1,\n  \"assigned_to_id\": null,\n  \"created_at\": 1749430800,\n  \"updated_at\": 1749430800\n}\n```\n\n---\n\n### `POST /tasks/assign`\n\n**Headers:** `Authorization: Bearer \u003ctoken\u003e`\n\n**Request:**\n```json\n{ \"task_id\": 1, \"user_id\": 2 }\n```\n\n**Response (200):** Returns the updated task object (same shape as above, with `assigned_to_id` populated).\n\n---\n\n### `GET /tasks/view-my-tasks`\n\n**Headers:** `Authorization: Bearer \u003ctoken\u003e`\n\n**Response (200):** Array of task objects assigned to the authenticated user.\n\n---\n\n## Testing\n\nThere is no automated test suite in this project. The API can be exercised manually using `curl` or any HTTP client (Postman, Bruno, Insomnia, etc.). The complete happy-path flow is:\n\n### Step 1 — Seed users\n\n```bash\ncurl -s -X POST http://127.0.0.1:8000/seed/users | jq\n```\n\n### Step 2 — Login as admin\n\n```bash\ncurl -s -X POST http://127.0.0.1:8000/auth/login \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"email\":\"admin@example.com\",\"password\":\"admin123\"}' | jq\n```\n\nCopy the `login_challenge_id` from the response.\n\n### Step 3 — Read the 2FA code\n\n```bash\ncurl -s http://127.0.0.1:8000/dev/email-logs/latest | jq '.code, .login_challenge_id'\n```\n\n### Step 4 — Verify 2FA and obtain JWT\n\n```bash\ncurl -s -X POST http://127.0.0.1:8000/auth/verify-2fa \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"login_challenge_id\":\"\u003cchallenge_id\u003e\",\"code\":\"\u003ccode\u003e\"}' | jq\n```\n\nExport the token for subsequent requests:\n\n```bash\nexport TOKEN=\"\u003cjwt from above\u003e\"\n```\n\n### Step 5 — Create a task (admin only)\n\n```bash\ncurl -s -X POST http://127.0.0.1:8000/tasks \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"title\":\"Fix login bug\",\"description\":\"Investigate the issue\"}' | jq\n```\n\n### Step 6 — Assign the task\n\n```bash\ncurl -s -X POST http://127.0.0.1:8000/tasks/assign \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"task_id\":1,\"user_id\":2}' | jq\n```\n\n### Step 7 — View tasks as the regular user\n\nRepeat steps 2–4 using `user@example.com` / `jamesbond123` to obtain a user token, then:\n\n```bash\nexport USER_TOKEN=\"\u003cjwt for james bond\u003e\"\n\ncurl -s http://127.0.0.1:8000/tasks/view-my-tasks \\\n  -H \"Authorization: Bearer $USER_TOKEN\" | jq\n```\n\nThe response should contain the task assigned in step 6.\n\n---\n\n## Error Responses\n\nAll errors follow a consistent shape:\n\n```json\n{ \"error\": \"\u003chuman-readable message\u003e\" }\n```\n\n| Status | Meaning |\n|---|---|\n| 400 Bad Request | Missing or invalid request field (e.g. blank task title) |\n| 401 Unauthorized | Missing, expired, or invalid bearer token / 2FA code |\n| 403 Forbidden | Authenticated but insufficient role or ownership |\n| 404 Not Found | Referenced resource does not exist |\n| 409 Conflict | Operation would violate a uniqueness constraint (e.g. re-seeding) |\n| 500 Internal Server Error | Unexpected database or server error |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbit-web24%2Falkye_assignment","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbit-web24%2Falkye_assignment","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbit-web24%2Falkye_assignment/lists"}