You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
332 lines
11 KiB
332 lines
11 KiB
import sys
|
|
import json
|
|
import requests
|
|
import dateutil.parser
|
|
|
|
from flask import Flask, request, Response
|
|
from flask_restplus import Api, Resource, fields, reqparse
|
|
from pymongo import MongoClient, errors
|
|
|
|
from utils import get_logger
|
|
|
|
|
|
if len(sys.argv) == 3:
|
|
_, users_host, mongo_host = sys.argv
|
|
mongo_client = MongoClient(mongo_host, 27017)
|
|
else:
|
|
users_host = "http://web-users:5000"
|
|
mongo_client = MongoClient("mongodb", 27017)
|
|
bookcollection = mongo_client.demo.bookcollection
|
|
borrowcollection = mongo_client.demo.borrowcollection
|
|
logger = get_logger()
|
|
|
|
|
|
app = Flask(__name__)
|
|
api = Api(
|
|
app=app,
|
|
title="Book collection",
|
|
description="Simulates a book library with users and book borrwing",
|
|
)
|
|
book_api = api.namespace("book", description="Book api")
|
|
borrow_api = api.namespace("borrow", description="Boorrow, returing api")
|
|
|
|
book_model = book_api.model(
|
|
"Book",
|
|
{
|
|
"isbn": fields.String(description="ISBN", required=True),
|
|
"name": fields.String(description="Name of the book", required=True),
|
|
"author": fields.String(description="Book author", required=True),
|
|
"publisher": fields.String(description="Book publisher", required=True),
|
|
"nr_available": fields.Integer(
|
|
min=0, description="Nr books available for lend", required=True
|
|
),
|
|
},
|
|
)
|
|
|
|
borrow_model = borrow_api.model(
|
|
"Borrow",
|
|
{
|
|
"id": fields.String(
|
|
min=0, description="Unique uuid for borrowing", required=True
|
|
),
|
|
"userid": fields.Integer(
|
|
min=0, description="Userid of the borrower", required=True
|
|
),
|
|
"isbn": fields.String(description="ISBN", required=True),
|
|
"borrow_date": fields.DateTime(required=True),
|
|
"return_date": fields.DateTime(required=False),
|
|
"max_return_date": fields.DateTime(required=True),
|
|
},
|
|
)
|
|
|
|
return_model = borrow_api.model(
|
|
"Return",
|
|
{
|
|
"id": fields.String(
|
|
min=0, description="Unique uuid for borrowing", required=True
|
|
),
|
|
"return_date": fields.DateTime(required=False),
|
|
},
|
|
)
|
|
|
|
|
|
class User:
|
|
def __init__(self, exists: bool, userid: int, name: str, email: str) -> None:
|
|
self.exists = exists
|
|
self.userid = userid
|
|
self.name = name
|
|
self.email = email
|
|
|
|
|
|
pagination_parser = reqparse.RequestParser()
|
|
pagination_parser.add_argument("limit", type=int, help="Limit")
|
|
pagination_parser.add_argument("offset", type=int, help="Offset")
|
|
|
|
|
|
def get_user(id: int) -> User:
|
|
try:
|
|
response = requests.get(url="{0}/users/{1}".format(users_host, str(id)))
|
|
except Exception as e:
|
|
logger.error("Error getting user data error: {0}".format(str(e)))
|
|
return User(False, id, None, None)
|
|
if response.status_code != 200:
|
|
return User(False, id, None, None)
|
|
try:
|
|
result = response.json()
|
|
return User(True, id, result["name"], result["email"])
|
|
except:
|
|
return User(False, id, None, None)
|
|
|
|
|
|
@borrow_api.route("/return/<string:id>")
|
|
class Return(Resource):
|
|
@borrow_api.doc(responses={200: "Ok"})
|
|
@borrow_api.expect(return_model)
|
|
def put(self, id):
|
|
borrow_api.payload["id"] = id
|
|
borrow = borrowcollection.find_one({"id": id})
|
|
if None is borrow:
|
|
return Response(
|
|
json.dumps({"error": "Borrow id not found"}),
|
|
status=404,
|
|
mimetype="application/json",
|
|
)
|
|
if "return_date" in borrow:
|
|
return Response(
|
|
json.dumps({"error": "Book already returned"}),
|
|
status=404,
|
|
mimetype="application/json",
|
|
)
|
|
del borrow["_id"]
|
|
bookcollection.update_one(
|
|
{"isbn": borrow["isbn"]}, {"$inc": {"nr_available": 1}}
|
|
)
|
|
borrowcollection.update_one(
|
|
{"id": borrow_api.payload["id"]},
|
|
{
|
|
"$set": {
|
|
"return_date": dateutil.parser.parse(
|
|
borrow_api.payload["return_date"]
|
|
)
|
|
}
|
|
},
|
|
)
|
|
return Response(
|
|
json.dumps(borrow_api.payload, default=str),
|
|
status=200,
|
|
mimetype="application/json",
|
|
)
|
|
|
|
|
|
@borrow_api.route("/<string:id>")
|
|
class Borrow(Resource):
|
|
def get(self, id):
|
|
borrow = borrowcollection.find_one({"id": id})
|
|
if None is borrow:
|
|
return Response(
|
|
json.dumps({"error": "Borrow id not found"}),
|
|
status=404,
|
|
mimetype="application/json",
|
|
)
|
|
del borrow["_id"]
|
|
user = get_user(borrow["userid"])
|
|
borrow["user_name"] = user.name
|
|
borrow["user_email"] = user.email
|
|
book = bookcollection.find_one({"isbn": borrow["isbn"]})
|
|
if None is book:
|
|
return Response(
|
|
json.dumps({"error": "Book not found"}),
|
|
status=404,
|
|
mimetype="application/json",
|
|
)
|
|
borrow["book_name"] = book["name"]
|
|
borrow["book_author"] = book["author"]
|
|
return Response(
|
|
json.dumps(borrow, default=str), status=200, mimetype="application/json"
|
|
)
|
|
|
|
@borrow_api.doc(responses={200: "Ok"})
|
|
@borrow_api.expect(borrow_model)
|
|
def put(self, id):
|
|
session = mongo_client.start_session()
|
|
session.start_transaction()
|
|
try:
|
|
borrow = borrowcollection.find_one({"id": id}, session=session)
|
|
if None is not borrow:
|
|
return Response(
|
|
json.dumps({"error": "Borrow already used"}),
|
|
status=404,
|
|
mimetype="application/json",
|
|
)
|
|
borrow_api.payload["id"] = id
|
|
user = get_user(borrow_api.payload["userid"])
|
|
if not user.exists:
|
|
return Response(
|
|
json.dumps({"error": "User not found"}),
|
|
status=404,
|
|
mimetype="application/json",
|
|
)
|
|
book = bookcollection.find_one(
|
|
{"isbn": borrow_api.payload["isbn"]}, session=session
|
|
)
|
|
if book is None:
|
|
return Response(
|
|
json.dumps({"error": "Book not found"}),
|
|
status=404,
|
|
mimetype="application/json",
|
|
)
|
|
if book["nr_available"] < 1:
|
|
return Response(
|
|
json.dumps({"error": "Book is not available yet"}),
|
|
status=404,
|
|
mimetype="application/json",
|
|
)
|
|
borrow_api.payload["borrow_date"] = dateutil.parser.parse(
|
|
borrow_api.payload["borrow_date"]
|
|
)
|
|
borrow_api.payload["max_return_date"] = dateutil.parser.parse(
|
|
borrow_api.payload["max_return_date"]
|
|
)
|
|
borrow_api.payload.pop("return_date", None)
|
|
borrowcollection.insert_one(borrow_api.payload, session=session)
|
|
bookcollection.update_one(
|
|
{"isbn": borrow_api.payload["isbn"]},
|
|
{"$inc": {"nr_available": -1}},
|
|
session=session,
|
|
)
|
|
del borrow_api.payload["_id"]
|
|
db_entry = borrowcollection.find_one({"id": id}, session=session)
|
|
session.commit_transaction()
|
|
except Exception as e:
|
|
session.end_session()
|
|
return Response(
|
|
json.dumps({"error": str(e)}, default=str),
|
|
status=500,
|
|
mimetype="application/json",
|
|
)
|
|
|
|
session.end_session()
|
|
return Response(
|
|
json.dumps(db_entry, default=str), status=200, mimetype="application/json"
|
|
)
|
|
|
|
|
|
@borrow_api.route("")
|
|
class BorrowList(Resource):
|
|
@borrow_api.marshal_with(borrow_model, as_list=True)
|
|
@borrow_api.expect(pagination_parser, validate=True)
|
|
def get(self):
|
|
args = pagination_parser.parse_args(request)
|
|
data = (
|
|
borrowcollection.find()
|
|
.sort("id", 1)
|
|
.limit(args["limit"])
|
|
.skip(args["offset"])
|
|
)
|
|
extracted = [
|
|
{
|
|
"id": d["id"],
|
|
"userid": d["userid"],
|
|
"isbn": d["isbn"],
|
|
"borrow_date": d["borrow_date"],
|
|
"return_date": d["return_date"] if "return_date" in d else None,
|
|
"max_return_date": d["max_return_date"],
|
|
}
|
|
for d in data
|
|
]
|
|
return extracted
|
|
|
|
|
|
@book_api.route("/<string:isbn>")
|
|
class Book(Resource):
|
|
def get(self, isbn):
|
|
book = bookcollection.find_one({"isbn": isbn})
|
|
if None is book:
|
|
return Response(
|
|
json.dumps({"error": "Book not found"}),
|
|
status=404,
|
|
mimetype="application/json",
|
|
)
|
|
del book["_id"]
|
|
return Response(json.dumps(book), status=200, mimetype="application/json")
|
|
|
|
@book_api.doc(responses={200: "Ok"})
|
|
@book_api.expect(book_model)
|
|
def put(self, isbn):
|
|
book_api.payload["isbn"] = isbn
|
|
try:
|
|
bookcollection.insert_one(book_api.payload)
|
|
except errors.DuplicateKeyError:
|
|
return Response(
|
|
json.dumps({"error": "Isbn already exists"}),
|
|
status=404,
|
|
mimetype="application/json",
|
|
)
|
|
del book_api.payload["_id"]
|
|
return Response(
|
|
json.dumps(book_api.payload), status=200, mimetype="application/json"
|
|
)
|
|
|
|
def delete(self, isbn):
|
|
bookcollection.delete_one({"isbn": isbn})
|
|
return Response("", status=200, mimetype="application/json")
|
|
|
|
|
|
@book_api.route("")
|
|
class BookList(Resource):
|
|
@book_api.marshal_with(book_model, as_list=True)
|
|
@book_api.expect(pagination_parser, validate=True)
|
|
def get(self):
|
|
args = pagination_parser.parse_args(request)
|
|
books = (
|
|
bookcollection.find()
|
|
.sort("id", 1)
|
|
.limit(args["limit"])
|
|
.skip(args["offset"])
|
|
)
|
|
extracted = [
|
|
{
|
|
"isbn": d["isbn"],
|
|
"name": d["name"],
|
|
"author": d["author"],
|
|
"publisher": d["publisher"],
|
|
"nr_available": d["nr_available"],
|
|
}
|
|
for d in books
|
|
]
|
|
return extracted
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
mongo_client.admin.command("replSetInitiate")
|
|
except errors.OperationFailure as e:
|
|
logger.error("Error setting mongodb replSetInitiate error: {0}".format(str(e)))
|
|
bookcollection.insert_one({"isbn": 0})
|
|
bookcollection.delete_one({"isbn": 0})
|
|
borrowcollection.insert_one({"id": 0})
|
|
borrowcollection.delete_one({"id": 0})
|
|
|
|
bookcollection.create_index("isbn", unique=True)
|
|
# starts the app in debug mode, bind on all ip's and on port 5000
|
|
app.run(debug=True, host="0.0.0.0", port=5000)
|
|
|