Skip to content

Commit

Permalink
development of users collection api endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
zarifmahfuz committed Feb 12, 2022
1 parent 896a7f8 commit 1c2fdd9
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 3 deletions.
5 changes: 4 additions & 1 deletion webserver/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
FROM python:3.8-alpine
FROM python:3.8-slim-buster

ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0

RUN mkdir /webserver
COPY . /webserver
WORKDIR /webserver
# RUN apk update && apk upgrade
# RUN apk add autoconf automake g++ make --no-cache
RUN python -m pip install --upgrade pip
RUN pip install -r requirements.txt

EXPOSE 5000
Expand Down
7 changes: 6 additions & 1 deletion webserver/app.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from flask import Flask
from flask_restful import Api
from webserver.resources.users_collection import UsersCollection
from flask_bcrypt import Bcrypt

app = Flask(__name__)
api = Api(app)
bcrypt = Bcrypt(app)

from webserver.resources.users_collection import UsersCollection
from webserver.resources.users import Users
api.add_resource(UsersCollection, "/users")
api.add_resource(Users, "/users/<string:user_id>")
Empty file added webserver/classes/__init__.py
Empty file.
Empty file.
3 changes: 3 additions & 0 deletions webserver/classes/exceptions/data_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class DataFormatError(Exception):
""" Raised when unrecognized data format is given """
pass
3 changes: 3 additions & 0 deletions webserver/classes/exceptions/invalid_userid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class InvalidUserException(Exception):
""" Raised when invalid userid is entered """
pass
72 changes: 72 additions & 0 deletions webserver/classes/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from webserver.database_manager import users_collection
from webserver.classes.exceptions.data_format import DataFormatError
from webserver.classes.exceptions.invalid_userid import InvalidUserException
from jsonschema import validate
from flask_bcrypt import Bcrypt
import pymongo

class User(object):
"""
This class interacts with the database to perform database operations related to the
Users collection.
"""
def __init__(self):
self.schema = {
"type": "object",
"properties": {
"userId": {"type": "string"},
"name": {"type": "string"},
"email": {"type": "string"},
"password": {"type": "string"}
},
"required": ["name", "userId", "password"],
"additionalProperties": False
}

def user_exists(self, userId: str) -> bool:
result = users_collection.find({"_id": userId})
return result != None

def add_user(self, data: dict, bcrypt: Bcrypt):
"""
Adds a new user to the Users collection.
Returns the inserted id if operation was successful. Returns None otherwise.
"""
# raises exception is invalid data
self.validate_data(data)
data["_id"] = data["userId"]
del data["userId"]

unhashed_password = data["password"]
data["password"] = bcrypt.generate_password_hash(unhashed_password).decode("utf-8")

try:
ret = users_collection.insert_one(data)
return ret.inserted_id
except pymongo.errors.DuplicateKeyError:
pass
return None

def login_user(self, userId: str, password: str, bcrypt: Bcrypt):
"""
Queries the database with the given userId and verifies the given password.
Raises:
InvalidUserException: if userId does not exist
Returns:
True, if password is valid. False otherwise.
"""
doc = users_collection.find_one({"_id": userId})
if (doc == None):
raise InvalidUserException(f'{userId} does not exist')
valid_password = bcrypt.check_password_hash(doc["password"], password)
return valid_password

def validate_data(self, data: dict) -> None:
# validate the data
if (isinstance(data, dict)):
validate(instance=data, schema=self.schema)
else:
raise DataFormatError("Invalid data format")

1 change: 1 addition & 0 deletions webserver/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Flask==1.1.2
Flask-RESTful==0.3.8
flask-bcrypt
pymongo
jsonschema
34 changes: 34 additions & 0 deletions webserver/resources/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from email.policy import default
from flask_restful import Resource
from flask import request
from webserver.app import bcrypt
from webserver.classes.user import User
from webserver.classes.exceptions.invalid_userid import InvalidUserException

user = User()

class Users(Resource):
def get(self, user_id):
"""
Handles a GET request.
Expects a "password" key in the request body.
Returns:
status code 404: if the given user_id does not exist
status code 401: if invalid password was given
status code 200: if login was successful
"""
try:
password = request.args.get("password", default=None, type=str)
if password is None:
return {"response": "password field was not given"}, 400

if (user.login_user(user_id, password, bcrypt)):
return {"response": "login successful"}, 200
else:
return {"response": "invalid password"}, 401
except InvalidUserException:
return {"response": "invalid user_id"}, 404
except Exception as e:
return {"response": str(e)}, 500
24 changes: 23 additions & 1 deletion webserver/resources/users_collection.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
from flask_restful import Resource
from flask import request
from webserver.database_manager import users_collection
from webserver.app import bcrypt
from webserver.classes.user import User
import jsonschema
from webserver.classes.exceptions.data_format import DataFormatError

user = User()

class UsersCollection(Resource):
def post(self):
return {"response": "hello world"}, 200
"""
Handles a POST request. Inserts a new user into the Users collection.
Returns:
status code 201 if the user was successfully created.
status code 409 if the given userId already exists.
"""
try:
data = request.get_json()
ret = user.add_user(data, bcrypt)
status = 201
if (ret == None):
status = 409
return {"userId": ret}, status
except (jsonschema.exceptions.ValidationError, DataFormatError) as e:
return {"response": str(e)}, 400
except Exception as e:
return {"response": str(e)}, 500

0 comments on commit 1c2fdd9

Please sign in to comment.