{"id":27234510,"url":"https://github.com/artkirienko/rewards-app","last_synced_at":"2025-08-22T11:10:45.650Z","repository":{"id":287086782,"uuid":"960069362","full_name":"artkirienko/rewards-app","owner":"artkirienko","description":"📝Test assignment for Senior Software Developer (Ruby on Rails + React) position","archived":false,"fork":false,"pushed_at":"2025-07-14T18:56:03.000Z","size":176,"stargazers_count":1,"open_issues_count":7,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-14T19:36:32.363Z","etag":null,"topics":["coding-challenge","coding-task","coding-test","home-assignment","home-task","home-test","rails","ruby","ruby-on-rails","take-home-assignment","take-home-project","take-home-test","takehome","test","test-assignment"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/artkirienko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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}},"created_at":"2025-04-03T20:07:46.000Z","updated_at":"2025-05-21T04:15:58.000Z","dependencies_parsed_at":"2025-05-07T02:36:53.895Z","dependency_job_id":"c03f84ee-71ee-48f5-a4a5-0c1c22583ab6","html_url":"https://github.com/artkirienko/rewards-app","commit_stats":null,"previous_names":["artkirienko/rewards-app"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/artkirienko/rewards-app","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artkirienko%2Frewards-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artkirienko%2Frewards-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artkirienko%2Frewards-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artkirienko%2Frewards-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/artkirienko","download_url":"https://codeload.github.com/artkirienko/rewards-app/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artkirienko%2Frewards-app/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271628166,"owners_count":24792821,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-22T02:00:08.480Z","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":["coding-challenge","coding-task","coding-test","home-assignment","home-task","home-test","rails","ruby","ruby-on-rails","take-home-assignment","take-home-project","take-home-test","takehome","test","test-assignment"],"created_at":"2025-04-10T15:56:42.955Z","updated_at":"2025-08-22T11:10:45.624Z","avatar_url":"https://github.com/artkirienko.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/artkirienko/rewards-app/issues)\n[![GitHub Actions CI](https://github.com/artkirienko/rewards-app/actions/workflows/ci.yml/badge.svg)](https://github.com/artkirienko/rewards-app/actions/workflows/ci.yml)\n[![SLOC](https://sloc.xyz/github/artkirienko/rewards-app)](https://en.wikipedia.org/wiki/Source_lines_of_code)\n[![Hits-of-Code](https://hitsofcode.com/github/artkirienko/rewards-app?branch=main)](https://hitsofcode.com)\n[![HitCount](https://hitscounter.dev/api/hit?url=https%3A%2F%2Fgithub.com%2Fartkirienko%2Frewards-app\u0026label=Visitors\u0026icon=heart-fill\u0026color=%23d1e7dd)](https://hitscounter.dev)\n\n# Take home challenge Rewards App\n\n### Table of Contents\n\n- [Summary](#summary)\n- [Tech Stack](#tech-stack)\n- [Core Requirements](#core-requirements)\n  - [Backend API](#backend-api)\n  - [Data Persistence](#data-persistence)\n  - [Interface](#interface)\n  - [Documentation](#documentation)\n- [Important Notes](#important-notes)\n- [Run with Dev Containers](#run-with-dev-containers)\n  - [Prerequisites](#prerequisites)\n  - [Steps](#steps)\n- [Run with Docker and Docker-Compose](#run-with-docker-and-docker-compose)\n  - [Prerequisites](#prerequisites-1)\n  - [Steps](#steps-1)\n- [Run](#run)\n  - [Prerequisites](#prerequisites-2)\n  - [Steps](#steps-2)\n  - [Run Tests](#run-tests)\n- [Interact with Backend API via CLI](#interact-with-backend-api-via-command-line-interface-cli)\n- [Codestyle](#codestyle)\n- [TODO](#todo)\n\n### Summary:\n\nThe challenge is to implement a basic rewards redemption web app that allows a user to do the following:\n\n- View their current reward points balance\n- Browse available rewards\n- Redeem rewards using their points\n- See a history of their reward redemptions\n\n### Tech Stack:\n\nYou can use whatever technologies you’d like. I will note that the main technologies at Thanx are React for the front-end, Ruby on Rails for the backend, and MySQL for the database\n\n### Core Requirements:\n\n#### Backend API:\n\nImplement RESTful endpoints for the following:\n\n- Retrieve a user’s current points balance\n- Get a list of available rewards\n- Allow users to redeem a reward\n- Retrieve a user’s redemption history\n\n#### Data Persistence:\n\nUse a database of your choice to store the information as you deem fit\n\n#### Interface:\n\nImplement a simple interface to interact with the backend API. This could be a command-line interface (CLI) or a web-based interface (Preferred if your focus is on the frontend)\n\n#### Documentation:\n\nProvide clear documentation on how to set up and run the application\n\n## **Important Notes**\n\nI use PointsEvent model with polymorphic association to implement rewards and bonuses, so in the `/api/v1/account/history` you could see all points transactions within your account. For now, application is Backend API only\n\n## Run with Dev Containers\n\nYou can run this project using [Dev Containers](https://containers.dev/) to ensure a consistent development environment. Follow the steps below to get started\n\n### Prerequisites\n\n- **[Docker](https://www.docker.com/get-started/)** installed locally on your machine\n- A [compatible editor](https://containers.dev/supporting#editors), such as [Visual Studio Code](https://code.visualstudio.com/) or [Cursor AI Editor](https://cursor.sh/), that supports Dev Containers\n- The **[Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)** installed in your editor. _Note:_ This extension is typically pre-installed in VS Code and Cursor AI Editor\n\n### Steps\n\n1. Clone this repository to your local machine:\n   ```bash\n   git clone https://github.com/artkirienko/rewards-app\n   cd rewards-app\n   ```\n2. Open the project folder in your editor\n3. When prompted, select \"Reopen in Container\" (or use the command palette: Dev Containers: Reopen in Container)\n4. The Dev Container will build and configure the environment based on the provided .devcontainer configuration\n5. Once the dev container setup is complete, open the terminal **within your editor** and run `bin/rails s`\n6. You are ready for [Interacting with Backend API via command-line interface (CLI)](#interact-with-backend-api-via-command-line-interface-cli) 🎉\n7. Run tests: open the terminal **within your editor** and run `RAILS_ENV=test bin/rspec`\n\n### Notes\n\n- Ensure Docker is running before launching the Dev Container\n- The `.devcontainer/devcontainer.json` file in this repository defines the container setup, including dependencies and tools\n- It is recommended to monitor the container setup process by clicking \"Connecting to Dev Container (show log)\" in your editor. This can help troubleshoot any issues that arise\n\n## Run with Docker and Docker-Compose\n\n### Prerequisites\n\n- **[Docker](https://www.docker.com/get-started/)** must be installed locally on your machine\n\n### Steps\n\nRun the following command to build and start the application:\n\n```bash\ndocker-compose -f .devdocker/docker-compose.yml up --build\n```\n\nYou are now ready to [interact with the Backend API via the command-line interface (CLI)](#interact-with-backend-api-via-command-line-interface-cli) 🎉\n\n### Run Tests\n\nTo run tests, open a new terminal window or tab and execute the following command from the project directory:\n\n```bash\ndocker-compose -f .devdocker/docker-compose.yml exec -e RAILS_ENV=test app bin/rspec\n```\n\n## Run\n\n### Prerequisites\n\nEnsure the following are installed on your machine:\n\n- MySQL 8\n- Ruby 3.4.2\n- Rails 8.0.2\n\n### Steps\n\nRun the setup script to initialize the application:\n\n```bash\nbin/setup\n```\n\nYou may need to change database configuration `config/database.yml`\n\nYou are now ready to [interact with the Backend API via the command-line interface (CLI)](#interact-with-backend-api-via-command-line-interface-cli) 🎉\n\n### Run Tests\n\nTo execute tests, run:\n\n```bash\nRAILS_ENV=test bin/rspec\n```\n\n## Interact with Backend API via Command-Line Interface (CLI)\n\nAs soon as you have Rewards App up and running\n\n- ⮕ **Sign in**\n  \u003cdetails\u003e\n  \u003csummary\u003eClick to expand/collapse\u003c/summary\u003e\n\n  _Request:_\n\n  ```bash\n  curl -X POST http://localhost:3000/api/v1/login \\\n    -H \"Content-Type: application/json\" \\\n    -d '{\"user\": {\"email\": \"one@example.com\", \"password\": \"password\"}}'\n  ```\n\n````\n\n_Example Response:_\n\n```json\n{\n  \"user\": {\n    \"id\": 1,\n    \"name\": \"Bryan Rainlord\",\n    \"email_address\": \"one@example.com\",\n    \"points\": 45,\n    \"created_at\": \"2025-04-09T14:31:15.216Z\",\n    \"updated_at\": \"2025-04-09T14:53:59.797Z\"\n  },\n  \"token\": \"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwic2NwIjoidXNlciIsImF1ZCI6bnVsbCwiaWF0IjoxNzQ0MjIyODE2LCJleHAiOjE3NDQzMDkyMTYsImp0aSI6IjRlOWQ5ZWIxLTk2MTAtNGRjMS05NTA1LWVjMzMyMWEzYmQxZiJ9.Xr_W558DnvP9zIbWVwTafgiL7DGvhx3ggUr4Xw_7O58\"\n}\n```\n\n  \u003c/details\u003e\n\n- Copy your token from this response and use for all the next requests\n\n- ⮕ **User information**\n  \u003cdetails\u003e\n  \u003csummary\u003eClick to expand/collapse\u003c/summary\u003e\n\n  _Request:_\n\n  ```bash\n  curl -X GET http://localhost:3000/api/v1/account \\\n    -H \"Authorization: Bearer \u003cYOUR_TOKEN_HERE\u003e\" \\\n    -H \"Content-Type: application/json\"\n  ```\n\n  _Example Response:_\n\n  ```json\n  {\n    \"name\": \"Bryan Rainlord\",\n    \"email_address\": \"one@example.com\"\n  }\n  ```\n\n  \u003c/details\u003e\n\n- ⮕ **Retrieve a user’s current points balance**\n  \u003cdetails\u003e\n  \u003csummary\u003eClick to expand/collapse\u003c/summary\u003e\n\n  _Request:_\n\n  ```bash\n  curl -X GET http://localhost:3000/api/v1/account/balance \\\n    -H \"Authorization: Bearer \u003cYOUR_TOKEN_HERE\u003e\" \\\n    -H \"Content-Type: application/json\"\n  ```\n\n  _Example Response:_\n\n  ```json\n  {\n    \"points\": 395\n  }\n  ```\n\n  \u003c/details\u003e\n\n- ⮕ **Browse available rewards** (no authentication required)\n  \u003cdetails\u003e\n  \u003csummary\u003eClick to expand/collapse\u003c/summary\u003e\n\n  _Request:_\n\n  ```bash\n  curl -X GET http://localhost:3000/api/v1/rewards\n  ```\n\n  _Example Response:_\n\n  ```json\n  [\n    {\n      \"id\": 1,\n      \"name\": \"Tim's Favourite Soup\",\n      \"description\": \"Free Tim's favourite soup, slice of bread included.\",\n      \"points\": 80,\n      \"created_at\": \"2025-04-09T18:19:40.233Z\",\n      \"updated_at\": \"2025-04-09T18:19:40.233Z\"\n    },\n    {\n      \"id\": 2,\n      \"name\": \"1 Baked Pie\",\n      \"description\": \"Small size free baked pie at the cafe.\",\n      \"points\": 50,\n      \"created_at\": \"2025-04-09T18:19:40.315Z\",\n      \"updated_at\": \"2025-04-09T18:19:40.315Z\"\n    },\n    {\n      \"id\": 3,\n      \"name\": \"Free Brewed Coffee or Tea\",\n      \"description\": \"Any size free brewed coffee or tea at the cafe.\",\n      \"points\": 50,\n      \"created_at\": \"2025-04-09T18:19:40.375Z\",\n      \"updated_at\": \"2025-04-09T18:19:40.375Z\"\n    },\n    {\n      \"id\": 4,\n      \"name\": \"Vanilla Cone Pick Up only\",\n      \"description\": \"Regular size free Vanilla Cone Pick Up only at the cafe.\",\n      \"points\": 60,\n      \"created_at\": \"2025-04-09T18:19:40.417Z\",\n      \"updated_at\": \"2025-04-09T18:19:40.417Z\"\n    },\n    {\n      \"id\": 5,\n      \"name\": \"Any Size Latte\",\n      \"description\": \"Any size coffee latte at the cafe.\",\n      \"points\": 70,\n      \"created_at\": \"2025-04-09T18:19:40.460Z\",\n      \"updated_at\": \"2025-04-09T18:19:40.460Z\"\n    },\n    {\n      \"id\": 6,\n      \"name\": \"Breakfast Wrap\",\n      \"description\": \"Free breakfast wrap at the cafe.\",\n      \"points\": 80,\n      \"created_at\": \"2025-04-09T18:19:40.525Z\",\n      \"updated_at\": \"2025-04-09T18:19:40.525Z\"\n    },\n    {\n      \"id\": 7,\n      \"name\": \"Eggs Benedict Meal\",\n      \"description\": \"Free Eggs Benedict meal including any size brewed tea or coffee, salad, and french fries.\",\n      \"points\": 150,\n      \"created_at\": \"2025-04-09T18:19:40.558Z\",\n      \"updated_at\": \"2025-04-09T18:19:40.558Z\"\n    },\n    {\n      \"id\": 8,\n      \"name\": \"Dinner Voucher\",\n      \"description\": \"A voucher for a dinner at the cafe.\",\n      \"points\": 300,\n      \"created_at\": \"2025-04-09T18:19:40.589Z\",\n      \"updated_at\": \"2025-04-09T18:19:40.589Z\"\n    }\n  ]\n  ```\n\n  \u003c/details\u003e\n\n- ⮕ **Show one of the rewards** (no authentication required)\n  \u003cdetails\u003e\n  \u003csummary\u003eClick to expand/collapse\u003c/summary\u003e\n\n  _Request:_\n\n  ```bash\n  curl -X GET http://localhost:3000/api/v1/rewards/3\n  ```\n\n  _Example Response:_\n\n  ```json\n  {\n    \"id\": 3,\n    \"name\": \"Free Brewed Coffee or Tea\",\n    \"description\": \"Any size free brewed coffee or tea at the cafe.\",\n    \"points\": 50,\n    \"created_at\": \"2025-04-09T18:19:40.375Z\",\n    \"updated_at\": \"2025-04-09T18:19:40.375Z\"\n  }\n  ```\n\n  \u003c/details\u003e\n\n- ⮕ **Redeem this reward using points**\n  \u003cdetails\u003e\n  \u003csummary\u003eClick to expand/collapse\u003c/summary\u003e\n\n  _Request:_\n\n  ```bash\n  curl -X POST http://localhost:3000/api/v1/rewards/3/redeem \\\n    -H \"Authorization: Bearer \u003cYOUR_TOKEN_HERE\u003e\" \\\n    -H \"Content-Type: application/json\"\n  ```\n\n  _Example Response:_\n\n  ```json\n  {\n    \"message\": \"Reward redeemed successfully\",\n    \"points_left\": 345,\n    \"points_used\": 50\n  }\n  ```\n\n  \u003c/details\u003e\n\n- ⮕ **Try redeeming most expensive reward several time till not enough points**\n  \u003cdetails\u003e\n  \u003csummary\u003eClick to expand/collapse\u003c/summary\u003e\n\n  _Request:_\n\n  ```bash\n  curl -X POST http://localhost:3000/api/v1/rewards/8/redeem \\\n    -H \"Authorization: Bearer \u003cYOUR_TOKEN_HERE\u003e\" \\\n    -H \"Content-Type: application/json\"\n  ```\n\n  _Example Response (when insufficient points):_\n\n  ```json\n  {\n    \"error\": \"Not enough points\"\n  }\n  ```\n\n  \u003c/details\u003e\n\n- ⮕ **See a history of user's reward redemptions**\n  \u003cdetails\u003e\n  \u003csummary\u003eClick to expand/collapse\u003c/summary\u003e\n\n  _Request:_\n\n  ```bash\n  curl -X GET http://localhost:3000/api/v1/account/history \\\n    -H \"Authorization: Bearer \u003cYOUR_TOKEN_HERE\u003e\" \\\n    -H \"Content-Type: application/json\"\n  ```\n\n  _Example Response:_\n\n  ```json\n  [\n    {\n      \"points\": -300,\n      \"type\": \"Reward\",\n      \"name\": \"Dinner Voucher\",\n      \"description\": \"A voucher for a dinner at the cafe.\",\n      \"created_at\": \"2025-04-09T18:25:25.305Z\"\n    },\n    {\n      \"points\": -50,\n      \"type\": \"Reward\",\n      \"name\": \"Free Brewed Coffee or Tea\",\n      \"description\": \"Any size free brewed coffee or tea at the cafe.\",\n      \"created_at\": \"2025-04-09T18:23:50.981Z\"\n    },\n    {\n      \"points\": -50,\n      \"type\": \"Reward\",\n      \"name\": \"Free Brewed Coffee or Tea\",\n      \"description\": \"Any size free brewed coffee or tea at the cafe.\",\n      \"created_at\": \"2025-04-09T18:19:41.787Z\"\n    },\n    {\n      \"points\": 75,\n      \"type\": \"Bonus\",\n      \"name\": \"Feedback Bonus\",\n      \"description\": \"Bonus for providing feedback.\",\n      \"created_at\": \"2025-04-09T18:19:41.739Z\"\n    },\n    {\n      \"points\": 150,\n      \"type\": \"Bonus\",\n      \"name\": \"Anniversary Bonus\",\n      \"description\": \"Anniversary bonus for users.\",\n      \"created_at\": \"2025-04-09T18:19:41.689Z\"\n    },\n    {\n      \"points\": 200,\n      \"type\": \"Bonus\",\n      \"name\": \"Birthday Bonus\",\n      \"description\": \"Birthday bonus for users.\",\n      \"created_at\": \"2025-04-09T18:19:41.610Z\"\n    },\n    {\n      \"points\": -50,\n      \"type\": \"Reward\",\n      \"name\": \"1 Baked Pie\",\n      \"description\": \"Small size free baked pie at the cafe.\",\n      \"created_at\": \"2025-04-09T18:19:41.552Z\"\n    },\n    {\n      \"points\": 50,\n      \"type\": \"Bonus\",\n      \"name\": \"Referral Bonus\",\n      \"description\": \"Bonus for referring a friend.\",\n      \"created_at\": \"2025-04-09T18:19:41.496Z\"\n    },\n    {\n      \"points\": -80,\n      \"type\": \"Reward\",\n      \"name\": \"Tim's Favourite Soup\",\n      \"description\": \"Free Tim's favourite soup, slice of bread included.\",\n      \"created_at\": \"2025-04-09T18:19:41.393Z\"\n    },\n    {\n      \"points\": 100,\n      \"type\": \"Bonus\",\n      \"name\": \"Sign Up Bonus\",\n      \"description\": \"Bonus for signing up.\",\n      \"created_at\": \"2025-04-09T18:19:41.300Z\"\n    }\n  ]\n  ```\n\n  \u003c/details\u003e\n````\n\n## Codestyle\n\n```bash\nbin/rubocop\n```\n\n## TODO\n\n- Add code coverage using Codecov\n- Update `Devise::JWT::RevocationStrategies::Null` to a more robust strategy\n- Explore modern alternatives to Devise/Devise JWT\n- Implement authorization and an admin dashboard\n- Move some logic from controllers to JSON view templates using tools like Jbuilder, Oj, or Fast JSON\n- Add pagination using Pagy, a modern gem, or implement it from scratch (offset + limit)\n- Develop a TypeScript React.js frontend using either esbuild or Webpacker\n- Add an animation when the user clicks the redeem button\n- Integrate CodeClimate for code quality analysis\n- Save cURL instructions as a Postman suite or create end-to-end tests\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartkirienko%2Frewards-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fartkirienko%2Frewards-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartkirienko%2Frewards-app/lists"}