NAV
python

Menu API

Welcome to Menu API!

Contributors:

Authentication API

Registration / Sign-up

POST to /auth/signup/

import requests

# User data includes all required fields of User model:
data = {"username": "example_username", "password": "example_password", "email": "example@example.com", "phone": "111-111-1111", "access": {"user": "true", "admin": "false"},  "restrictions": ["vegetarian", "vegan"], "preferences": {"Sushi": 10, "Asian": 10, "Latin American": 1}}

response = requests.post("https://www.menubackend.com/auth/signup/", json=data) # json=data automatically handles Content-Type header
print(response.text)

{ "result": { "id": "5efa368ac5fab88355ffa9c0" } }

Registering a user is performed by sending a POST request to the endpoint at /auth/signup/. The body of the POST includes user information, including all required fields and any optional fields in the user model. The header must include Content-Type: application/json. If the username or email is already taken, returns an error response. Upon success, the user's id is returned.

Login

POST to /auth/login/ -- Native login


# Native user's password and email:
data = {"password": "example_password", "username_or_email": "example@example.com" or "example"}

response = requests.post("https://www.menubackend.com/auth/login/", json=data)
print(response.text)

{ "result": { "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciO...", "logged_in_as": "example_username", "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciO...", "user_id": "5f0fb5503cb6921b32c6b...", } }

The native login API takes a POST request containing the user's (username or email) and (password). If successful, returns a JSON response containing a newly created access token and refresh token. If the user has not already registered (i.e., is not on the database), tokens will not be issued and an error response is returned. Again requires the Content-Type: application/json header.

POST to /auth/login/google/ -- Google login

# Google user's ID token obtained from Google's login API:
data = {"id_token": "insert_google_id_token_here"}

response = requests.post("https://www.menubackend.com/auth/login/google/", json=data) 
print(response.text)

{ "result": { "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciO...", "logged_in_as": "example_username", "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciO...", "user_id": "5f0fb5503cb6921b32c6b...", } }

Google login takes a POST request containing the user's id token obtained from Google's login API. If successful, returns a JSON response containing a newly created access token and refresh token. If the user has not already registered (i.e., is not on the database), tokens will not be issued and an error response is returned. Again requires the Content-Type: application/json header.

A successful login attempt provides the user with a valid access token and refresh token. As its name suggests, the access token is used to access the backend's protected endpoints. However, the access token has a very limited lifespan of a few mins. Once the access token expires, the refresh token, which has a much longer lifespan of several hours, can be used to obtain a new access token using the refresh endpoint.

Logout

# First revoke the current access token:
data = {} # Empty POST body
headers = {'Authorization': 'Bearer insert_access_token_here'}
response = requests.post("https://www.menubackend.com/auth/logout/", headers=headers, json=data) 
print(response.text)

{ "result": "Logout successful. Access token successfully revoked." }

# Next revoke the current refresh token:
data = {} # Empty POST body
headers = {'Authorization': 'Bearer insert_refresh_token_here'}
response = requests.post("https://www.menubackend.com/auth/logout2/", headers=headers, json=data) 
print(response.text)

{ "result": "Logout successful. Refresh token successfully revoked." }

POST to /auth/logout/ and /auth/logout2/

After login, the frontend user possesses an access token and a refresh token. When logging out, the backend needs to revoke both of these tokens so they are no longer viable. To do this, the logout process provides two endpoints: /auth/logout/ and /auth/logout2/, which revoke the active access token and refresh token, respectively. The POST body can be empty, but /auth/logout/ requires the access token header Authorization: Bearer insert_access_token_here, and /auth/logout2/ requires the refresh token header Authorization: Bearer insert_refresh_token_here. Upon success, a success message is returned in both cases.

Manage refresh token

POST to /auth/refresh/

# Obtain a new valid access token using a valid refresh token:
data = {} # Empty POST body
headers = {'Authorization': 'Bearer insert_refresh_token_here'}
response = requests.post("https://www.menubackend.com/auth/refresh/", headers=headers, json=data) 
print(response.text)

{ "result": {"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciO..."} }

Sending a POST to this endpoint with a valid refresh token in the headers (Authorization: Bearer insert_refresh_token_here) issues a new valid access token.

