Skip to content

Commit

Permalink
intial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
yathishram committed Jul 14, 2024
0 parents commit 4b7622c
Show file tree
Hide file tree
Showing 34 changed files with 12,127 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
.DS_STORE
database.sqlite3
98 changes: 98 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# DEEL BACKEND TASK

💫 Welcome! 🎉

This backend exercise involves building a Node.js/Express.js app that will serve a REST API. We imagine you should spend around 3 hours at implement this feature.

## Data Models

> **All models are defined in src/model.js**
### Profile

A profile can be either a `client` or a `contractor`.
clients create contracts with contractors. contractor does jobs for clients and get paid.
Each profile has a balance property.

### Contract

A contract between and client and a contractor.
Contracts have 3 statuses, `new`, `in_progress`, `terminated`. contracts are considered active only when in status `in_progress`
Contracts group jobs within them.

### Job

contractor get paid for jobs by clients under a certain contract.

## Getting Set Up

The exercise requires [Node.js](https://nodejs.org/en/) to be installed. We recommend using the LTS version.

1. Start by creating a local repository for this folder.

1. In the repo root directory, run `npm install` to gather all dependencies.

1. Next, `npm run seed` will seed the local SQLite database. **Warning: This will drop the database if it exists**. The database lives in a local file `database.sqlite3`.

1. Then run `npm start` which should start both the server and the React client.

❗️ **Make sure you commit all changes to the master branch!**

## Technical Notes

- The server is running with [nodemon](https://nodemon.io/) which will automatically restart for you when you modify and save a file.

- The database provider is SQLite, which will store data in a file local to your repository called `database.sqlite3`. The ORM [Sequelize](http://docs.sequelizejs.com/) is on top of it. You should only have to interact with Sequelize - **please spend some time reading sequelize documentation before starting the exercise.**

- To authenticate users use the `getProfile` middleware that is located under src/middleware/getProfile.js. users are authenticated by passing `profile_id` in the request header. after a user is authenticated his profile will be available under `req.profile`. make sure only users that are on the contract can access their contracts.
- The server is running on port 3001.

## APIs To Implement

Below is a list of the required API's for the application.

1. **_GET_** `/contracts/:id` - It should return the contract only if it belongs to the profile calling. - Fixed - Can make it better to include client and contractor details.

2. **_GET_** `/contracts` - Returns a list of contracts belonging to a user (client or contractor), the list should only contain non terminated contracts. - Done

3. **_GET_** `/jobs/unpaid` - Get all unpaid jobs for a user (**_either_** a client or contractor), for **_active contracts only_**. - Done

4. **_POST_** `/jobs/:job_id/pay` - Pay for a job, a client can only pay if his balance >= the amount to pay. The amount should be moved from the client's balance to the contractor balance. - Done

5. **_POST_** `/balances/deposit/:userId` - Deposits money into the the the balance of a client, a client can't deposit more than 25% his total of jobs to pay. (at the deposit moment) - Done

6. **_GET_** `/admin/best-profession?start=<date>&end=<date>` - Returns the profession that earned the most money (sum of jobs paid) for any contactor that worked in the query time range.

7. **_GET_** `/admin/best-clients?start=<date>&end=<date>&limit=<integer>` - returns the clients the paid the most for jobs in the query time period. limit query parameter should be applied, default limit is 2.

```
[
{
"id": 1,
"fullName": "Reece Moyer",
"paid" : 100.3
},
{
"id": 200,
"fullName": "Debora Martin",
"paid" : 99
},
{
"id": 22,
"fullName": "Debora Martin",
"paid" : 21
}
]
```

## Going Above and Beyond the Requirements

Given the time expectations of this exercise, we don't expect anyone to submit anything super fancy, but if you find yourself with extra time, any extra credit item(s) that showcase your unique strengths would be awesome! 🙌

It would be great for example if you'd write some unit test / simple frontend demostrating calls to your fresh APIs.

## Submitting the Assignment

When you have finished the assignment, zip your repo (make sure to include .git folder) and send us the zip.

Thank you and good luck! 🙏
56 changes: 56 additions & 0 deletions README_SUBMISSION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# DEEL BACKEND TASK

Time taken to finish: 2hours approx

## Getting Set Up

1. Start by creating a local repository for this folder.

2. In the repo root directory, run `npm install` to gather all dependencies.

3. Next, `npm run seed` will seed the local SQLite database. **Warning: This will drop the database if it exists**. The database lives in a local file `database.sqlite3`.

4. Then run `npm start` which should start both the server.

5. Alternatively, you can run `npm run test` to run the tests which covers all the APIs.

## APIs Implemented

Below is a list of the required API's for the application.

1. **_GET_** `/contracts/:id` - It should return the contract only if it belongs to the profile calling. -
Status: Fixed.
Note: Can make it better to include client and contractor details in response.

2. **_GET_** `/contracts` - Returns a list of contracts belonging to a user (client or contractor), the list should only contain non terminated contracts.|
Status: Done.

3. **_GET_** `/jobs/unpaid` - Get all unpaid jobs for a user (**_either_** a client or contractor), for **_active contracts only_**.
Status: Done.

4. **_POST_** `/jobs/:job_id/pay` - Pay for a job, a client can only pay if his balance >= the amount to pay. The amount should be moved from the client's balance to the contractor balance.
Status: Done.
Note: Uses the transaction lock to ensure that the balance is updated correctly.

5. **_POST_** `/balances/deposit/:userId` - Deposits money into the the the balance of a client, a client can't deposit more than 25% his total of jobs to pay. (at the deposit moment)
Status: Done.
Note: Uses the transaction lock to ensure that the balance is updated correctly.

6. **_GET_** `/admin/best-profession?start=<date>&end=<date>` - Returns the profession that earned the most money (sum of jobs paid) for any contactor that worked in the query time range.
Status: Done.

7. **_GET_** `/admin/best-clients?start=<date>&end=<date>&limit=<integer>` - returns the clients the paid the most for jobs in the query time period. limit query parameter should be applied, default limit is 2.
Status: Done.

## Going Above and Beyond the Requirements

1. Could have added docker based testing for making the testing env different from the actual db to keep the data clean.
2. Could have added more tests for edge cases
3. Could have added more validations for the APIs
4. Could have added more error handling for the APIs - Like a global error handler for both db and api errors.

## Reach out to me if you have any questions or need any clarifications

<[email protected]>

## See you on the other side! 🚀
221 changes: 221 additions & 0 deletions coverage/clover.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1720971740051" clover="3.2.0">
<project timestamp="1720971740051" name="All files">
<metrics statements="188" coveredstatements="154" conditionals="69" coveredconditionals="48" methods="15" coveredmethods="13" elements="272" coveredelements="215" complexity="0" loc="188" ncloc="188" packages="4" files="5" classes="5"/>
<package name="src">
<metrics statements="149" coveredstatements="126" conditionals="41" coveredconditionals="35" methods="9" coveredmethods="9"/>
<file name="app.js" path="/Users/yathishr/Desktop/personal/hometask-be-template-master/src/app.js">
<metrics statements="137" coveredstatements="114" conditionals="41" coveredconditionals="35" methods="9" coveredmethods="9"/>
<line num="1" count="1" type="stmt"/>
<line num="2" count="1" type="stmt"/>
<line num="3" count="1" type="stmt"/>
<line num="4" count="1" type="stmt"/>
<line num="5" count="1" type="stmt"/>
<line num="6" count="1" type="stmt"/>
<line num="7" count="1" type="stmt"/>
<line num="8" count="1" type="stmt"/>
<line num="9" count="1" type="stmt"/>
<line num="10" count="1" type="stmt"/>
<line num="11" count="1" type="stmt"/>
<line num="20" count="1" type="stmt"/>
<line num="22" count="3" type="stmt"/>
<line num="24" count="3" type="stmt"/>
<line num="26" count="3" type="stmt"/>
<line num="29" count="3" type="stmt"/>
<line num="40" count="3" type="cond" truecount="2" falsecount="0"/>
<line num="43" count="2" type="stmt"/>
<line num="61" count="0" type="stmt"/>
<line num="62" count="0" type="stmt"/>
<line num="71" count="1" type="stmt"/>
<line num="73" count="3" type="stmt"/>
<line num="75" count="3" type="stmt"/>
<line num="77" count="3" type="stmt"/>
<line num="91" count="3" type="stmt"/>
<line num="95" count="0" type="stmt"/>
<line num="96" count="0" type="stmt"/>
<line num="108" count="1" type="stmt"/>
<line num="109" count="6" type="stmt"/>
<line num="111" count="6" type="stmt"/>
<line num="112" count="6" type="stmt"/>
<line num="133" count="6" type="stmt"/>
<line num="135" count="0" type="stmt"/>
<line num="136" count="0" type="stmt"/>
<line num="145" count="1" type="stmt"/>
<line num="146" count="6" type="stmt"/>
<line num="147" count="6" type="stmt"/>
<line num="150" count="6" type="cond" truecount="2" falsecount="0"/>
<line num="151" count="1" type="stmt"/>
<line num="156" count="5" type="stmt"/>
<line num="158" count="5" type="stmt"/>
<line num="160" count="5" type="stmt"/>
<line num="174" count="5" type="cond" truecount="2" falsecount="0"/>
<line num="175" count="1" type="stmt"/>
<line num="176" count="1" type="stmt"/>
<line num="181" count="4" type="cond" truecount="2" falsecount="0"/>
<line num="182" count="1" type="stmt"/>
<line num="183" count="1" type="stmt"/>
<line num="187" count="3" type="cond" truecount="2" falsecount="0"/>
<line num="188" count="1" type="stmt"/>
<line num="189" count="1" type="stmt"/>
<line num="196" count="2" type="cond" truecount="2" falsecount="0"/>
<line num="197" count="1" type="stmt"/>
<line num="198" count="1" type="stmt"/>
<line num="204" count="1" type="stmt"/>
<line num="205" count="1" type="stmt"/>
<line num="206" count="1" type="stmt"/>
<line num="207" count="1" type="stmt"/>
<line num="210" count="1" type="stmt"/>
<line num="211" count="1" type="stmt"/>
<line num="212" count="1" type="stmt"/>
<line num="214" count="1" type="stmt"/>
<line num="216" count="1" type="stmt"/>
<line num="219" count="0" type="stmt"/>
<line num="220" count="0" type="stmt"/>
<line num="221" count="0" type="stmt"/>
<line num="234" count="1" type="stmt"/>
<line num="235" count="5" type="stmt"/>
<line num="236" count="5" type="stmt"/>
<line num="238" count="5" type="cond" truecount="2" falsecount="0"/>
<line num="239" count="1" type="stmt"/>
<line num="244" count="4" type="stmt"/>
<line num="246" count="4" type="stmt"/>
<line num="248" count="4" type="stmt"/>
<line num="250" count="4" type="stmt"/>
<line num="255" count="4" type="cond" truecount="1" falsecount="1"/>
<line num="256" count="0" type="stmt"/>
<line num="257" count="0" type="stmt"/>
<line num="260" count="4" type="cond" truecount="2" falsecount="0"/>
<line num="261" count="1" type="stmt"/>
<line num="262" count="1" type="stmt"/>
<line num="265" count="3" type="cond" truecount="2" falsecount="0"/>
<line num="266" count="1" type="stmt"/>
<line num="267" count="1" type="stmt"/>
<line num="271" count="2" type="stmt"/>
<line num="285" count="2" type="stmt"/>
<line num="288" count="2" type="cond" truecount="2" falsecount="0"/>
<line num="289" count="1" type="stmt"/>
<line num="290" count="1" type="stmt"/>
<line num="298" count="1" type="stmt"/>
<line num="299" count="1" type="stmt"/>
<line num="302" count="1" type="stmt"/>
<line num="304" count="1" type="stmt"/>
<line num="306" count="0" type="stmt"/>
<line num="307" count="0" type="stmt"/>
<line num="308" count="0" type="stmt"/>
<line num="322" count="1" type="stmt"/>
<line num="323" count="3" type="stmt"/>
<line num="326" count="3" type="cond" truecount="1" falsecount="1"/>
<line num="329" count="3" type="cond" truecount="2" falsecount="0"/>
<line num="332" count="3" type="cond" truecount="2" falsecount="0"/>
<line num="333" count="1" type="stmt"/>
<line num="338" count="2" type="stmt"/>
<line num="340" count="2" type="stmt"/>
<line num="344" count="2" type="stmt"/>
<line num="374" count="2" type="cond" truecount="1" falsecount="1"/>
<line num="375" count="0" type="stmt"/>
<line num="382" count="2" type="stmt"/>
<line num="384" count="2" type="stmt"/>
<line num="389" count="0" type="stmt"/>
<line num="390" count="0" type="stmt"/>
<line num="399" count="1" type="stmt"/>
<line num="400" count="3" type="cond" truecount="1" falsecount="0"/>
<line num="402" count="3" type="cond" truecount="1" falsecount="1"/>
<line num="405" count="3" type="cond" truecount="2" falsecount="0"/>
<line num="408" count="3" type="cond" truecount="2" falsecount="0"/>
<line num="409" count="1" type="stmt"/>
<line num="414" count="2" type="stmt"/>
<line num="416" count="2" type="stmt"/>
<line num="417" count="2" type="stmt"/>
<line num="449" count="2" type="cond" truecount="1" falsecount="1"/>
<line num="450" count="0" type="stmt"/>
<line num="455" count="2" type="stmt"/>
<line num="457" count="6" type="stmt"/>
<line num="465" count="0" type="stmt"/>
<line num="466" count="0" type="stmt"/>
<line num="477" count="1" type="stmt"/>
<line num="478" count="5" type="stmt"/>
<line num="479" count="5" type="stmt"/>
<line num="481" count="5" type="stmt"/>
<line num="482" count="5" type="stmt"/>
<line num="489" count="5" type="cond" truecount="1" falsecount="1"/>
<line num="490" count="0" type="stmt"/>
<line num="493" count="5" type="stmt"/>
<line num="495" count="0" type="stmt"/>
<line num="496" count="0" type="stmt"/>
<line num="504" count="1" type="stmt"/>
</file>
<file name="model.js" path="/Users/yathishr/Desktop/personal/hometask-be-template-master/src/model.js">
<metrics statements="12" coveredstatements="12" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
<line num="1" count="1" type="stmt"/>
<line num="3" count="1" type="stmt"/>
<line num="10" count="1" type="stmt"/>
<line num="38" count="1" type="stmt"/>
<line num="55" count="1" type="stmt"/>
<line num="79" count="1" type="stmt"/>
<line num="80" count="1" type="stmt"/>
<line num="81" count="1" type="stmt"/>
<line num="82" count="1" type="stmt"/>
<line num="83" count="1" type="stmt"/>
<line num="84" count="1" type="stmt"/>
<line num="86" count="1" type="stmt"/>
</file>
</package>
<package name="src.constants">
<metrics statements="2" coveredstatements="2" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
<file name="common.constants.js" path="/Users/yathishr/Desktop/personal/hometask-be-template-master/src/constants/common.constants.js">
<metrics statements="2" coveredstatements="2" conditionals="0" coveredconditionals="0" methods="0" coveredmethods="0"/>
<line num="1" count="1" type="stmt"/>
<line num="7" count="1" type="stmt"/>
</file>
</package>
<package name="src.middleware">
<metrics statements="7" coveredstatements="7" conditionals="4" coveredconditionals="2" methods="1" coveredmethods="1"/>
<file name="getProfile.js" path="/Users/yathishr/Desktop/personal/hometask-be-template-master/src/middleware/getProfile.js">
<metrics statements="7" coveredstatements="7" conditionals="4" coveredconditionals="2" methods="1" coveredmethods="1"/>
<line num="2" count="1" type="stmt"/>
<line num="3" count="23" type="stmt"/>
<line num="4" count="23" type="cond" truecount="1" falsecount="1"/>
<line num="5" count="23" type="cond" truecount="1" falsecount="1"/>
<line num="6" count="23" type="stmt"/>
<line num="7" count="23" type="stmt"/>
<line num="9" count="1" type="stmt"/>
</file>
</package>
<package name="src.utils">
<metrics statements="30" coveredstatements="19" conditionals="24" coveredconditionals="11" methods="5" coveredmethods="3"/>
<file name="int-spec-helper.utils.js" path="/Users/yathishr/Desktop/personal/hometask-be-template-master/src/utils/int-spec-helper.utils.js">
<metrics statements="30" coveredstatements="19" conditionals="24" coveredconditionals="11" methods="5" coveredmethods="3"/>
<line num="1" count="1" type="stmt"/>
<line num="2" count="1" type="stmt"/>
<line num="12" count="12" type="stmt"/>
<line num="13" count="12" type="stmt"/>
<line num="14" count="12" type="cond" truecount="2" falsecount="0"/>
<line num="15" count="12" type="cond" truecount="1" falsecount="1"/>
<line num="16" count="12" type="stmt"/>
<line num="18" count="12" type="cond" truecount="1" falsecount="1"/>
<line num="19" count="12" type="stmt"/>
<line num="21" count="12" type="cond" truecount="1" falsecount="1"/>
<line num="22" count="0" type="stmt"/>
<line num="27" count="15" type="stmt"/>
<line num="28" count="15" type="cond" truecount="3" falsecount="1"/>
<line num="29" count="0" type="stmt"/>
<line num="32" count="15" type="stmt"/>
<line num="33" count="15" type="cond" truecount="1" falsecount="1"/>
<line num="34" count="15" type="stmt"/>
<line num="36" count="15" type="stmt"/>
<line num="38" count="15" type="stmt"/>
<line num="39" count="15" type="stmt"/>
<line num="51" count="0" type="stmt"/>
<line num="52" count="0" type="cond" truecount="0" falsecount="4"/>
<line num="53" count="0" type="stmt"/>
<line num="56" count="0" type="stmt"/>
<line num="57" count="0" type="cond" truecount="0" falsecount="2"/>
<line num="58" count="0" type="stmt"/>
<line num="60" count="0" type="stmt"/>
<line num="61" count="0" type="stmt"/>
<line num="62" count="0" type="stmt"/>
<line num="67" count="1" type="stmt"/>
</file>
</package>
</project>
</coverage>
6 changes: 6 additions & 0 deletions coverage/coverage-final.json

Large diffs are not rendered by default.

Loading

0 comments on commit 4b7622c

Please sign in to comment.