diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..643b9c8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+test/test.py
+test/test.db
+__pycache__
+.vscode
+.idea
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/PythonRESTapi.iml b/.idea/PythonRESTapi.iml
new file mode 100644
index 0000000..e469c68
--- /dev/null
+++ b/.idea/PythonRESTapi.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..e7ffb93
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..bb14381
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..2c6c77b
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "python.pythonPath": "/usr/sbin/python3.9",
+ "python.formatting.provider": "black"
+}
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3661f3e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,22 @@
+# REST API application made with flask.
+Contains <> branches
+
+Final and stable application is in the master branch
+
+NOTE: Use a virtual environment with python 3.9 to run the application
+
+## INSATLLATION
+
+Run the following command to install dependencies:
+
+```
+pip install -r requirements.txt
+```
+
+## RUN
+
+Run the following command to start the application:
+
+```
+python app.py
+```
\ No newline at end of file
diff --git a/__pycache__/blacklist.cpython-39.pyc b/__pycache__/blacklist.cpython-39.pyc
new file mode 100644
index 0000000..1a3d504
Binary files /dev/null and b/__pycache__/blacklist.cpython-39.pyc differ
diff --git a/__pycache__/database.cpython-39.pyc b/__pycache__/database.cpython-39.pyc
new file mode 100644
index 0000000..eeefcfc
Binary files /dev/null and b/__pycache__/database.cpython-39.pyc differ
diff --git a/__pycache__/item.cpython-39.pyc b/__pycache__/item.cpython-39.pyc
new file mode 100644
index 0000000..19f4cec
Binary files /dev/null and b/__pycache__/item.cpython-39.pyc differ
diff --git a/__pycache__/security.cpython-39.pyc b/__pycache__/security.cpython-39.pyc
new file mode 100644
index 0000000..0289371
Binary files /dev/null and b/__pycache__/security.cpython-39.pyc differ
diff --git a/__pycache__/user.cpython-39.pyc b/__pycache__/user.cpython-39.pyc
new file mode 100644
index 0000000..5c46dce
Binary files /dev/null and b/__pycache__/user.cpython-39.pyc differ
diff --git a/app.py b/app.py
index 5fbfd70..effbe0e 100644
--- a/app.py
+++ b/app.py
@@ -1,10 +1,135 @@
-from flask import Flask
-# creating an app from Flask class
-app = Flask(__name__)
-# letting the application know what requests it will understand
-@app.route('/')
-def home():
- return "Hello World!"
-
-# run the app in specified folder
-app.run(port=5000)
\ No newline at end of file
+from flask import Flask, jsonify
+from flask_restful import Api
+from flask_jwt_extended import JWTManager
+from marshmallow import ValidationError
+
+from ma import ma
+from resources.user import (
+ UserRegister,
+ User,
+ UserLogin,
+ UserLogout,
+ TokenRefresh,
+)
+from resources.item import Item, ItemList
+from resources.store import Store, StoreList
+from blacklist import BLACKLIST
+from database import db
+
+app = Flask(__name__)
+
+app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database/data.db"
+
+app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False # turns of flask_sqlalchemy modification tracker
+app.config["PROPAGATE_EXCEPTIONS"] = True # if flask_jwt raises an error, the flask app will check the error if this is set to true
+app.config["JWT_BLACKLIST_ENABLED"] = True
+app.config["JWT_BLACKLIST_TOKEN_CHECKS"] = [
+ "access",
+ "refresh",
+] # both access and refresh tokens will be denied for the user ids
+
+app.secret_key = "komraishumtirkomchuri"
+# app.config["JWT_SECRET_KEY"] = "YOUR KEY HERE"
+
+api = Api(app)
+
+
+@app.errorhandler(ValidationError)
+def handle_marshmallow_validation(err):
+ return jsonify(err.messages), 400
+
+
+@app.before_first_request
+def create_tables():
+ db.create_all()
+ # above function creates all the tables before the 1st request is made
+ # unless they exist already
+
+
+# JWT() creates a new endpoint: /auth
+# we send username and password to /auth
+# JWT() gets the username and password, and sends it to authenticate function
+# the authenticate function maps the username and checks the password
+# if all goes well, the authenticate function returns user
+# which is the identity or jwt(or token)
+# jwt = JWT(app, authenticate, identity)
+jwt = JWTManager(app) # JwtManager links up to the application, doesn't create /auth point
+
+
+# below function returns True, if the token that is sent is in the blacklist
+@jwt.token_in_blocklist_loader
+def check_if_token_in_blacklist(jwt_header, jwt_data):
+ print("Log Message:", jwt_data)
+ return jwt_data["jti"] in BLACKLIST
+
+
+@jwt.additional_claims_loader # modifies the below function, and links it with JWTManager, which in turn is linked with our app
+def add_claims_to_jwt(identity):
+ if identity == 1: # instead of hard-coding this, we should read it from a config file or database
+ return {"is_admin": True}
+
+ return {"is_admin": False}
+
+
+# JWT Configurations
+@jwt.expired_token_loader
+def expired_token_callback():
+ return (
+ jsonify({"description": "The token has expired.", "error": "token_expired"}),
+ 401,
+ )
+
+
+@jwt.invalid_token_loader
+def invalid_token_callback(error):
+ return (
+ jsonify({"description": "Signature verification failed.", "error": "invalid_token"}),
+ 401,
+ )
+
+
+@jwt.unauthorized_loader
+def missing_token_callback(error): # when no jwt is sent
+ return (
+ jsonify(
+ {
+ "description": "Request doesn't contain a access token.",
+ "error": "authorization_required",
+ }
+ ),
+ 401,
+ )
+
+
+@jwt.needs_fresh_token_loader
+def token_not_fresh_callback(self, callback):
+ # print("Log:", callback)
+ return (
+ jsonify({"description": "The token is not fresh.", "error": "fresh_token_required"}),
+ 401,
+ )
+
+
+@jwt.revoked_token_loader
+def revoked_token_callback(self, callback):
+ # print("Log:", callback)
+ return (
+ jsonify({"description": "The token has been revoked.", "error": "token_revoked"}),
+ 401,
+ )
+
+
+api.add_resource(Item, "/item/")
+api.add_resource(Store, "/store/")
+api.add_resource(ItemList, "/items")
+api.add_resource(StoreList, "/stores")
+api.add_resource(UserRegister, "/register")
+api.add_resource(User, "/user/")
+api.add_resource(UserLogin, "/login")
+api.add_resource(UserLogout, "/logout")
+api.add_resource(TokenRefresh, "/refresh")
+
+if __name__ == "__main__":
+ db.init_app(app)
+ ma.init_app(app) # tells marshmallow that it should be communicating with this flask app
+ app.run(port=5000, debug=True)
diff --git a/blacklist.py b/blacklist.py
new file mode 100644
index 0000000..ec72982
--- /dev/null
+++ b/blacklist.py
@@ -0,0 +1,2 @@
+BLACKLIST = set() # user ids that will be denied access
+
diff --git a/database.py b/database.py
new file mode 100644
index 0000000..d43f174
--- /dev/null
+++ b/database.py
@@ -0,0 +1,3 @@
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
diff --git a/database/data.db b/database/data.db
new file mode 100644
index 0000000..a710185
Binary files /dev/null and b/database/data.db differ
diff --git a/ma.py b/ma.py
new file mode 100644
index 0000000..cf7687a
--- /dev/null
+++ b/ma.py
@@ -0,0 +1,3 @@
+from flask_marshmallow import Marshmallow
+
+ma = Marshmallow()
diff --git a/models/__init__.py b/models/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/models/__pycache__/__init__.cpython-39.pyc b/models/__pycache__/__init__.cpython-39.pyc
new file mode 100644
index 0000000..9c283e0
Binary files /dev/null and b/models/__pycache__/__init__.cpython-39.pyc differ
diff --git a/models/__pycache__/item.cpython-39.pyc b/models/__pycache__/item.cpython-39.pyc
new file mode 100644
index 0000000..d73f2cc
Binary files /dev/null and b/models/__pycache__/item.cpython-39.pyc differ
diff --git a/models/__pycache__/store.cpython-39.pyc b/models/__pycache__/store.cpython-39.pyc
new file mode 100644
index 0000000..878dd17
Binary files /dev/null and b/models/__pycache__/store.cpython-39.pyc differ
diff --git a/models/__pycache__/user.cpython-39.pyc b/models/__pycache__/user.cpython-39.pyc
new file mode 100644
index 0000000..c7f78db
Binary files /dev/null and b/models/__pycache__/user.cpython-39.pyc differ
diff --git a/models/item.py b/models/item.py
new file mode 100644
index 0000000..6b87894
--- /dev/null
+++ b/models/item.py
@@ -0,0 +1,36 @@
+from typing import List
+
+from database import db
+
+
+class ItemModel(db.Model): # tells SQLAlchemy that it is something that will be saved to database and will be retrieved from database
+
+ __tablename__ = "items"
+
+ # Columns
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), nullable=False, unique=True)
+ price = db.Column(db.Float(precision=2), nullable=False) # precision: numbers after decimal point
+
+ store_id = db.Column(db.Integer, db.ForeignKey("stores.id"), nullable=False)
+ store = db.relationship("StoreModel")
+
+ # searches the database for items using name
+ @classmethod
+ def find_item_by_name(cls, name: str) -> "ItemModel":
+ # return cls.query.filter_by(name=name) # SELECT name FROM __tablename__ WHERE name=name
+ # this function would return a ItemModel object
+ return cls.query.filter_by(name=name).first() # SELECT name FROM __tablename__ WHERE name=name LIMIT 1
+
+ @classmethod
+ def find_all(cls) -> List["ItemModel"]:
+ return cls.query.all()
+
+ # method to insert or update an item into database
+ def save_to_database(self) -> None:
+ db.session.add(self) # session here is a collection of objects that wil be written to database
+ db.session.commit()
+
+ def delete_from_database(self) -> None:
+ db.session.delete(self)
+ db.session.commit()
diff --git a/models/store.py b/models/store.py
new file mode 100644
index 0000000..deae614
--- /dev/null
+++ b/models/store.py
@@ -0,0 +1,35 @@
+from typing import List
+
+from database import db
+
+
+class StoreModel(db.Model): # tells SQLAlchemy that it is something that will be saved to database and will be retrieved from database
+
+ __tablename__ = "stores"
+
+ # Columns
+ id = db.Column(db.Integer, primary_key=True)
+ name = db.Column(db.String(80), nullable=False, unique=True)
+
+ items = db.relationship("ItemModel", lazy="dynamic") # this will allow us to check which item is in store whose id is equal to it's id.
+ # lazy="dynamic" tells sqlalchemy to not create seperate objects for each item that is created
+
+ # searches the database for items using name
+ @classmethod
+ def find_store_by_name(cls, name: str) -> "StoreModel":
+ # return cls.query.filter_by(name=name) # SELECT name FROM __tablename__ WHERE name=name
+ # this function would return a StoreModel object
+ return cls.query.filter_by(name=name).first() # SELECT name FROM __tablename__ WHERE name=name LIMIT 1
+
+ @classmethod
+ def find_all(cls) -> List["StoreModel"]:
+ return cls.query.all()
+
+ # method to insert or update an item into database
+ def save_to_database(self) -> None:
+ db.session.add(self) # session here is a collection of objects that wil be written to database
+ db.session.commit()
+
+ def delete_from_database(self) -> None:
+ db.session.delete(self)
+ db.session.commit()
diff --git a/models/user.py b/models/user.py
new file mode 100644
index 0000000..100f098
--- /dev/null
+++ b/models/user.py
@@ -0,0 +1,40 @@
+from enum import unique
+from typing import Dict, Union
+
+from database import db
+
+UserJSON = Dict[str, Union[int, str]]
+
+
+class UserModel(db.Model):
+
+ __tablename__ = "users" # will be used to tell sqlalchemy the table name for users
+
+ # table columns for users table
+ id = db.Column(db.Integer, primary_key=True)
+ username = db.Column(db.String(80), nullable=False, unique=True)
+ password = db.Column(db.String(80), nullable=False)
+
+ # def __init__(self, username: str, password: str):
+ # self.username = username
+ # self.password = password
+
+ # json() function is no longer required when marshmallow is used
+ # def json(self) -> UserJSON:
+ # return {"id": self.id, "name": self.username}
+
+ @classmethod
+ def find_by_username(cls, username: str) -> "UserModel":
+ return cls.query.filter_by(username=username).first()
+
+ @classmethod
+ def find_by_id(cls, _id: int) -> "UserModel":
+ return cls.query.filter_by(id=_id).first()
+
+ def save_to_database(self) -> None:
+ db.session.add(self)
+ db.session.commit()
+
+ def delete_from_database(self) -> None:
+ db.session.delete(self)
+ db.session.commit()
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..242289e
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,7 @@
+Flask
+Flask-RESTful
+Flask_-JWT-Extended
+Flask-SQLAlchemy
+Marshmallow
+Flask-Marshmallow
+Marshmallow-SQLAlchemy
\ No newline at end of file
diff --git a/resources/__init__.py b/resources/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/resources/__pycache__/__init__.cpython-39.pyc b/resources/__pycache__/__init__.cpython-39.pyc
new file mode 100644
index 0000000..a900378
Binary files /dev/null and b/resources/__pycache__/__init__.cpython-39.pyc differ
diff --git a/resources/__pycache__/item.cpython-39.pyc b/resources/__pycache__/item.cpython-39.pyc
new file mode 100644
index 0000000..624e8a7
Binary files /dev/null and b/resources/__pycache__/item.cpython-39.pyc differ
diff --git a/resources/__pycache__/store.cpython-39.pyc b/resources/__pycache__/store.cpython-39.pyc
new file mode 100644
index 0000000..d63844a
Binary files /dev/null and b/resources/__pycache__/store.cpython-39.pyc differ
diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc
new file mode 100644
index 0000000..d9c38d2
Binary files /dev/null and b/resources/__pycache__/user.cpython-39.pyc differ
diff --git a/resources/item.py b/resources/item.py
new file mode 100644
index 0000000..82306b3
--- /dev/null
+++ b/resources/item.py
@@ -0,0 +1,109 @@
+from typing import Dict
+from flask import request
+from flask_restful import Resource
+from flask_jwt_extended import (
+ jwt_required,
+ get_jwt,
+ get_jwt_identity,
+)
+
+from models.item import ItemModel
+from schemas.item import ItemSchema
+
+BLANK_ERROR = "'{}' cannot be blank"
+ITEM_NOT_FOUND = "item not found."
+NAME_ALREADY_EXISTS = "item '{}' already exists"
+ERROR_INSERTING = "An error occured while inserting the item."
+ITEM_DELETED = "Item deleted"
+item_schema = ItemSchema()
+item_list_schema = ItemSchema(many=True)
+
+
+class Item(Resource):
+
+ # TO GET ITEM WITH NAME
+ @classmethod
+ @jwt_required()
+ def get(cls, name: str) -> Dict:
+ item = ItemModel.find_item_by_name(name)
+ if item:
+ return item_schema.dump(item), 200
+
+ return {"message": ITEM_NOT_FOUND}, 404
+
+ # TO POST AN ITEM
+ @classmethod
+ @jwt_required(fresh=True)
+ def post(cls, name: str):
+ # if there already exists an item with "name", show a messege, and donot add the item
+ if ItemModel.find_item_by_name(name):
+ return {"messege": NAME_ALREADY_EXISTS.format(name)}, 400
+
+ item_json = request.get_json()
+ item_json["name"] = name # this part is necessary to populate the payload with item name
+
+ item = item_schema.load(item_json)
+
+ # TODO: put these in above try except
+ try:
+ item.save_to_database()
+ except:
+ return {"messege": ERROR_INSERTING}, 500
+
+ return item_schema.dump(item), 201 # 201 is for CREATED status
+
+ # TO DELETE AN ITEM
+ @classmethod
+ @jwt_required()
+ def delete(cls, name: str):
+ claims = get_jwt()
+
+ if not claims["is_admin"]:
+ return {"message": "Admin privilages required"}, 401
+
+ item = ItemModel.find_item_by_name(name)
+ if item:
+ item.delete_from_database()
+ return {"messege": ITEM_DELETED}
+
+ # if doesn't exist, skip deleting
+ return {"messege": ITEM_NOT_FOUND}, 400
+
+ # TO ADD OR UPDATE AN ITEM
+ @classmethod
+ def put(cls, name: str):
+ item_json = request.get_json()
+
+ item = ItemModel.find_item_by_name(name)
+
+ # if item is not available, add it
+ if item is None:
+ item_json["name"] = name
+ item = item_schema.load(item_json)
+
+ # if item exists, update it
+ else:
+ item.price = item_json["price"]
+ item.store_id = item_json["store_id"]
+
+ # whether item is changed or inserted, it has to be saved to db
+ item.save_to_database()
+ return item_schema.dump(item), 200
+
+
+class ItemList(Resource):
+ @classmethod
+ @jwt_required(optional=True)
+ def get(cls):
+ user_id = get_jwt_identity()
+ items = [item_list_schema.dump(ItemModel.find_all())]
+
+ # if user id is given, then display full details
+ if user_id:
+ return {"items": items}, 200
+
+ # else display only item name
+ return {
+ "items": [item["name"] for item in items],
+ "message": "Login to view more data.",
+ }, 200
diff --git a/resources/store.py b/resources/store.py
new file mode 100644
index 0000000..15b7a15
--- /dev/null
+++ b/resources/store.py
@@ -0,0 +1,50 @@
+from flask import request
+from flask_restful import Resource
+
+from schemas.store import StoreSchema
+from models.store import StoreModel
+
+store_schema = StoreSchema()
+store_list_schema = StoreSchema(many=True)
+
+
+class Store(Resource):
+ @classmethod
+ def get(cls, name: str):
+ store = StoreModel.find_store_by_name(name)
+ if store:
+ return store_schema.dump(store), 200
+
+ return {"message": "Store not found."}, 404
+
+ @classmethod
+ def post(cls, name: str):
+ if StoreModel.find_store_by_name(name):
+ return {"message": "Store alrady exists."}, 400
+
+ store = StoreModel(name=name) # since __init__() method was removed from StoreModel, the name needs to be passed manually as such
+ # as StoreModel is a model subclass, model class acceps keyword arguements and maps them to columns
+ # so here, passing name as a keyword argument will map to name key in store model
+
+ try:
+ store.save_to_database()
+ except:
+ return {"message": "An error occured while creating the store."}, 500
+
+ return store_schema.dump(store), 201
+
+ @classmethod
+ def delete(cls, name: str):
+ store = StoreModel.find_store_by_name(name)
+ if store:
+ store.delete_from_database()
+ return {"message": "store deleted."}
+
+ return {"message": "store don't exist"}
+
+
+class StoreList(Resource):
+ @classmethod
+ def get(cls):
+ # return {"item": list(map(lambda x: x.json(), ItemModel.query.all()))}
+ return {"stores": [store_list_schema.dump(StoreModel.find_all())]}, 200
diff --git a/resources/user.py b/resources/user.py
new file mode 100644
index 0000000..75ff8fe
--- /dev/null
+++ b/resources/user.py
@@ -0,0 +1,116 @@
+from flask import request
+from flask_restful import Resource
+from marshmallow import ValidationError
+from werkzeug.security import safe_str_cmp
+from flask_jwt_extended import (
+ create_access_token,
+ create_refresh_token,
+ jwt_required,
+ get_jwt_identity,
+ get_jwt,
+)
+
+from models.user import UserModel
+from schemas.user import UserSchema
+from blacklist import BLACKLIST
+
+# extracted parser variable for global use, and made it private
+# _user_parser = reqparse.RequestParser()
+# _user_parser.add_argument(
+# "username",
+# type=str,
+# required=True,
+# help="This field cannot be empty"
+# )
+# _user_parser.add_argument(
+# "password",
+# type=str,
+# required=True,
+# help="This field cannot be empty"
+# )
+
+user_schema = UserSchema()
+
+
+# New user registration class
+class UserRegister(Resource):
+
+ # calls to post a new user (new user registration)
+ @classmethod
+ def post(cls):
+ user = user_schema.load(request.get_json())
+
+ # First check if that user is present or not
+ if UserModel.find_by_username(user.username):
+ # if exists, then don't add
+ return {"message": "An user with that username already exists."}, 400
+
+ # user = UserModel(data["username"], data["password"])
+ # user = UserModel(**user_data) # since parser only takes in username and password, only those two will be added.
+ # flask_marshmallow already creates a user model, so we need not do it manually
+ user.save_to_database()
+
+ return {"messege": "User added successfully."}, 201
+
+
+class User(Resource):
+ @classmethod
+ def get(cls, user_id: int):
+
+ user = UserModel.find_by_id(user_id)
+ if not user:
+ return {"message": "User not found."}, 404
+
+ return user_schema.dump(user), 200
+
+ @classmethod
+ def delete(cls, user_id: int):
+ user = UserModel.find_by_id(user_id)
+ if not user:
+ return {"message": "User not found."}, 404
+
+ user.delete_from_database()
+ return {"message": "User deleted."}, 200
+
+
+class UserLogin(Resource):
+ @classmethod
+ def post(cls):
+ # get data from parser
+ user_data = user_schema.load(request.get_json())
+
+ # find user in database
+ user = UserModel.find_by_username(user_data.username)
+
+ # check password
+ # this here is what authenticate() function used to do
+ if user and safe_str_cmp(user.password, user_data.password):
+
+ # create access and refresh tokens
+ access_token = create_access_token(identity=user.id, fresh=True) # here, identity=user.id is what identity() used to do previously
+ refresh_token = create_refresh_token(identity=user.id)
+ print("user logged in")
+
+ return {"access_token": access_token, "refresh_token": refresh_token}, 200
+
+ return {"message": "Invalid credentials."}, 401 # Unauthorized
+
+
+class UserLogout(Resource):
+ # Loggig out requirees jwt as if user is not logged in they cannot log out
+ @classmethod
+ @jwt_required()
+ def post(cls):
+ jti = get_jwt()["jti"] # jti is JWT ID, unique identifier for a JWT
+ BLACKLIST.add(jti)
+ return {"message": "Successfully logged out."}, 200
+
+
+class TokenRefresh(Resource):
+ @classmethod
+ @jwt_required(refresh=True)
+ def post(cls):
+ current_user = get_jwt_identity()
+ new_token = create_access_token(identity=current_user, fresh=False) # fresh=Flase means that user have logged in days ago.
+
+ return {"access_token": new_token}, 200
diff --git a/schemas/item.py b/schemas/item.py
new file mode 100644
index 0000000..8df7cc0
--- /dev/null
+++ b/schemas/item.py
@@ -0,0 +1,16 @@
+from xml.etree.ElementInclude import include
+from ma import ma
+from models.item import ItemModel
+from models.store import StoreModel
+
+
+class ItemSchema(ma.SQLAlchemyAutoSchema):
+ # If the below Meta class is excluded, while fetching user information, we also fetch
+ # the user password. So, password is included in the load_only tuple so that password field
+ # is only loaded and not displayed.
+ class Meta:
+ model = ItemModel
+ load_instance = True
+ load_only = ("store",) # makes 'store' field load_only. store_id will be displayed
+ dump_only = ("id",) # makes 'id' field dump_only.
+ include_fk = True
diff --git a/schemas/store.py b/schemas/store.py
new file mode 100644
index 0000000..a6a7444
--- /dev/null
+++ b/schemas/store.py
@@ -0,0 +1,14 @@
+from ma import ma
+from models.store import StoreModel
+from models.item import ItemModel
+from schemas.item import ItemSchema
+
+
+class StoreSchema(ma.SQLAlchemyAutoSchema):
+ items = ma.Nested(ItemSchema, many=True)
+
+ class Meta:
+ model = StoreModel
+ load_instance = True
+ dump_only = ("id",) # makes 'id' field dump_only.
+ include_fk = True
diff --git a/schemas/user.py b/schemas/user.py
new file mode 100644
index 0000000..0ff2e2f
--- /dev/null
+++ b/schemas/user.py
@@ -0,0 +1,14 @@
+from ma import ma
+from models.user import UserModel
+
+
+# use SQLAlchemyAutoSchema. ModelSchema is deprecated
+class UserSchema(ma.SQLAlchemyAutoSchema):
+ # If the below Meta class is excluded, while fetching user information, we also fetch
+ # the user password. So, password is included in the load_only tuple so that password field
+ # is only loaded and not displayed.
+ class Meta:
+ model = UserModel
+ load_instance = True
+ load_only = ("password",) # makes 'password' field load_only
+ dump_only = ("id",) # makes 'id' field dump_only.