Forgot password

POST to /auth/forgot

data = {"email": "example@example.com"} # email in the post body
response = requests.post("https://www.menubackend.com/auth/forgot/", json=data) 
print(response.text)

{'result': 'An email has been sent if this is a valid Menu email.'}

[This endpoint is under construction. We need to set up a menu email first] Sending a POST to this endpoint with a user's email in the body will send an email to that address with a token in the email body. This token will be included in the body of the post request to the reset endpoint.

Reset password

POST to /auth/reset/

data = {"reset_token": "insert_reset_token_from_email_here", "password": "new_password"} # reset token obtained from the email sent out by forgot endpoint
response = requests.post("https://www.menubackend.com/auth/forgot/", json=data) 
print(response.text)

{'result': 'Successfully reset password.'}

[This endpoint works with /auth/forgot/, so is still under construction. Sending a POST to this endpoint with the reset token in the request body as well as the user's new password. The endpoint will update the user's password and send a confirmation email to them that the password has been changed.

Query API

data = { "name": "Avalon Diner", "addr": "12810 Southwest Fwy. Stafford, TX 77477"} # query the database for restaurant named Avalon diner at that address
response = requests.post("https://www.menubackend.com/query/restaurant/", json=data) 
print(response.text)

{ "result": [ { "_id": ... "addr": "12810 Southwest Fwy. Stafford, TX 77477" ... etc ... } ] }

Resource for querying the mongoDB Restaurant collection from an HTTP request. Post request must contain header Content-Type: application/json and the body must be a JSON query. Response is a JSON with one key -- "result" whose corresponding value is a JSON List containing JSON dicts for every restuarant matching the query. If the JSON List is empty, nothing in the database matched the query.

Analagous post requests can be sent to the /query/item/ endpoint to query the database for menu items.

Category API

Get all categories

GET to /categories/

headers = {'Authorization': 'Bearer insert_access_token_here'}

response = requests.get("https://www.menubackend.com/category/", headers=headers) 
print(response.text)

{'result': ['array of all categories']}

To obtain all the categories in the database, send a get request to this endpoint with an access token in the header.

Add a categories

POST to /categories/

headers = {'Authorization': 'Bearer insert_access_token_here'}

data = {"name": "category_name", "description": "category_description", "items":["array of references to item model"]}# Must be admin user token
response = requests.post("https://www.menubackend.com/category/", headers=headers, json=data) 
print(response.text)

{'result': {'id': 'category id'}

To add a category to the database, send a post request to this endpoint with the access token from a user with admin priviledges (admin = true in the access field of the user model). If added successfully, returns the category id.

Get a specific category

GET to /category/<category_id>/

headers = {'Authorization': 'Bearer insert_access_token_here'}

response = requests.post("https://www.menubackend.com/category/", headers=headers) 
print(response.text)

{'result': {"_id": {"$oid": "category_id"}, "description": "category_description", "items": [], "tags":[], "name":"category_name"}}

To obtain a specific category, send a get request to this endpoint with an access token in the header and category id in the URL.

Update a specific category

PATCH to /category/<category_id>/

headers = {'Authorization': 'Bearer insert_access_token_here'} # Must be admin user token

data = {"description": "category description update"}
response = requests.post("https://www.menubackend.com/category/category_id", headers=headers, json=data) 
print(response.text)

{'result': {"_id": {"$oid": "category_id"}, "description": "category description update", "items": [], "tags":[], "name":"category_name"}}

To update a specific category, send a patch request to this endpoint with an admin access token in the header and category id in the URL, and the updated category fields in the body.

Delete a specific category

DELETE to /category/<category_id>/

headers = {'Authorization': 'Bearer insert_access_token_here'} # Must be admin user token

response = requests.delete("https://www.menubackend.com/category/category_id", headers=headers) 
print(response.text)

{'result': {"_id": {"$oid": "category_id"}, "description": "category description update", "items": [], "tags":[], "name":"category_name"}}

To delete a specific category, send a delete request to this endpoint with an admin access token in the header and category id in the URL, and the updated category fields in the body.

Menu Item API

The Item API provides basic CRUD functionality which is very similar to the other CRUD APIs in our system. To the \items\ endpoint, we support:

To the \item\<item_id>\ endpoint, we support:

Restaurant API

The Restaurant API provides basic CRUD functionality which is very similar to the other CRUD APIs in our system. To the \restaurants\ endpoint, we support:

To the \restaurant\<restaurant_id>\ endpoint, we support:

Review API

The Review API provides basic CRUD functionality which is very similar to the other CRUD APIs in our system. To the \reviews\ endpoint, we support:

To the \review\<review_id>\ endpoint, we support:

To the \review\<restaurant_id>\ endpoint, we support:

To the \review\<restaurant_id>\<max_number_of_reviews>\ endpoint, we support:

User API

The User API provides basic CRUD functionality which is very similar to the other CRUD APIs in our system. To the \users\ endpoint, we support:

To the \user\<user_id>\ endpoint, we support:

Search API

POST to /search/

The Search API provides one endpoint supporting a post request to handle search queries. To the \search\ endpoint, send a POST request (with a logged in user's access token in the header), where POST body contains the following:

Errors: In the case that page parameter is not included in the POST request body, returns error id 15. If the processed_prefs field of the user is an empty list on the database, meaning a PATCH request was never sent to the /user// endpoint following user registration, returns error id 16.

Response: The response is a list where each recommended restaurant is represented by a dictionary with 6 keys:

headers = {'Authorization': 'Bearer insert_access_token_here'}

data = {"page": 0, "search_string": "breakfast", "submenus":["breakfast"], "user_coords": [29.72154017, -95.39608383]}

response = requests.post("https://www.menubackend.com/search/", headers=headers, json=data) 
print(response.text)

[ { "restaurant_object": { "_id": "ce64dcbe-5399-4be7-8f4b-88dc01b022b1", "name": "The Original Ninfa's - Uptown Houston", "link": "https://www.opentable.com/r/the-original-ninfas-uptown-houston?avt=eyJ2IjoxLCJtIjoxLCJwIjowfQ&corrid=f458c3a2-b079-46d0-a159-e58fae678ba4", "rating": "4.2", "attrs": { "piclink": "https://images.otstatic.com/prod/26414663/4/medium.jpg", "addr": "1700 Post Oak Blvd Houston, TX 77056", "diningstyle": "Casual Dining", "dresscode": "Casual Dress", "additional": "Beer, Cocktails, Delivery, Full Bar, Outdoor dining, Private Room, Takeout, Weekend Brunch, Wine", "website": "http://www.ninfas.com/" }, "dollarsigncount": 2, "cuisine": "Mexican", "revcount": 175, "loc": "Galleria / Uptown" }, "items": { "Bean, Egg, Cheese": { "score": 0.33183933558814865, "rev": 30 }, "Fajita, Egg, Cheese": { "score": 0.2749612585690266, "rev": 24 }, "Chorizo, Egg, Cheese": { "score": 0.23158002514783957, "rev": 24 }, "Potato, Egg, Cheese": { "score": 0.22243416722471326, "rev": 24 }, "Bacon, Egg, Cheese": { "score": 0.20954943656571773, "rev": 24 } }, "item_sum_score": 0.26302694106198093, "rev_sum_score": 0.6363636363636364, "loc": 4.408288309118191, "locscore": 0.04665314401622718 }, ... ]

Interested API

POST to /interested/

This endpoint is specifically for storing beta-user-request email info into mongodb collection, to later send out emails on updates to our product The Interested API supports only one request: POST. Use the POST request for /interested/ endpoint to store an email address and a default timestamp to 'interested' collection. The signup_time field will default to the request time.

data = {"email": <email address>}

response = requests.post("https://www.menubackend.com/interested/", json=data) 

{'id': <"mongoDB document id">}

DSCI API

Note: Frontend should NOT call this directly; you should call backend's endpoint which then calls these.

Reviews

POST to /usefulreviewsgen/

# Get useful reviews
rhash = '0b7f3d99-13ce-4d45-955e-71902c4cb9a4'
response = requests.post(awsurl + 'usefulreviewsgen/', json = {'rhash':rhash})
newdict = ast.literal_eval(response.text.replace('Infinity', '-1').replace('false', 'False').replace('null','None'))

[ { 'name': 'RevrendDr', 'city': 'Kansas City', 'text': 'The mushroom risotto was delicious, as was the lobster bisque. Also had the lamb shank, which was nice and tender. The white sangria on special every Saturday night was also a big hit. Our waitress was sweet and attentive, if a little new. Will definitely go back for another visit.', 'overallrating': '5', 'subratings': { 'Overall': '5', 'food': '5', 'service': '4', 'ambience': '4' } }, ... # Other reviews left out for clarity ]

Getting useful reviews is performed by sending a POST request to the endpoint at /usefulreviewsgen/. The body of the POST includes rhash. The header must include Content-Type: application/json. Upon success, a list of dictionaries representing reviews is returned.

Inputs

Outputs

User preference

POST to /usrprefgen/

# Generate user preferences
prefdict = {'Sushi': 10, 'Asian': 6, 'Latin Ameridfasdfcan': 10}
test_cluster_group_prefs = {'0': 1, '2': 2, '3': 3, '6':2}
response = requests.post(awsurl + 'usrprefgen/', json = {'prefscu':prefdict, 'prefscl': test_cluster_group_prefs})
utdl = ast.literal_eval(response.text.replace('Infinity', '-1').replace('false', 'False').replace('null','None'))

[ [['piece', 'succulent'], 0.0039, 1.0], [['mole', 'annie'], 0.0039, 1.0], [['crawfish', 'étouffée'], 0.0039, 1.0], [['pickle', 'lodge'], 0.0039, 1.0] ]

Takes a POST request optionally containing a cuisine preferences dictionary and/or a cluster preferences dictionary. Returns a list of lists representing the user's preferences.

Inputs

# All possible cuisines (as of July 12, 2020)
from reccomender import make_from_cluster as mfc
mfc.cuisine_list()[1]

['American', 'Seafood', 'Steak', 'Italian', 'Steakhouse', 'Contemporary American', 'Southern', 'Mexican', 'Japanese', 'French', 'Wine Bar', 'Comfort Food', 'Contemporary Italian', 'Latin American', 'Tex-Mex', 'Cajun', 'Pizzeria', 'Mediterranean', 'Sushi', 'International', 'Lounge', 'Spanish', 'Asian', 'Global', 'Brazilian', 'Shellfish', 'Creole / Cajun / Southern', 'Bar / Lounge / Bottle Service', 'Tapas / Small Plates', 'Contemporary Southern', 'Bistro', 'Brazilian Steakhouse', 'European', 'Pub', 'Oyster Bar', 'Cocktail Bar', 'Chinese', 'Contemporary French', 'South American', 'Fusion / Eclectic', 'Farm-to-table', 'Gastro Pub', 'Sports Bar', 'Indian', 'Contemporary French / American', 'French American', 'Barbecue', 'Southwest', 'Continental', 'Vegetarian / Vegan', 'Southern African', 'Afternoon Tea', 'Contemporary Mexican', 'Latin / Spanish', 'Dessert', 'Grill', 'Lebanese', 'Korean', 'Sicilian', 'Peruvian', 'Chinese (Canton)', 'Fish', 'Burgers', 'Wild Game', 'Mexican / Southwestern', 'Contemporary Asian', 'Vietnamese', 'Traditional Mexican', 'Breakfast', 'Organic', 'Regional Japanese', 'Prime Rib', 'Dim Sum', 'Meat', 'Contemporary Indian', 'Winery', 'Halal', 'Pakistani', 'Argentinean', 'Greek', 'Creative Japanese', 'Teppanyaki', 'Ecuadorian', 'Cuban', 'Soul food', 'British', 'Beer Garden', 'Austrian', 'German', 'Rotisserie Chicken', 'Ramen', 'Regional Mexican', 'Café', 'Modern European', 'Northern Mexican', 'Thai', 'English', 'Fondue']

Outputs

Search results

POST to /search/

# Search for chinese restaurants with delivery

# utdl = result of previous call to /userprefgen/
response = requests.post(awsurl + 'search/', json = {
    'search_string':'chinese', # breaks properly
    'submenus':['delivery'], # non required works
    'prefs':utdl, # breaks properly
    'coords':[29.72154017, -95.39608383], # non required works
    'restrictions':[], # non required works
    'prefdict': {'locscore': 2, 'rev_sum_score': 0.03, 'item_sum_score': 0.6},
    'page': 1
    })
newdict = ast.literal_eval(response.text.replace('Infinity', '-1').replace('false', 'False').replace('null','None'))
{
  "3841689": { // hash of restaurant 
    "items": { // dictionary of items in the top n belonging to that restaurant 
      "82051594": {"score": 0.3346763757962888, "rev": 0}, // item hash is mapped to the score
      "21833507": {"score": 0.3191075257345033, "rev": 3},
      ...
    },
    "item_sum_score": 1.307567803061584, // sum of item scores for that restaurant
    "rev_sum_score": 0.0, // the sum of how the items were perceived in the reviews
    "loc": 21.016145659571272, // how far away the restaurant is in miles
    "locscore": 0.005 // miles of location converted to a score
  },
  ...
}
/* All possible submenu filters */
main, takeout, delivery, dessert, dinner, lunch, breakfast, pizza, sushi, appetizers, kids, brunch

Takes a POST request containing a search query and other parameters. Returns a list of dictionaries representing the recommended restaurants and their basic information.

Inputs

Outputs

Restaurant menu

POST to /rstget/

# Get recommended menu items for a restaurant 
rhash = '0b7f3d99-13ce-4d45-955e-71902c4cb9a4'
response = requests.post(awsurl + 'rstget/', json = {'rhash':rhash}), 'prefs':utdl}) 
newdict = ast.literal_eval(response.text.replace('Infinity', '-1').replace('false', 'False').replace('null','None'))
{
    "42d9f32b-f4d8-4723-b905-240dcd3324f2":  // hash of the item
        {"itemscores": 
            [ // list of ingredients, each with a scores dict and the name and description. This structure is consistent for each ingredient, with the 'scores' key mapped to a dictionary of 'score' mapped to the item score, 'n' mapped to the confidence index, 'desc' mapped to the descriptor of that ingredient, and 'name' mapped to the name of that ingredient.
                {"scores": {"score": 0.829, "n": 3}, "desc": "", "name": "tomato"}, 
                {"scores": {"score": 0.738, "n": 3}, "desc": "", "name": "onion"},
                ...
            ],
            "score": 0.171969300975924, // score of the item's ingredients (float)
            "hash": 4e17ee65-c3ad-480d-8457-1878fc2c146f, // hash of the restaurant it belongs to (string)
            "submenu": "Lunch Menu", // submenu the item was found in (string)
            "submenufilter": ["lunch"], // submenu filter the item was found in (list of strings - constrained to items in all possible submenufilters
            "price": "9.00", // price of the item (string of float)
            "rev": 4, // accumulated score from the reviews (int))
        }
    ...
}
/* All possible submenu filters */
main, takeout, delivery, dessert, dinner, lunch, breakfast, pizza, sushi, appetizers, kids, brunch

Takes a POST request containing a restaurant hash and (optionally) user preferences. Returns a list of dictionaries representing the recommended menu items and their basic information.

Inputs

Outputs

Models

Category

Field Name Code Type Description Required
name StringField(required=True) String name of category Required
description StringField(required=True) String description of category Required
tags ListField(StringField()) Array of String tags associated with category.
items ListField(ReferenceField(Item)) Array of ObjectID array of references to Item model.
Field Name Code Type Description Required
_id StringField(primary_key=True, required=True) String of length 36 MongoDB Primary Key hash, must be length 36 alphanumeric Required
location StringField(required=True) String of length 36 reference to Restaurant model. Required
name StringField(required=True) String name of menu item. Required
desc StringField() String description of menu item.
price FloatField(required=True) Float price of menu item. Required
image_url StringField() String url that holds menu item image.
tags ListField(StringField()) Array of String tags associated with menu item.

Restaurant

Restaurant model

Field Name Code Type Description Required
_id _id = StringField(primary_key=True, required=True) String MongoDB Primary Key hash, must be length 36 alphanumeric Required
name StringField(required=True) String name of restaurant Required
link StringField() String link to restaurant info
rating StringField() String rating of restaurant
attrs EmbeddedDocumentField(Attributes) Attribute model attributes of restaurant
dollarsigncount IntField() Integer dollar sign indicating price of restaurant
cuisine StringField() String type of cuisine
revcount IntField() Integer
loc StringField() String restaurant address
menudict ListField(StringField()) Array of String (length 36) object ids of menu item that the restaurant serves

Attributes model

Field Name Code Type Description Required
piclink StringField() String url of restaurant picture
addr StringField() String restaurant address
cuisines StringField() String type of cuisine
diningstyle StringField() String dining style of cuisine
dresscode StringField() String restaurant dress code
additional StringField() String additional info of restaurant
website StringField() String restaurant website link

Review

Review model

Field Name Code Type Description Required
location StringField(required=True) String of length 36 MongoDb object id of restaurant model Required
name StringField(required=True) String reviewer name Required
city StringField(required=True) String city location name Required
overallrating IntField() Integer overall rating for this review
subratings EmbeddedDocumentField(Subrating) Subrating model subrating information for this review
text StringField() String review text

Subrating model

Field Name Code Type Description Required
overall IntField() Integer overall rating
food IntField() Integer food rating
service IntField() Integer service rating
ambience IntField() Integer ambience rating

User

User model

- User info

Field Name Code Type Description Required
username StringField(required=True) String first and last name of user Required
password BinaryField(required=True) Binary encrypted account password Required
email EmailField(required=True, unique=True) Email field account email Required
access EmbeddedDocumentField(Access) Access model refer to Access model
phone PhoneField() Phone field account phone number

- Restrictions

Field Name Code Type Description Required
restrictions ListField() Array of String list of restrictions

- Pref levels

Field Name Code Type Description Required
preferences DictField(default = {}) Dictionary (key = string, value = integer) key-value pair of user preferences
cluster_group_prefs DictField(default = {}) Dictionary (key = string, value = integer) key-value pair of cluster_group_prefs (https://docs.google.com/spreadsheets/d/108FCNhgjhSnSmJ-OyNcjUzSwVw2f7l6cF6dU--GoR9M/edit#gid=0)
processed_prefs DynamicField() Array of String cached preferences after being processed by dsci

- Other

Field Name Code Type Description Required
created_on DateTimeField(default = datetime.now) DateTime field indicates when this account was generated
fav_restaurant StringField() String of length 36 reference to restaurant model

Access model

Field Name Code Type Description Required
user BooleanField(default=False) Boolean is this account "user" level?
admin BooleanField(default=False) Boolean is this account "admin" level?
googlelogin BooleanField(default=False) Boolean is this account "googlelogin" level?
facebooklogin BooleanField(default=False) Boolean is this account "facebooklogin" level?
guest BooleanField(default=False) Boolean is this account "guest" level?

Errors

Menu Backend API uses the following error codes:

Error Code Meaning
400 Bad Request -- Your request is invalid. Could be: Request not json, Update failure, or Deletion failure.
401 Unauthorized -- Authorization has been refused for the given credentials
403 Forbidden -- The current user is not authorized to take this action.
404 Not Found -- This route is currently not supported.
409 Something is Invalid - Could be: This email is already taken or the email address is wrong, The user did not authorize the use of their email, This username is already taken, or Invalid token provided.
500 Internal Server Error -- We had a problem with our server.

Error Ids

Look for specific error ids here!

Error Id Name of Error Error message Status Code
1 request_not_json Data type in request is not JSON 400
2 unauthorized Authorization has been revoked 401
3 forbidden Current user is not allowed to take this action 403
4 invalid_route This route is currently not supported 404
5 invalid_email This email is already taken or the email address is wrong 409
6 oauth_scopes_unauthorized The user did not authorize the use of their email 409
7 invalid_username This username is already taken 409
8 invalid_token Invalid token provided 409
9 update_failure Database document update failed 400
10 deletion_failure Database document deletion failed 400
11 validation_error Marshmallow validation has failed 400
12 mongodb_get_object_error MongoDB document get request has failed 500
13 all_other_error This error message corresponds to all other errors 500
14 wrong_login_route You cannot attempt login to /auth/login/ without user access field being True 401
15 unexpected_body_format Request body has format deviating from what is expected 400
16 no_user_prefs User doesn't have processed preferences before searching 500