There is a restaurant called Transparent Restaurant
. In this restaurant, honesty is extremely promoted. So extreme, that the restaurant declares that differing quality of ingredients are used in their meals. Like that's not enough, it also allows the customers to choose the ingredients of each meal in different qualities. Each ingredient has the following quality levels:
low
: the cheapestmedium
: moderatehigh
: the most expensive
Due to the instability in the economy, the prices increase about every two weeks. This is a problem for the business because restaurant needs to print out new menu books each time the prices are updated, which is a significant cost in long term. Therefore, the restaurant owners have decided to go digital. They want to replace the physical menus with digital ones and have hired you to develop this system.
Because you work alone, you need to build both the backend and the frontend of this system. The backend shall be responsible of serving the menu to the frontend and the frontend shall be responsible of processing the menu and displaying it to the customers.
Below are the detailed descriptions of the required and the bonus HTTP endpoints to be implemented by the backend. A few notes about the descriptions:
- The sample responses are only for examplification and do not represent actual data. However, they do represent the required data format.
- For all
GET
methods, the parameters must be in the URL query. - For all
POST
methods, the parameters must be in the request body. The body can consist of eitherJSON
orurl-encoded form
data. You can choose either (or both) of them. - Parameters can either be
required
oroptional
. - Parameters can have default values. These default values, if specified, are generally programmatically-default values (e.g.
false
forboolean
) and must be used if incoming requests do not contain other values. - All responses must be in
JSON
format. - The server must be running locally.
HTTPS
is not required.
Lists the meals in the menu and their ingredients. Options to filter for vegetarian or vegan meals must be implemented as well (see Vegetarian and Vegan Definition)
Parameter | Type | Required | Default | Description |
---|---|---|---|---|
is_vegetarian | boolean | false | false | When true, non-vegetarian meals must be discarded. |
is_vegan | boolean | false | false | When true, non-vegan meals must be discarded. |
curl "http://localhost:8080/listMeals"
[
{
"id": 1,
"name": "Shrimp-fried Rice",
"ingredients": ["shrimp", "rice"]
},
{
"id": 2,
"name": "Beer Plate",
"ingredients": ["French Fries", "Onion Rings", "Sausages", "Chicken Wings"]
}
]
This endpoint takes a meal ID and returns its name and ingredients with each option of ingredients included.
Parameter | Type | Required | Default | Description |
---|---|---|---|---|
id | integer | true | The unique ID of the meal. |
curl "http://localhost:8080/getMeal?id=2"
{
"id": 2,
"name": "Beer Plate",
"ingredients": [
{
"name": "Rice",
"groups": ["vegan", "vegetarian"],
"options": [
{
"name": "Long grain white rice",
"quality": "high",
"price": 3
},
{
"name": "Medium grain brown rice",
"quality": "medium",
"price": 2
},
{
"name": "Quick cooking white rice",
"quality": "low",
"price": 1.5
}
]
},
{
"name": "Pasta",
"groups": ["vegetarian"],
"options": [
{
"name": "Semolina pasta",
"quality": "high",
"price": 2
},
{
"name": "Whole wheat pasta",
"quality": "medium",
"price": 1.5
},
{
"name": "Enriched pasta",
"quality": "low",
"price": 1
}
]
}
]
}
This endpoint takes a meal id with its ingredients' quality selecitons and returns the resulting quality score. If an ingredient's quality is not specified, "high" quality should be assumed by default.
Each <ingredient-n>
can be any one of the ingredients in the meal. If meal doesn't have that ingredient, than it should be ignored.
Parameter | Type | Required | Default | Description |
---|---|---|---|---|
meal_id | integer | true | The unique ID of the meal. | |
<ingredient-n> | enum("high", "medium", "low) | true | Name of the ingredient in the meal and its desired quality value. |
curl "http://localhost:8080/getMeal?id=2&garlic=high"
{
"quality": 30
}
This endpoint takes a meal id with its ingredients' quality selecitons and returns the resulting price. If an ingredient's quality is not specified, "high" quality should be assumed by default.
Each <ingredient-n>
can be any one of the ingredients in the meal. If meal doesn't have that ingredient, than it should be ignored.
Parameter | Type | Required | Default | Description |
---|---|---|---|---|
meal_id | integer | true | The unique ID of the meal. | |
<ingredient-n> | enum("high", "medium", "low) | true | Name of the ingredient in the meal and its desired quality value. |
curl -d "meal_id=3&garlic=low" -X POST "http://localhost:8080/price"
{
"price": 40.99
}
These endpoints are optional but will get you a higher score during the evalutaion phase should you implement them.
This endpoint returns a randomly selected meal of random quality parameters, with an option to set a budget.
Parameter | Type | Required | Default | Description |
---|---|---|---|---|
budget | double | false | The maximum price of the generated meal. If not specified, there is no maximum. |
curl -d "budget=42.42" -X POST "http://localhost:8080/random"
{
"id": 2,
"name": "Beer Plate",
"price": 40.99,
"quality_score": 27,
"ingredients": [
{
"name": "French Fries",
"quality": "high"
},
{
"name": "Onion Rings",
"quality": "high"
},
{
"name": "Sausages",
"quality": "high"
},
{
"name": "Chicken Wings",
"quality": "low"
}
]
}
This endpoint takes a search text and returns the meals that contain the search text. Search will be made in a case-insensitive manner.
Parameter | Type | Required | Default | Description |
---|---|---|---|---|
query | string | true | The search string |
curl "http://localhost:8080/search?query=beer"
[
{
"id": 2,
"name": "Beer Plate",
"ingredients": ["French Fries", "Onion Rings", "Sausages", "Chicken Wings"]
}
]
If in any case the API needs to return an error (e.g. when an invalid id is passed to /getMeal
endpoint), it shall set a proper status code and return a JSON response body that states the error correctly (an error with message Something went wrong
is discouraged).
In the website that you are going to build, the core actions that customers will be able to perform are:
- View a list of the meals in the menu.
- Filter for vegetarian or vegan meals in the menu.
- Get details of a single meal.
- Play with the quality levels of each ingredient in a meal and dynamically see the price and quality score of that selection.
The website shall:
- not parse any dataset file. It must fetch the menu from the locally-running backend server
- make use of
/quality
and/price
endpoints in the backend when calculating the prices and quality scores.
In addition to these, below are bonus features that you can choose to implement to get extra points.
- Random meal selection for a given budget (aka. Kendimi şanslı hissediyorum :) )
- Sorting the menu items based on attributes such as name, average price etc.. You can be creative and add more sorting options.
There are many points of extensions to this system, so feel free to do it your own way. You may add your own bonus features as long as the main requirements are completed. There are no restrictions in terms of the UI of your application. Feel free to be creative :)
Some sample pages of the application are listed below as recommendation. As stated above, you may create your application UI as you like, you don't need to stick to these sample pages as long as the main requirements are satisfied.
A page that welcomes the user and describes the restaurant, its methods and purposes briefly.
This page can include the list of meals. A rough sample design is shown below.
This page can include the details of the meals. A rough simple design is shown below.
If in any case your code encounters an error, a proper error message should be displayed on the screen (an error with message Something went wrong
is discouraged).
The backend server shall parse the given dataset file, that is in JSON format, and use it as the source of information in further operations. This dataset is provided by us with all information about the restaurant's menu, the ingredients, their quality and their prices. You should not base your implementation on the exact data in this dataset as we will use a different one during evaluation, but the data structure will be the same.
In this system, a vegetarian meal is one that contains only vegetarian
or vegan
ingredients and a vegan meal is one that contains only vegan
ingredients.
An ingredient is vegetarian
if it contains one of the groups vegetarian
or vegan
and an ingredient is vegan
if it contains the group vegan
.
- Each quality level has a corresponding score (e.g. low->10, medium->20, high->30, you may use your own values).
- The scores of each ingredient used in a meal are summed and divided to the number of ingredients to find the overall score of the meal.
- This overall score represents the quality of the meal.
For each ingredient in a meal with N used amount, add N * <unit_price>
to the overall cost.
- For instance,
Grilled chicken with roasted vegetables
has two ingredients;Chicken
andVegetables
. - For this meal, we need to use 3 units of
Chicken
and 4 units ofVegetables
. - Say, we choose medium-quality chicken and high-quality vegetables, which cost $4 and $5 per unit.
- The quality of the meal would then be
(20 + 30) / 2 = 25
over 30 points (assuming that we use the above example quality scores, you can use your own grading). - The price of the used
Chicken
would be3 * 4 = 12
and the price of the usedVegetables
would be5 * 4 = 20
. - The overall price would be the sum of both the above prices, which is
12 + 20 = $32
.
Provide README file explaining how to run your code. You should also explain the features you have implemented.
You must put your implementation in a private repositories on GitHub or GitLab. Name it <name>-<surname>-otsimo-fullstack-2023
. Both the backend and the frontend implementations must be in this repository, but should be separate from each other.
For backend, you may use only Go
, Python
or Node.js
. You must stick to built-in libraries for the backend as they are more than capable for this task.
For frontend, you may use any framework or CSS stylesheet you like. TypeScript usage is recommended, but not mandatory.
-
Until you finish your implementation, do not send us the link to your repository and do not add the collaborators described below.
-
Once you finish your implementation, send us an e-mail informing that you've completed your task and include the link to the repository. Add all of the following collaborators to your repository depending on your platform.
-
Add the collaborators only after you finished the task.
-
GitHub:
-
GitLab:
Expand to view the full dataset file
{
"meals": [
{
"id": 1,
"name": "Rice and chicken bowl",
"ingredients": [
{ "name": "Rice", "quantity": 3 },
{ "name": "Chicken", "quantity": 2 }
]
},
{
"id": 2,
"name": "Pasta with marinara sauce and vegetables",
"ingredients": [
{ "name": "Pasta", "quantity": 2 },
{
"name": "Marinara sauce",
"quantity": 1
},
{ "name": "Vegetables", "quantity": 4 }
]
},
{
"id": 3,
"name": "Grilled chicken with roasted vegetables",
"ingredients": [
{ "name": "Chicken", "quantity": 3 },
{ "name": "Vegetables", "quantity": 4 }
]
},
{
"id": 4,
"name": "Beef stir-fry with rice",
"ingredients": [
{ "name": "Beef", "quantity": 1 },
{ "name": "Rice", "quantity": 2 },
{ "name": "Vegetables", "quantity": 3 }
]
},
{
"id": 5,
"name": "Pork chops with mashed potatoes and gravy",
"ingredients": [
{ "name": "Pork chops", "quantity": 3 },
{
"name": "Mashed potatoes",
"quantity": 1
},
{ "name": "Gravy", "quantity": 1 }
]
},
{
"id": 6,
"name": "Grilled salmon with roasted asparagus",
"ingredients": [
{ "name": "Salmon", "quantity": 4 },
{ "name": "Asparagus", "quantity": 1 }
]
},
{
"id": 7,
"name": "Shrimp scampi with linguine",
"ingredients": [
{ "name": "Shrimp", "quantity": 1 },
{ "name": "Linguine", "quantity": 2 },
{ "name": "Butter", "quantity": 1 },
{ "name": "Garlic", "quantity": 1 },
{ "name": "White wine", "quantity": 1 }
]
},
{
"id": 8,
"name": "Vegetarian stir-fry with tofu",
"ingredients": [
{ "name": "Tofu", "quantity": 1 },
{ "name": "Rice", "quantity": 2 },
{ "name": "Vegetables", "quantity": 2 }
]
},
{
"id": 9,
"name": "Fruit salad with mixed berries and yogurt",
"ingredients": [
{ "name": "Mixed berries", "quantity": 2 },
{ "name": "Yogurt", "quantity": 4 }
]
}
],
"ingredients": [
{
"name": "Rice",
"groups": ["vegan", "vegetarian"],
"options": [
{
"name": "Long grain white rice",
"quality": "high",
"unit_price": 3
},
{
"name": "Medium grain brown rice",
"quality": "medium",
"unit_price": 2
},
{
"name": "Quick cooking white rice",
"quality": "low",
"unit_price": 1.5
}
]
},
{
"name": "Pasta",
"groups": ["vegetarian"],
"options": [
{
"name": "Semolina pasta",
"quality": "high",
"unit_price": 2
},
{
"name": "Whole wheat pasta",
"quality": "medium",
"unit_price": 1.5
},
{
"name": "Enriched pasta",
"quality": "low",
"unit_price": 1
}
]
},
{
"name": "Chicken",
"groups": [],
"options": [
{
"name": "Organic, free-range chicken",
"quality": "high",
"unit_price": 5
},
{
"name": "Conventional chicken",
"quality": "medium",
"unit_price": 4
},
{
"name": "Frozen chicken",
"quality": "low",
"unit_price": 3
}
]
},
{
"name": "Beef",
"groups": [],
"options": [
{
"name": "Grass-fed beef",
"quality": "high",
"unit_price": 8
},
{
"name": "Grain-fed beef",
"quality": "medium",
"unit_price": 6
},
{
"name": "Processed beef",
"quality": "low",
"unit_price": 5
}
]
},
{
"name": "Pork",
"groups": [],
"options": [
{
"name": "Heritage breed pork",
"quality": "high",
"unit_price": 7
},
{
"name": "Conventional pork",
"quality": "medium",
"unit_price": 3
},
{
"name": "Processed pork",
"quality": "low",
"unit_price": 2.5
}
]
},
{
"name": "Salmon",
"groups": [],
"options": [
{
"name": "Wild-caught salmon",
"quality": "high",
"unit_price": 12
},
{
"name": "Farmed salmon",
"quality": "medium",
"unit_price": 7
},
{
"name": "Canned tuna",
"quality": "low",
"unit_price": 3
}
]
},
{
"name": "Shrimp",
"groups": [],
"options": [
{
"name": "Wild-caught shrimp",
"quality": "high",
"unit_price": 15
},
{
"name": "Farm-raised shrimp",
"quality": "medium",
"unit_price": 13
},
{
"name": "Frozen shrimp",
"quality": "low",
"unit_price": 10
}
]
},
{
"name": "Vegetables",
"groups": ["vegan", "vegetarian"],
"options": [
{
"name": "Fresh, organic vegetables",
"quality": "high",
"unit_price": 5
},
{
"name": "Fresh, conventional vegetables",
"quality": "medium",
"unit_price": 4
},
{
"name": "Frozen vegetables",
"quality": "low",
"unit_price": 2
}
]
},
{
"name": "Fruit",
"groups": ["vegan", "vegetarian"],
"options": [
{
"name": "Fresh, organic fruit",
"quality": "high",
"unit_price": 7
},
{
"name": "Fresh, conventional fruit",
"quality": "medium",
"unit_price": 3
},
{
"name": "Canned fruit",
"quality": "low",
"unit_price": 1.5
}
]
},
{
"name": "Dairy",
"groups": ["vegetarian"],
"options": [
{
"name": "Organic, grass-fed dairy",
"quality": "high",
"unit_price": 7
},
{
"name": "Conventional dairy",
"quality": "medium",
"unit_price": 5
},
{
"name": "Processed dairy",
"quality": "low",
"unit_price": 4
}
]
},
{
"name": "Marinara sauce",
"groups": ["vegan", "vegetarian"],
"options": [
{
"name": "Homemade marinara sauce",
"quality": "high",
"unit_price": 3
},
{
"name": "Store-bought marinara sauce",
"quality": "medium",
"unit_price": 2
},
{
"name": "Canned marinara sauce",
"quality": "low",
"unit_price": 1
}
]
},
{
"name": "Butter",
"groups": ["vegetarian"],
"options": [
{
"name": "Grass-fed butter",
"quality": "high",
"unit_price": 4
},
{
"name": "Conventional butter",
"quality": "medium",
"unit_price": 2
},
{
"name": "Margarine",
"quality": "low",
"unit_price": 1
}
]
},
{
"name": "Garlic",
"groups": ["vegan", "vegetarian"],
"options": [
{
"name": "Fresh, organic garlic",
"quality": "high",
"unit_price": 0.5
},
{
"name": "Fresh, conventional garlic",
"quality": "medium",
"unit_price": 0.4
},
{
"name": "Frozen garlic",
"quality": "low",
"unit_price": 0.25
}
]
},
{
"name": "White wine",
"groups": ["vegan", "vegetarian"],
"options": [
{
"name": "High-end white wine",
"quality": "high",
"unit_price": 6
},
{
"name": "Mid-range white wine",
"quality": "medium",
"unit_price": 4
},
{
"name": "Cheap white wine",
"quality": "low",
"unit_price": 1
}
]
},
{
"name": "Mashed potatoes",
"groups": ["vegan", "vegetarian"],
"options": [
{
"name": "Homemade mashed potatoes",
"quality": "high",
"unit_price": 1.5
},
{
"name": "Store-bought mashed potatoes",
"quality": "medium",
"unit_price": 1
},
{
"name": "Instant mashed potatoes",
"quality": "low",
"unit_price": 0.5
}
]
},
{
"name": "Gravy",
"groups": [],
"options": [
{
"name": "Homemade gravy",
"quality": "high",
"unit_price": 4
},
{
"name": "Store-bought gravy",
"quality": "medium",
"unit_price": 3
},
{
"name": "Instant gravy",
"quality": "low",
"unit_price": 1.25
}
]
},
{
"name": "Asparagus",
"groups": ["vegan", "vegetarian"],
"options": [
{
"name": "Fresh, organic asparagus",
"quality": "high",
"unit_price": 4
},
{
"name": "Fresh, conventional asparagus",
"quality": "medium",
"unit_price": 3
},
{
"name": "Frozen asparagus",
"quality": "low",
"unit_price": 2
}
]
},
{
"name": "Tofu",
"groups": ["vegan", "vegetarian"],
"options": [
{
"name": "High-quality tofu",
"quality": "high",
"unit_price": 3
},
{
"name": "Medium-quality tofu",
"quality": "medium",
"unit_price": 2
},
{
"name": "Low-quality tofu",
"quality": "low",
"unit_price": 1
}
]
},
{
"name": "Yogurt",
"groups": ["vegetarian"],
"options": [
{
"name": "Organic, grass-fed yogurt",
"quality": "high",
"unit_price": 2
},
{
"name": "Conventional yogurt",
"quality": "medium",
"unit_price": 1
},
{
"name": "Processed yogurt",
"quality": "low",
"unit_price": 0.5
}
]
},
{
"name": "Mixed berries",
"groups": ["vegan", "vegetarian"],
"options": [
{
"name": "Fresh, organic mixed berries",
"quality": "high",
"unit_price": 7
},
{
"name": "Fresh, conventional mixed berries",
"quality": "medium",
"unit_price": 4
},
{
"name": "Frozen mixed berries",
"quality": "low",
"unit_price": 2
}
]
},
{
"name": "Linguine",
"groups": ["vegetarian"],
"options": [
{
"name": "High-end linguine",
"quality": "high",
"unit_price": 2
},
{
"name": "Mid-range linguine",
"quality": "medium",
"unit_price": 1.5
},
{
"name": "Cheap linguine",
"quality": "low",
"price": 1
}
]
}
]
}