From 7f6f246073f237c241696533a330906093c91d10 Mon Sep 17 00:00:00 2001 From: Subhadeep Mandal Date: Sat, 15 Jan 2022 20:30:16 +0530 Subject: [PATCH 01/42] Added get and post methods --- app.py | 70 ++++++++++++++++++++++++++++++++++++++++++-- templates/index.html | 28 ++++++++++++++++++ 2 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 templates/index.html diff --git a/app.py b/app.py index 5fbfd70..e6a70ab 100644 --- a/app.py +++ b/app.py @@ -1,10 +1,74 @@ -from flask import Flask +from flask import Flask, jsonify, request, render_template # creating an app from Flask class app = Flask(__name__) -# letting the application know what requests it will understand + +stores = [ + { + "name": "My store", + "items": [ + { + "name": "My item", + "price": 12.99 + } + ] + } +] + @app.route('/') def home(): - return "Hello World!" + return render_template('index.html') + +# POST /store data: {name:} +@app.route('/store', methods=['POST']) +def create_store(): + request_data = request.get_json() + new_store = { + 'name': request_data['name'], + 'items': [], + } + stores.append(new_store) + return jsonify(new_store) + +# GET /store/ +@app.route('/store/') # http://127.0.0.1:5000/store/some_name +def get_store(name): # here 'some_name' has to be the 'name' + # iterate over stores + # return the name when matches + # if none matches, return error message + for store in stores: + if store['name'] == name: + return jsonify(store) + else: + return jsonify({"messege":"Store not found!"}) + +# GET /store +@app.route('/store') +def get_stores(): + return jsonify({'stores': stores}) + +# POST /store//item {name:, price:} +@app.route('/store//item', methods=['POST']) +def create_item_in_store(name): + request_data = request.get_json() + for store in stores: + if store["name"] == name: + new_item = { + "name": request_data["name"], + "price": request_data["price"] + } + store['items'].append(new_item) + return jsonify(new_item) + + return jsonify({"messege": "Store not found"}) + +# GET /store//item +@app.route('/store//item') +def get_item_from_store(name): + for store in stores: + if store['name'] == name: + return jsonify({"items":store['items']}) + + return jsonify({"messege": "Store not found"}) # run the app in specified folder app.run(port=5000) \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..e3e7cf3 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,28 @@ + + + + + + + + + +
+ Hello, world! +
+ + \ No newline at end of file From ee3d1210caf3cd0d5c4afd91ab08bd08fc8c361f Mon Sep 17 00:00:00 2001 From: Subhadeep Mandal Date: Sat, 15 Jan 2022 20:31:37 +0530 Subject: [PATCH 02/42] Added README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..901d1b5 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# REST API application made with flask. +Contains <> branches + +Final and stable application is in the master branch \ No newline at end of file From 0e3861813c33e0e4ab49db1c47cfd617703a186c Mon Sep 17 00:00:00 2001 From: Subhadeep Mandal Date: Sun, 16 Jan 2022 15:23:33 +0530 Subject: [PATCH 03/42] Initial Commit --- app.py | 77 ++++++---------------------------------------------------- 1 file changed, 8 insertions(+), 69 deletions(-) diff --git a/app.py b/app.py index e6a70ab..bd6f87e 100644 --- a/app.py +++ b/app.py @@ -1,74 +1,13 @@ -from flask import Flask, jsonify, request, render_template -# creating an app from Flask class -app = Flask(__name__) - -stores = [ - { - "name": "My store", - "items": [ - { - "name": "My item", - "price": 12.99 - } - ] - } -] - -@app.route('/') -def home(): - return render_template('index.html') - -# POST /store data: {name:} -@app.route('/store', methods=['POST']) -def create_store(): - request_data = request.get_json() - new_store = { - 'name': request_data['name'], - 'items': [], - } - stores.append(new_store) - return jsonify(new_store) +from flask import Flask +from flask_restful import Resource, Api -# GET /store/ -@app.route('/store/') # http://127.0.0.1:5000/store/some_name -def get_store(name): # here 'some_name' has to be the 'name' - # iterate over stores - # return the name when matches - # if none matches, return error message - for store in stores: - if store['name'] == name: - return jsonify(store) - else: - return jsonify({"messege":"Store not found!"}) - -# GET /store -@app.route('/store') -def get_stores(): - return jsonify({'stores': stores}) - -# POST /store//item {name:, price:} -@app.route('/store//item', methods=['POST']) -def create_item_in_store(name): - request_data = request.get_json() - for store in stores: - if store["name"] == name: - new_item = { - "name": request_data["name"], - "price": request_data["price"] - } - store['items'].append(new_item) - return jsonify(new_item) +app = Flask(__name__) +api = Api(app) - return jsonify({"messege": "Store not found"}) +class Student(Resource): + def get(self, name): + return {"student": name} -# GET /store//item -@app.route('/store//item') -def get_item_from_store(name): - for store in stores: - if store['name'] == name: - return jsonify({"items":store['items']}) - - return jsonify({"messege": "Store not found"}) +api.add_resource(Student, '/student/') # http://127.0.0.1:5000/student/Bob -# run the app in specified folder app.run(port=5000) \ No newline at end of file From 335d0a82771df22eb3430e9feac1607265d20056 Mon Sep 17 00:00:00 2001 From: Subhadeep Mandal Date: Tue, 18 Jan 2022 11:51:23 +0530 Subject: [PATCH 04/42] added requirements.txt --- app.py | 31 ++++++++++++++++++++++++++----- requiements.txt | 3 +++ 2 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 requiements.txt diff --git a/app.py b/app.py index bd6f87e..44099cf 100644 --- a/app.py +++ b/app.py @@ -1,13 +1,34 @@ -from flask import Flask +from flask import Flask, request from flask_restful import Resource, Api app = Flask(__name__) api = Api(app) -class Student(Resource): +items = [] + +class Item(Resource): def get(self, name): - return {"student": name} + item = next(filter(lambda x: x["name"] == name, items), None) + return {"item": item}, 200 if item else 404 + + def post(self, name): + # if there already exists an item with "name", show a messege, and donot add the item + if next(filter(lambda x: x["name"] == name, items), None): + return {"messege": f"item {name} already exists"} ,400 + + data = request.get_json() # get_json(force=True) means, we don't need a content type header + item = { # flask will look into the content, and format the data. + "name": name, + "price": data["price"] + } + items.append(item) + return item, 201 # 201 is for CREATED status + +class ItemList(Resource): + def get(self): + return {"items": items} -api.add_resource(Student, '/student/') # http://127.0.0.1:5000/student/Bob +api.add_resource(Item, '/item/') +api.add_resource(ItemList, '/items') -app.run(port=5000) \ No newline at end of file +app.run(port=5000, debug=True) \ No newline at end of file diff --git a/requiements.txt b/requiements.txt new file mode 100644 index 0000000..0213c81 --- /dev/null +++ b/requiements.txt @@ -0,0 +1,3 @@ +Flask +Flask-RESTful +Flask-JWT \ No newline at end of file From c85794b40e40cb228e0b1056f98ff5ba19158e3d Mon Sep 17 00:00:00 2001 From: Subhadeep Mandal Date: Tue, 18 Jan 2022 13:55:34 +0530 Subject: [PATCH 05/42] added authentication to app --- __pycache__/security.cpython-39.pyc | Bin 0 -> 858 bytes __pycache__/user.cpython-39.pyc | Bin 0 -> 442 bytes app.py | 15 +++++++++++++++ security.py | 20 ++++++++++++++++++++ user.py | 6 ++++++ 5 files changed, 41 insertions(+) create mode 100644 __pycache__/security.cpython-39.pyc create mode 100644 __pycache__/user.cpython-39.pyc create mode 100644 security.py create mode 100644 user.py diff --git a/__pycache__/security.cpython-39.pyc b/__pycache__/security.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3e357eac8809c82e2a937ab800d5c5e8fec0f52 GIT binary patch literal 858 zcmb7C&2AGh5cb%cWSee63S78xfWuxQetJSxg#=tfMS#O9D00^(-H^>LwpUfq=G49c zF965BlCMZey~2$P%q&V3^?<}k9?y(DpTC*0?De_~YOtjJZEQ+v{bm|wbhR5p0lK*c2$IXsCr`- z_b(Bf#o!|=-|MM$W}4Dvv<)N zYM8ScKJ$;km|YNdSRvL@=K6wrK78PzGA+E{s9*QhlgW`O<~ntDaOdA7cK+%9Qu5t5`tlWuSx-6AW6F*A{3?e=Na}%w9w*$Gt9kzysHxO^+$45TQ z6oI-`%zdBU&D4gcxq+w{?v!%1D5Of-f6uQmGq>;>5wO%C1|c+&QM{Avooe&pldfjN z%|M&7%^uz})Y7Cykk<4mKtHLeq{-iUrI`}V#fJxZp{j*Gr0-}cD}qRLe+NOI_j%a< E1M-5ZO#lD@ literal 0 HcmV?d00001 diff --git a/__pycache__/user.cpython-39.pyc b/__pycache__/user.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21686752f0d90a86a99c0817360d0922c3554b93 GIT binary patch literal 442 zcmY*T!AiqG5S`sEH53CP;@x8|{QwauhLnDGkV!F;HgUH_D!JJY@HhOWy$b$- zH&4E4Db!iso1NWxZzhl90U=Ii-}wjfmlTI#P>cY73c4dERvX}5XT*9!i7D{@FEpwV z;LkuMWr``&tXOX-Gi;#gh2EntUC~3sKIAUS8^E7~I@OV-PJ>lvz?unKl_n8)UmM3_?8|zV8C$WF&7IhOlItV=Uhw5~?3(AYSo-OBy?kmG_qUUWysf6< zHEh=*vbI`OOKW8nFDg=foVQpO9`1irOhT(nw>Au{oj1Iy<$i4K%POyTKQ4lPJNtl+ g Date: Tue, 18 Jan 2022 21:36:16 +0530 Subject: [PATCH 06/42] added delete and put methods --- __pycache__/security.cpython-39.pyc | Bin 858 -> 858 bytes app.py | 50 ++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/__pycache__/security.cpython-39.pyc b/__pycache__/security.cpython-39.pyc index a3e357eac8809c82e2a937ab800d5c5e8fec0f52..9d73ba2fbd937f52e589dcff5a187512be95d73e 100644 GIT binary patch delta 25 fcmcb`c8iTGk(ZZ?0SLaIn#lE!(Pc9yV;~a%T^$Dh delta 25 fcmcb`c8iTGk(ZZ?0SG?jPvrW?n6jCZF^~xWS-1wj diff --git a/app.py b/app.py index 2682f2a..ec608c0 100644 --- a/app.py +++ b/app.py @@ -1,5 +1,5 @@ from flask import Flask, request -from flask_restful import Resource, Api +from flask_restful import Resource, Api, reqparse from flask_jwt import JWT, jwt_required from security import authenticate, identity @@ -21,17 +21,28 @@ items = [] class Item(Resource): + + parser = reqparse.RequestParser() + parser.add_argument("price", + type=float, + required=True, + help="This field cannot be blank" + ) + + # TO GET ITEM WITH NAME @jwt_required() def get(self, name): item = next(filter(lambda x: x["name"] == name, items), None) return {"item": item}, 200 if item else 404 + # TO POST AN ITEM def post(self, name): # if there already exists an item with "name", show a messege, and donot add the item if next(filter(lambda x: x["name"] == name, items), None): return {"messege": f"item {name} already exists"} ,400 - data = request.get_json() # get_json(force=True) means, we don't need a content type header + data = Item.parser.parse_args() + # data = request.get_json() # get_json(force=True) means, we don't need a content type header item = { # flask will look into the content, and format the data. "name": name, "price": data["price"] @@ -39,7 +50,42 @@ def post(self, name): items.append(item) return item, 201 # 201 is for CREATED status + # TO DELETE AN ITEM + def delete(self, name): + # use the items variable avalable globally + global items + # check if the item exists + item = next(filter(lambda x: x["name"] == name ,items), None) + # if doesn't exist, skip deleting + if item is None: + return {"messege": "Item don't exist"}, 400 + + # store all the elements of items variable into local item + # except the one that was selected to be deleted + items = list(filter(lambda x: x["name"] != name, items)) + return {"messege": "Item deleted"} + + # TO ADD OR UPDATE AN ITEM + def put(self, name): + data = Item.parser.parse_args() + # data = request.get_json() + item = next(filter(lambda x: x["name"] == name ,items), None) + # if item is not available, add it + if item is None: + item = { + "name": name, + "price": data["price"] + } + items.append(item) + # if item exists, update it + else: + item.update(data) + return item + + class ItemList(Resource): + + # TO GET ALL ITEMS def get(self): return {"items": items} From 697673844913cc0e1e271e34b47c7fde5290e282 Mon Sep 17 00:00:00 2001 From: Subhadeep Mandal Date: Tue, 18 Jan 2022 21:44:09 +0530 Subject: [PATCH 07/42] modified README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 901d1b5..52cfe84 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # REST API application made with flask. Contains <> branches -Final and stable application is in the master branch \ No newline at end of file +Final and stable application is in the master branch + +Use a virtual environment with python 3.9 to run the application \ No newline at end of file From 1b65a9807c809086878f067248724af27bcb50de Mon Sep 17 00:00:00 2001 From: Subhadeep Mandal Date: Thu, 20 Jan 2022 12:21:11 +0530 Subject: [PATCH 08/42] minor changes --- .vscode/settings.json | 3 +++ __pycache__/security.cpython-39.pyc | Bin 858 -> 899 bytes __pycache__/user.cpython-39.pyc | Bin 442 -> 483 bytes app.py | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..56fb648 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "/usr/sbin/python" +} \ No newline at end of file diff --git a/__pycache__/security.cpython-39.pyc b/__pycache__/security.cpython-39.pyc index 9d73ba2fbd937f52e589dcff5a187512be95d73e..81cd5eb73d6371ebd8cf04a32c922e544ca71a26 100644 GIT binary patch delta 92 zcmcb`*38b8$ji&c00cXpPUQOQ;-_C!nx~(env$8QUtF4$k(iR2TA=TmQktAtl9`{U ukeHXE5S*V@Ql40psvl64pOuzs1It$ji&c00iGpP2~D3t?FVG6Ht_&m6}{q91~Dkl98Vm_a<5TXD8 diff --git a/__pycache__/user.cpython-39.pyc b/__pycache__/user.cpython-39.pyc index 21686752f0d90a86a99c0817360d0922c3554b93..c11d9fabeb5c1010e173558713b90a687f7aa257 100644 GIT binary patch delta 91 zcmdnR{Fs?5k(ZZ?0SIXAQk`s delta 50 zcmaFNyo;GDk(ZZ?0SE-rCUQNJR!p&q2`I|XN=+^)jtQtN$;i(Oat#hiEXa(RY{0k~ E0A+Fy&j0`b diff --git a/app.py b/app.py index ec608c0..b981a5c 100644 --- a/app.py +++ b/app.py @@ -43,7 +43,7 @@ def post(self, name): data = Item.parser.parse_args() # data = request.get_json() # get_json(force=True) means, we don't need a content type header - item = { # flask will look into the content, and format the data. + item = { # flask will look into the content, and format the data. "name": name, "price": data["price"] } From c8007f1e975a538dc87414f072a247bc21a70db4 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Thu, 20 Jan 2022 20:46:20 +0530 Subject: [PATCH 09/42] added database and user registration --- .vscode/settings.json | 2 +- README.md | 10 +- __pycache__/security.cpython-39.pyc | Bin 899 -> 602 bytes __pycache__/user.cpython-39.pyc | Bin 483 -> 2034 bytes app.py | 190 ++++++++++++++-------------- create_tables.py | 10 ++ requiements.txt | 4 +- security.py | 32 ++--- templates/index.html | 54 ++++---- test/data.db | Bin 0 -> 12288 bytes test/test.py | 32 +++++ user.py | 93 +++++++++++++- 12 files changed, 272 insertions(+), 155 deletions(-) create mode 100644 create_tables.py create mode 100644 test/data.db create mode 100644 test/test.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 56fb648..7e7f74b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "python.pythonPath": "/usr/sbin/python" + "python.pythonPath": "/usr/sbin/python3.9" } \ No newline at end of file diff --git a/README.md b/README.md index 52cfe84..1331531 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# REST API application made with flask. -Contains <> branches - -Final and stable application is in the master branch - +# REST API application made with flask. +Contains <> branches + +Final and stable application is in the master branch + Use a virtual environment with python 3.9 to run the application \ No newline at end of file diff --git a/__pycache__/security.cpython-39.pyc b/__pycache__/security.cpython-39.pyc index 81cd5eb73d6371ebd8cf04a32c922e544ca71a26..c398288b90f32042c7a50f6440c0306b27b74392 100644 GIT binary patch literal 602 zcmYjO%}(1u5Z+mv#6&@&y>USiH%k?M0R)vQt`Tb5i!avV*)$9Bk9OCJT$vMGc#CrE zEBV?Byh2aRI7-AwGdtg|=bQP)>gZ@9$X>7B)H4Y2kHJTi1iq5HFBDK9tVIDS0OxWo ziy#ds4={WZX^00nqVoX93lT?eR3T#Mr%o+(=Df}Fq9z>vblT=jyt9B3_HX3wjDi<` zpc60fAUkPKJ;l4AgGb6cxuDiW5A!&*T$CO!O^MlUn>EB+szO`l?xR{cx2Y_~K_Bg( zLVgMv{uOpi;Qu_aO_>xLjY?c|yHcoiom^v+D{rcDrb?VGs-@p3tCJgB-Ra!BAUDN|)YHz2s~V(EVJf51J3yd?@&Et; literal 899 zcmb7Cy^hmB5Z+xoi7)4b9MI6Eph*++bLHuT1lNWW0SZef+IaR7hxmuxwW1)W%Dn+E z0Oh>_&*Bz=bTu6f%oybqU4g_%JD!={`S$ZSD?6PwL;L#uhkPj*`$oyWxtKh|W!|C# z27Jj99&%2xSc=369nO>k4!F?RIx`-+(1g|{Q|=44`H1Coa=0%B5Q*4wVh|_tF!TIna6=qQK4hI9_H(+eFAZ0qb$i!J-p;6 zE~XFB{XhAdAo3S6=oZ1;u+}uW8nq65f+sz8=3sE6%|lEq`^-xm+!wJCrOosPbYUP`e(8s!R9Au;Z!kAS#wyxm+(Z z31Rc^{w-$u9v&kCh6G~V<8@^;I0%ncO27Z8irH{C(E7La5uVc|Y1kl0qq|g~Cs+ST j-M@EBJtdk;<((uM6ianV@77S4&wZ!;+jYC5E4=0(eq*_* diff --git a/__pycache__/user.cpython-39.pyc b/__pycache__/user.cpython-39.pyc index c11d9fabeb5c1010e173558713b90a687f7aa257..b04ff652ee70d85231b07deb2b65de705d8d2949 100644 GIT binary patch literal 2034 zcmcgt&2k$>5T2R+wfqws2-pEqdk6(GE{Q5`DT19!2}sH)DanQ^UAERcBYVwiSDu+I zM`C?)u24LJe9WWp3Uj46_As{L2$xpP6qg7IUTV98#x1ql5YujxciK7_nZw}7kuTV}Gw?X=lVPdrB9%yS?Rs-yumfFffe>UsITBGsz>WTKDCqSxu~CF87(MqA^F3F9~`G9AZgMWGwjnZs);Xv>AQVd;EQbo~!7`?>fDX)e*XT z+s}OysFOU?;!6{xWl=!P#!oA$N@)V|RHT&_gK#Kx3MdHUrFp3Yyg?_;m4(h zH}4mgmeFaZk0O1P=vOR_l3a>}Pa^RYa8Rx6H;81Oi=RB~_B*}3=wWy7@g>85xWBvK z=|`V+_jh-sfrr^P_%JWI<#qR(_b5Ta{Futw&(lqQd zPUNAo?|hyvH(-lgMPU>B9nc1TT8>7UmhZx#=}9bi#>Hy%CIep2FyoF(rR|Hb)Ta%$ z!j{|yg95V!iAb2`A@J#^7%H9yC?!^X d>&_gp2#5b+%!FUfN%}RWru~Y9bd{}a{SDw@(vAQC delta 242 zcmeyw|Cl*8k(ZZ?0SI-@NIwQ~kO4E0;{e3PLO{ZuA%!7@u?2{mnW7j{n1UHJ znUjIyD1Zsba{%IEk%t Dkf') -api.add_resource(ItemList, '/items') - +from flask import Flask, request +from flask_restful import Resource, Api, reqparse +from flask_jwt import JWT, jwt_required + +from security import authenticate, identity +from user import UserRegister + +app = Flask(__name__) + +app.secret_key = "komraishumtirkomchuri" + +api = Api(app) + +# JWT() creates a new endpoint: /auth +# we send an 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) + +items = [] + +class Item(Resource): + + parser = reqparse.RequestParser() + parser.add_argument("price", + type=float, + required=True, + help="This field cannot be blank" + ) + + # TO GET ITEM WITH NAME + @jwt_required() + def get(self, name): + item = next(filter(lambda x: x["name"] == name, items), None) + return {"item": item}, 200 if item else 404 + + # TO POST AN ITEM + def post(self, name): + # if there already exists an item with "name", show a messege, and donot add the item + if next(filter(lambda x: x["name"] == name, items), None): + return {"messege": f"item {name} already exists"} ,400 + + data = Item.parser.parse_args() + # data = request.get_json() # get_json(force=True) means, we don't need a content type header + item = { # flask will look into the content, and format the data. + "name": name, + "price": data["price"] + } + items.append(item) + return item, 201 # 201 is for CREATED status + + # TO DELETE AN ITEM + def delete(self, name): + # use the items variable avalable globally + global items + # check if the item exists + item = next(filter(lambda x: x["name"] == name ,items), None) + # if doesn't exist, skip deleting + if item is None: + return {"messege": "Item don't exist"}, 400 + + # store all the elements of items variable into local item + # except the one that was selected to be deleted + items = list(filter(lambda x: x["name"] != name, items)) + return {"messege": "Item deleted"} + + # TO ADD OR UPDATE AN ITEM + def put(self, name): + data = Item.parser.parse_args() + # data = request.get_json() + item = next(filter(lambda x: x["name"] == name ,items), None) + # if item is not available, add it + if item is None: + item = { + "name": name, + "price": data["price"] + } + items.append(item) + # if item exists, update it + else: + item.update(data) + return item + + +class ItemList(Resource): + + # TO GET ALL ITEMS + def get(self): + return {"items": items} + +api.add_resource(Item, '/item/') +api.add_resource(ItemList, '/items') +api.add_resource(UserRegister, '/register') + app.run(port=5000, debug=True) \ No newline at end of file diff --git a/create_tables.py b/create_tables.py new file mode 100644 index 0000000..4889a69 --- /dev/null +++ b/create_tables.py @@ -0,0 +1,10 @@ +import sqlite3 + +connection = sqlite3.connect('./test/data.db') +cursor = connection.cursor() + +create_table = "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username text, password text)" +cursor.execute(create_table) + +connection.commit() +connection.close() \ No newline at end of file diff --git a/requiements.txt b/requiements.txt index 0213c81..aaa2275 100644 --- a/requiements.txt +++ b/requiements.txt @@ -1,3 +1,3 @@ -Flask -Flask-RESTful +Flask +Flask-RESTful Flask-JWT \ No newline at end of file diff --git a/security.py b/security.py index 893ddf4..d8db52d 100644 --- a/security.py +++ b/security.py @@ -1,20 +1,12 @@ -from werkzeug.security import safe_str_cmp -from user import User - -users = [ - User(1, "bob", "1234") -] - -username_mapping = {u.username: u for u in users} # mapping the users using set comprehension - -userid_mapping = {u.id: u for u in users} - -def authenticate(username, password): - user = username_mapping.get(username, None) - # if user and user.password == password: - if user and safe_str_cmp(user.password, password): - return user - -def identity(payload): # payload is the contents of jwt - userid = payload["identity"] - return userid_mapping.get(userid, None) +from werkzeug.security import safe_str_cmp +from user import User + +def authenticate(username, password): + user = User.find_by_username(username) + # if user and user.password == password: + if user and safe_str_cmp(user.password, password): + return user + +def identity(payload): # payload is the contents of jwt + user_id = payload["identity"] + return User.find_by_id(user_id) diff --git a/templates/index.html b/templates/index.html index e3e7cf3..fb11bf2 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,28 +1,28 @@ - - - - - - - - - -
- Hello, world! -
- + + + + + + + + + +
+ Hello, world! +
+ \ No newline at end of file diff --git a/test/data.db b/test/data.db new file mode 100644 index 0000000000000000000000000000000000000000..06724902c8b6e9fa88f3d2b97be4929e9ba0c30c GIT binary patch literal 12288 zcmeI&!AiqG5C-7cBq*XKf`nY>*wP|sy$NEJMT>2%F(N%lYPMQv8{19Mt8d`b`2@az z2k-6%daxJI%0FZ`nPp}V-)$h*SM5=uc$m*8X~9j}CL!pUnTR~IT(g!T%!fT&hRuHq zk9Kd*E2hfvkH~*B8v+6lfB*y_009U<00Izz00bcL=LCArT3D}(evuBwO3%h-l)lqy zwoudi)zm$WWt2!xqLa4dmD=hwov4QCtWCvw7>45Yu)K1i)m)dG^P{~y;_4{lRyUDn zGUi^~>O^s$FJzyio8+?9H4PovO&V;A?fqP+m!iQ>sn*Z=JS)?hoy}eUo%~P#!@Lj> zfB*y_009U<00Izz00bZa0SNpVfi2ggAb7~f+1`HhVAJtvCm7@dn^c5HVW20Y;xX0P q(9ZwD|044sAOHafKmY;|fB*y_009U<00I#BPXZgl4Mq76f%^@U|5-Bt literal 0 HcmV?d00001 diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..eb1b92f --- /dev/null +++ b/test/test.py @@ -0,0 +1,32 @@ +import sqlite3 + +connection = sqlite3.connect('./test/data.db') + +cursor = connection.cursor() + +create_table = "CREATE TABLE IF NOT EXISTS users (id int PRIMARY KEY, username text, password text)" +cursor.execute(create_table) + +user1 = (1, "bob", "1234") + +insert_query = "INSERT INTO users VALUES (?, ?, ?)" + +cursor.execute(insert_query, user1) + +users = [ + (2, "rolf", "2345"), + (3, "smith", "3456"), + (4, "john", "4567"), + (5, "clint", "5678") +] + +cursor.executemany(insert_query, users) + +select_query = "SELECT username, password FROM users" +for row in cursor.execute(select_query): + print(row) + +# commiting the changes to database +connection.commit() +# closing the connection to database +connection.close() \ No newline at end of file diff --git a/user.py b/user.py index 354e8ff..dcca8df 100644 --- a/user.py +++ b/user.py @@ -1,6 +1,87 @@ -class User: - def __init__(self, _id, username, password): - self.id = _id - self.username = username - self.password = password - \ No newline at end of file +import sqlite3 +from flask_restful import Resource, reqparse + +class User: + + def __init__(self, _id, username, password): + self.id = _id + self.username = username + self.password = password + + @classmethod + def find_by_username(cls, username): + connection = sqlite3.connect('./test/data.db') + cursor = connection.cursor() + + query = "SELECT * FROM users WHERE username=?" + result = cursor.execute(query, (username,)) # parameters must be in the form of tuple. + + row = result.fetchone() + if row: + # user = cls(row[0], row[1], row[2]) + user = cls(*row) # is similar to passing all values of row + else: + user = None + + connection.close() + return user + + @classmethod + def find_by_id(cls, _id): + connection = sqlite3.connect('./test/data.db') + cursor = connection.cursor() + + query = "SELECT * FROM users WHERE id=?" + result = cursor.execute(query, (_id,)) # parameters must be in the form of tuple. + + row = result.fetchone() + if row: + # user = cls(row[0], row[1], row[2]) + user = cls(*row) # is similar to passing all values of row + else: + user = None + + connection.close() + return user + +# New user registraction class +class UserRegister(Resource): + # Creating a request parser + parser = reqparse.RequestParser() + # Adding username argument to parser + parser.add_argument( + "username", + type=str, + required=True, + help="This field cannot be empty" + ) + # Adding password argument to parser + parser.add_argument( + "password", + type=str, + required=True, + help="This field cannot be empty" + ) + + # calls to post a new user (new user registration) + def post(self): + data = UserRegister.parser.parse_args() + # First check if that user is present or not + if User.find_by_username(data["username"]): + # if exists, then don't add + return {"message": "An user with that username already exists."}, 400 + + # else...continue + # 1. Connect to database + connection = sqlite3.connect('./test/data.db') + # 2. Create database cursor + cursor = connection.cursor() + # 3. SQL query to inser new useer information + query = "INSERT INTO users VALUES (NULL, ?, ?)" + + cursor.execute(query, (data["username"], data["password"])) + # 4. Commit the changes and close the connection to database + connection.commit() + connection.close() + + return {"messege": "User added successfully."}, 201 \ No newline at end of file From 191924f1c07b055354770a6854fb733777388769 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Thu, 20 Jan 2022 22:42:17 +0530 Subject: [PATCH 10/42] added database integration to get and post --- __pycache__/item.cpython-39.pyc | Bin 0 -> 2794 bytes __pycache__/user.cpython-39.pyc | Bin 2034 -> 2034 bytes app.py | 81 ++---------------------- create_tables.py | 5 +- item.py | 106 ++++++++++++++++++++++++++++++++ test/data.db | Bin 12288 -> 12288 bytes 6 files changed, 116 insertions(+), 76 deletions(-) create mode 100644 __pycache__/item.cpython-39.pyc create mode 100644 item.py diff --git a/__pycache__/item.cpython-39.pyc b/__pycache__/item.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e9bc6466ec2cc3f9c37118e862b6bcbd72d88cf GIT binary patch literal 2794 zcmb7G&2Jk;6rY)${jlSF^n=nAwOt8B*;2(og~Uf`gNh5OirdC1AYZJ_&bV>9-gRf! zYGQ3psYEYG91s_Zs;q-Ks!0+y_ zd*SpHA%9}y@Q*`d5x(pP5P}FAlQxztrhV3CeXDIzY+Et!+ikn=v>nstvD^3B9wlEB zVGHLm5stLCt#$?4uJEAknRZps76~eQusUf5Oxri&Fngd~B_9rDmP3!XEVFc|BB?#- z9E2*90oAqjM|lU8dP<52XCMAK&{%}eSS?AL3eslM8uJ~}wxzx92r6B{geCY-oD%76 zPpsUg?TW0H7D9bWR7CYLY1hP*s6ja`ro{}DGh!C3p3{6OmwjyyR4uC8o5Cs{Ng6_~QRxowE zU@gF3upSjw7wQ3TYPT=5EW9sAvpulH$1S_*FcAyAUnn7f)3I(Z5gi=ttZsIaurGJP zluX85&9S99j_ne49QJ#5v#y3oy)Q*CtY^d3wNOYos5itg3iDo?_+cXaR@%)Ug-X`% zsB~RMc~-x(nXjeEa-+2p4tjN5Z(*>ht@|=Rh9~hr5LTs}ayIgoa2SSOI=KrP=xOHU z%TUS5n2O2C$83jeS%Pix!WvrzFKCzUur0f=k-n;o(G?M z3TOdp1kiGu&t?|tIXG2agn76iR!4rTajS8C#lPs^Sib$OkCtct_un*@8$M$5#WjTo zP=F-aL$S-$9CqM|0!O>q!x-jV)^3z02|%HpXsEJOX;*H@XqW@tT{tO#BefmHDR{1{ zttigSGP*jM2FR*2u%x!Z=hdcml+1>4t}T^5GQ5=H1a3VI0(G;~AP`lLaXQ01EE%H? zb)NHoMyCwQuTcLA<`^di^8zaS-loo>mlgCki0a=h@HUR#meX}Mwg7J!DcC-`TUO4; zws65mdBMjHjN8HkPVBK;IE5uDz=eg{UADu3BR=*FSA1MCr6sDym!~GaEWwv;B*Guf z`eCeOC^mh$(aZ8|?`Pn^V6WZ;bJZIlM(3BBt;X_-ztmj0Jt6SBFK^vzwEXw3UGlF5 z2I;*&KsBfu@D%!*c1#{ox@yW!sP1P95moaj5McF|(Tmc4zn81C&Liv#EanxhYu z?r|PF%@iMN7)S?%X+B7^d;$70R6)j0vZ@74J$LMqz0-!hQN?hE+AqSK_e0D#EL1=U?!V(OGjUA!C`# zg)4Cyg>iNjDy5!!8+GPT#}{dGAup*`??Cr!nr5|#dLPtF$S+|H(LP{6)uDM8yFLIh zs+F4=gG9g~3(5@$+-@(T#F{;ZnA)L~Bvr_TZo#l# z00e>kKd5{SyzsU!@j7EpH5lecu!nij#A4u?uu|`0#|0E0qWB2KWf0m42LqW1?FgI?0OFdGwdg{$^dH2anBuIv#NA*mw-|;nAKj#+-{wYu68>8 zR19MY^;)O%a2Upz%{v{1BndI8;#%Y)Zo(MWFZ6 z55sT~z6|%@f54cCCv8h0;ar=m^cFbkk&$v8XCpy^=EOuh1Tbv;SC)`}J_D zd6>{LTyPV-vzx%%r!c5KL-9EZ)Iak0i!jiI&n)Cw3fsYyNwNqqJn|H7p)jxMY10Ak txh(GvV}$`zl2s`O;IUWIfqxXL~9b#T%4bSejZ~5|CI_oLW?*HrbY4oKbUf HDEnLhi>428 delta 53 zcmeyw|B0U~k(ZZ?0SL6KH*(pqG2WW&&h}LB7H?2$VQFe{NkC#zacWVK`ea*naYn7l Hq3m-3ru7hE diff --git a/app.py b/app.py index 0f51f54..1d2185f 100644 --- a/app.py +++ b/app.py @@ -1,9 +1,10 @@ -from flask import Flask, request -from flask_restful import Resource, Api, reqparse -from flask_jwt import JWT, jwt_required +from flask import Flask +from flask_restful import Api +from flask_jwt import JWT from security import authenticate, identity from user import UserRegister +from item import Item, ItemList app = Flask(__name__) @@ -19,79 +20,9 @@ # which is the identity or jwt(or token) jwt = JWT(app, authenticate, identity) -items = [] - -class Item(Resource): - - parser = reqparse.RequestParser() - parser.add_argument("price", - type=float, - required=True, - help="This field cannot be blank" - ) - - # TO GET ITEM WITH NAME - @jwt_required() - def get(self, name): - item = next(filter(lambda x: x["name"] == name, items), None) - return {"item": item}, 200 if item else 404 - - # TO POST AN ITEM - def post(self, name): - # if there already exists an item with "name", show a messege, and donot add the item - if next(filter(lambda x: x["name"] == name, items), None): - return {"messege": f"item {name} already exists"} ,400 - - data = Item.parser.parse_args() - # data = request.get_json() # get_json(force=True) means, we don't need a content type header - item = { # flask will look into the content, and format the data. - "name": name, - "price": data["price"] - } - items.append(item) - return item, 201 # 201 is for CREATED status - - # TO DELETE AN ITEM - def delete(self, name): - # use the items variable avalable globally - global items - # check if the item exists - item = next(filter(lambda x: x["name"] == name ,items), None) - # if doesn't exist, skip deleting - if item is None: - return {"messege": "Item don't exist"}, 400 - - # store all the elements of items variable into local item - # except the one that was selected to be deleted - items = list(filter(lambda x: x["name"] != name, items)) - return {"messege": "Item deleted"} - - # TO ADD OR UPDATE AN ITEM - def put(self, name): - data = Item.parser.parse_args() - # data = request.get_json() - item = next(filter(lambda x: x["name"] == name ,items), None) - # if item is not available, add it - if item is None: - item = { - "name": name, - "price": data["price"] - } - items.append(item) - # if item exists, update it - else: - item.update(data) - return item - - -class ItemList(Resource): - - # TO GET ALL ITEMS - def get(self): - return {"items": items} - api.add_resource(Item, '/item/') api.add_resource(ItemList, '/items') api.add_resource(UserRegister, '/register') -app.run(port=5000, debug=True) \ No newline at end of file +if __name__ == "__main__": + app.run(port=5000, debug=True) \ No newline at end of file diff --git a/create_tables.py b/create_tables.py index 4889a69..6afb122 100644 --- a/create_tables.py +++ b/create_tables.py @@ -3,7 +3,10 @@ connection = sqlite3.connect('./test/data.db') cursor = connection.cursor() -create_table = "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username text, password text)" +create_table = "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username text, password text)" +cursor.execute(create_table) + +create_table = "CREATE TABLE IF NOT EXISTS items (name text, price real)" cursor.execute(create_table) connection.commit() diff --git a/item.py b/item.py new file mode 100644 index 0000000..4dfffae --- /dev/null +++ b/item.py @@ -0,0 +1,106 @@ +import sqlite3 + +from flask import Flask, request +from flask_restful import Resource, reqparse +from flask_jwt import jwt_required + +class Item(Resource): + + parser = reqparse.RequestParser() + parser.add_argument("price", + type=float, + required=True, + help="This field cannot be blank" + ) + + # TO GET ITEM WITH NAME + @jwt_required() + def get(self, name): + item = self.find_item_by_name(name) + if item: + return item + + return {"message": "item not found."}, 404 + + # searches the database for items using name + @classmethod + def find_item_by_name(cls, name): + connection = sqlite3.connect('./test/data.db') + cursor = connection.cursor() + + query = "SELECT * FROM items WHERE name=?" + result = cursor.execute(query, (name,)) + + row = result.fetchone() + connection.close() + + if row: + return { + "item": { + "name": row[0], + "price": row[1] + } + } + + # TO POST AN ITEM + def post(self, name): + # if there already exists an item with "name", show a messege, and donot add the item + if self.find_item_by_name(name): + return {"messege": f"item {name} already exists"} ,400 + + data = Item.parser.parse_args() + # data = request.get_json() # get_json(force=True) means, we don't need a content type header + item = { # flask will look into the content, and format the data. + "name": name, + "price": data["price"] + } + + connection = sqlite3.connect("./test/data.db") + cursor = connection.cursor() + + query = "INSERT INTO items VALUES (?, ?)" + cursor.execute(query, (item["name"], item["price"])) + + connection.commit() + connection.close() + + return item, 201 # 201 is for CREATED status + + # TO DELETE AN ITEM + def delete(self, name): + # use the items variable avalable globally + global items + # check if the item exists + item = next(filter(lambda x: x["name"] == name ,items), None) + # if doesn't exist, skip deleting + if item is None: + return {"messege": "Item don't exist"}, 400 + + # store all the elements of items variable into local item + # except the one that was selected to be deleted + items = list(filter(lambda x: x["name"] != name, items)) + return {"messege": "Item deleted"} + + # TO ADD OR UPDATE AN ITEM + def put(self, name): + data = Item.parser.parse_args() + # data = request.get_json() + item = next(filter(lambda x: x["name"] == name ,items), None) + # if item is not available, add it + if item is None: + item = { + "name": name, + "price": data["price"] + } + items.append(item) + # if item exists, update it + else: + item.update(data) + return item + + +class ItemList(Resource): + + # TO GET ALL ITEMS + def get(self): + return {"items": items} \ No newline at end of file diff --git a/test/data.db b/test/data.db index 06724902c8b6e9fa88f3d2b97be4929e9ba0c30c..8edd7f0f3822d827500a1897594190c689202a4c 100644 GIT binary patch delta 172 zcmZojXh@i#&B!!S$Br$Afqx!<%w|D>H+Ol;!f;*5zUiAg!BnI);Y#bAQjImp#9 z#8n~0(aFbE0W74Tk(Zd8s!)Ng%ATjBeN)bQht86 MgZi(B?(0OV0o1H8<^TWy delta 226 zcmZojXh@i#&B!=W$Bxa8fqw$O-NwRme9-|+Y~tG5jFBaYNja&-g*llesqw|Bg{7%^ z$>>~W=O9Tk$D4hKBfi`T@22 zy84Aoe#o!P#ms+=f&U}_6aH(P1qDv Date: Fri, 21 Jan 2022 17:00:31 +0530 Subject: [PATCH 11/42] implemeted delete method --- .gitignore | 2 + __pycache__/item.cpython-39.pyc | Bin 2794 -> 3025 bytes item.py | 79 ++++++++++++++++++++------------ test/data.db | Bin 12288 -> 12288 bytes test/test.db | Bin 0 -> 12288 bytes test/test.py | 14 +++--- 6 files changed, 60 insertions(+), 35 deletions(-) create mode 100644 .gitignore create mode 100644 test/test.db diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f714eb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +./test/test.py +./test/test.db \ No newline at end of file diff --git a/__pycache__/item.cpython-39.pyc b/__pycache__/item.cpython-39.pyc index 7e9bc6466ec2cc3f9c37118e862b6bcbd72d88cf..97519cb561e2b0bfa7498383f9ca3c5baa2c3338 100644 GIT binary patch delta 1616 zcmah}OK%)S5bo-E?Ck7%?2YZj2FDp7!3+c^kwb!c7{$(7AcBKzOA(fWGj`89EA7MS znH9-ut(9;jByLd)$r2pA+{_Q)#t#U$aMP?$JhOM z^uaMN92jsJe&6ps4sR6ic@II{`)z7oSi-)`c3~|aIU*+Knc{mYUThs!PB_9uXYy)NF+HNWEjMUKqxS)VkG* z?Bo#w@=}fbODpTTh)rFOLuM>08uT zsJuBl-S#?c-|QGY0CtTvMgSwlSV82fV6!hU=CiD02&cj^`n>wqeA^#HEA7&yh961U zlzww_vn3-j{pdbFZ zhabt4q*NqF{jP{=Q4)#vkQPO=F_rky?P{Dzs&A0fwR;hJ>{U3F*iogfq;qPN zN**{ehn<`vcmqK}?r30#+uKn?q;_i?3mnOSRy6^qX{0S0+pXkF^`rAT45|ChY?myg z#ZZX#P;RyAQ6ovMO05|tX<@S##&JDLZZ<`hA)l-eXXR@IZxNg(xIoZnx*pMK+DURW zrrY6Q^fqw%|FUx&++l2P!35WFMw}t_b^h~FvVM3g)==1DJD~RXM7IZ*+4YGXs{_ax zOFs}6JrKhBfzyS#brN~=Z~oXFApQhTIWxS1oLN})-+YEO`k9j+o+K%;as~kz@t>!p zi)-gep-%}ryHT{`c}l!YbNz(qTPC}Z*35sF_Bw8pj-)f*LNKp`tP-t;0m0qlkXH6FQOS5Cq zM2>uLB+k`H2reiQCv)Qu0L~mioc6|{hj0Xm0}_b$#w{*4)|%gYZ{{~|-h1=LKQ8~e zSc;3qkid8Ivx~S`xL^8--nswF=IbLOUFn^Y5xpBEq4cG&dO-#W!*1t;-P*0V zmsZi=R_`cxK1wv5H$U@D^9B8UNzKCniUM39jG3APVXm`Vbk%&xmKSXV6IeiM5n$DL z&Th#E&4aN6BO92&-F{c|GgtcO^n?%inRiZ3Tq#bxfqUi;yn!Qw6$0q4kS054lmJJZ z6DtEN9T_$m6sll)?hD>B_*IMMD<=vregHiRRndIwt_{n$u!69P@FYO)HM;F~s?{18 zk*{WP0W-Y7CMZ2e|v671>>~L1jyrI zaNX7t=naO{ho1w|g@H2L1JFd%Pf3$XD%q*fY{VoPu@g1`*(d6MsX8Sfjo4}an^xl|GClp>)F0QuX_JNGAVKBer-s3eJ zx=dP$PPVSLx{bJ%T?1vpQ%@sj4mlOs?QH5waxcxadIqMa=Yv7bAO6Z)PK~JN!I~8D zGHO`AX-!oX%$qp%BEa#?#IZeGM%)a=(yP3M2bBKZW*yk*y-Aesz*1(PAjkU8@Fi*v z{ja0{aq=B|ve^~S*0Gl&p|EAu2Er!7O9(F`yaJGWaj%zjWbXBQ&@@TJr){JnWZAc3 z+h!W3)9dTo<~y-LXUzk#O*hPXP#!9L80r#wEi_tjmbDXjjMBay^&GCfhHwR8+J{!d z@fP0%}- za6}&o`&_1R*bp%I6t~(Qr<2=z4{mD<{z3MkINPwO-avR00r`*T@)X+E)7zjmVOUSX aQD{d6RW=_Mb{5fid>fhz=9|Jgt^Whj;})&} diff --git a/item.py b/item.py index 4dfffae..4c42e75 100644 --- a/item.py +++ b/item.py @@ -13,15 +13,6 @@ class Item(Resource): help="This field cannot be blank" ) - # TO GET ITEM WITH NAME - @jwt_required() - def get(self, name): - item = self.find_item_by_name(name) - if item: - return item - - return {"message": "item not found."}, 404 - # searches the database for items using name @classmethod def find_item_by_name(cls, name): @@ -42,6 +33,27 @@ def find_item_by_name(cls, name): } } + # method to insert an item into + @classmethod + def insert(cls, item): + connection = sqlite3.connect("./test/data.db") + cursor = connection.cursor() + + query = "INSERT INTO items VALUES (?, ?)" + cursor.execute(query, (item["name"], item["price"])) + + connection.commit() + connection.close() + + # TO GET ITEM WITH NAME + @jwt_required() + def get(self, name): + item = self.find_item_by_name(name) + if item: + return item + + return {"message": "item not found."}, 404 + # TO POST AN ITEM def post(self, name): # if there already exists an item with "name", show a messege, and donot add the item @@ -54,32 +66,31 @@ def post(self, name): "name": name, "price": data["price"] } - - connection = sqlite3.connect("./test/data.db") - cursor = connection.cursor() - - query = "INSERT INTO items VALUES (?, ?)" - cursor.execute(query, (item["name"], item["price"])) - - connection.commit() - connection.close() + try: + self.insert(item) + except: + return {"messege": "An error occured."} + return item, 201 # 201 is for CREATED status # TO DELETE AN ITEM def delete(self, name): - # use the items variable avalable globally - global items - # check if the item exists - item = next(filter(lambda x: x["name"] == name ,items), None) + # check if there exists any item by name: "name" + # if exists then delete it + if self.find_item_by_name(name): + connection = sqlite3.connect("./test/data.db") + cursor = connection.cursor() + + query = "DELETE FROM items WHERE name=?" + cursor.execute(query, (name,)) + + connection.commit() + connection.close() + return {"messege": "Item deleted"} + # if doesn't exist, skip deleting - if item is None: - return {"messege": "Item don't exist"}, 400 - - # store all the elements of items variable into local item - # except the one that was selected to be deleted - items = list(filter(lambda x: x["name"] != name, items)) - return {"messege": "Item deleted"} + return {"messege": "Item don't exist"}, 400 # TO ADD OR UPDATE AN ITEM def put(self, name): @@ -103,4 +114,14 @@ class ItemList(Resource): # TO GET ALL ITEMS def get(self): + items = [] + connection = sqlite3.connect("./test/data.db") + cursor = connection.cursor() + + query = "SELECT * FROM items" + for row in cursor.execute(query): + items.append({"name": row[0], "price": row[1]}) + + connection.commit() + connection.close() return {"items": items} \ No newline at end of file diff --git a/test/data.db b/test/data.db index 8edd7f0f3822d827500a1897594190c689202a4c..e7ccc2c6a658f4629b74699efe46a1aa2fca2c1c 100644 GIT binary patch delta 83 zcmZojXh@hK&B!@X#+i|GW5N=7E++oF4E*o;@A9A7EGTf0-%yYN83-^ji?b(ZBxV*l dXnsA^AEWV&pOIOVJt;pw+d===L-%ze)c}mC7V-c9 delta 50 zcmZojXh@hK&B!uQ#+i|2W5N=7Hb(yU4E*mm3kp2opBTU@$j`_u%AS;;pY5Rj>!JHP Gk!k>YD-V|d diff --git a/test/test.db b/test/test.db new file mode 100644 index 0000000000000000000000000000000000000000..fef2752436dc5b6d33dd3291e6afeca5141448ec GIT binary patch literal 12288 zcmeI$ze~eF6bJD8k*HPD6C`B4(Lxbw{j~^I2`E<3T9ATJQk&99n$sl3uKrQ}365_5 z37wn-K^z>s3v}+P@_q2$-MdEu`D~ZVi)NIEa-h;tkjpw*C6tl_DG4D=C)YU(syEiG z(W~j_1tS|*$By_SOei9hc$xPA<`94Y1Rwwb2tWV=5P$##AOL}XAaG3=Jn+Ln z{i3Z@*n+q3Q4%HnaFX4}`exq^#P`tys*xe#v+`I&JYp#D|!QSMjX32nav` z0uX=z1Rwwb2tWV=5P$##euF^Swy5XYH?jUbu)VXp=U5hXUF%j2lP%qsxf%0p6y?LZ zK5&>BvsA@{P2I0htF-3!RIjGHW&J(2m=f_VKE=Cu(^~`tAOHafKmY;|fB*y_009U< Y00Mtdz~+?NwyBn7D(9v$#(Amq18uZWrvLx| literal 0 HcmV?d00001 diff --git a/test/test.py b/test/test.py index eb1b92f..56eab91 100644 --- a/test/test.py +++ b/test/test.py @@ -1,17 +1,17 @@ import sqlite3 -connection = sqlite3.connect('./test/data.db') +connection = sqlite3.connect('./test/test.db') cursor = connection.cursor() create_table = "CREATE TABLE IF NOT EXISTS users (id int PRIMARY KEY, username text, password text)" cursor.execute(create_table) -user1 = (1, "bob", "1234") +# user1 = (1, "bob", "1234") -insert_query = "INSERT INTO users VALUES (?, ?, ?)" +# insert_query = "INSERT INTO users VALUES (?, ?, ?)" -cursor.execute(insert_query, user1) +# cursor.execute(insert_query, user1) users = [ (2, "rolf", "2345"), @@ -20,12 +20,14 @@ (5, "clint", "5678") ] -cursor.executemany(insert_query, users) +# cursor.executemany(insert_query, users) -select_query = "SELECT username, password FROM users" +select_query = "SELECT username, password FROM users where username='gold'" for row in cursor.execute(select_query): print(row) + + # commiting the changes to database connection.commit() # closing the connection to database From 933e106ff2a5c742bd10d40f8cb5ad7317db4921 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Fri, 21 Jan 2022 17:08:05 +0530 Subject: [PATCH 12/42] minor changes --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index f714eb5..4a25664 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -./test/test.py -./test/test.db \ No newline at end of file +test/test.py +test/test.db \ No newline at end of file From 71f90dced10fe702c95396f19cfadbcb8136cace Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Fri, 21 Jan 2022 21:05:02 +0530 Subject: [PATCH 13/42] implemented database into put and get items --- __pycache__/item.cpython-39.pyc | Bin 3025 -> 3297 bytes item.py | 43 ++++++++++++++++++++++++-------- test/data.db | Bin 12288 -> 12288 bytes 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/__pycache__/item.cpython-39.pyc b/__pycache__/item.cpython-39.pyc index 97519cb561e2b0bfa7498383f9ca3c5baa2c3338..19f4cecd14bb21374652f2ff409d7da9b01d0d56 100644 GIT binary patch delta 1074 zcmZ{jO-vI(6vyYyessJ2pp=FWgLe5UZIFt}0S|~IHUU&(07GoRrePMWky59vCRkDv zxR`h$bJYY6#&Fh~C(kAth$n7%^y0;M)A-&NG@7`Z`R#oC_szUF@9no8b%gVwkj?N} z`1mFNHM|`@fQQ>hmq#t(7xot0glxbKZZZ)NL2nciAranU*|0a;BqH8ylV}!EoQ;T< zS(b{uo`SakQDtiHVNV@s-Tpm8w&5l+q~6&rDx$YSLiOuubxm8S_uaaAb$l-46e{j& z*_qAEIVD+Ga!1FU`H9R-#wq4kaUd=FKSGETx(J=WaT2%~}w>Q8X4!tTKi-(?%T#&)zC?7{}J#szdR!JqRD z9S_Q?OwO%`QK8rLT>v)E9@LH<(}a$W8GjrrCOxHbqAZ?-KojO)$~vp*=)Z5eaT`r| z8rh3j?;0;Uu9R!iSzB7FN>@0K?iTL5PN7(KWu;JDNy`-Jod4*&Yav<6ls>XLKsZbA z;zl8sJ*?gc5)Hge7^GeZkqtI3R^%v){>=-mRIQXyRVG^uY;J2D%%7%V0f#V{5W_Kj zqkP@+(gc5)PBkA|xg2F*E_Z0>a;s}XeYcV@pyK|4`0x5D>_X>qVyum!M%bg(TehK#CL{zaHkhJ8b2?P1@c;gy5h*V<$GQpqif zMv9oSkL+KNS<6Tlq=~K7roKtc@#-14tbG~pfg70 i2=`vl>`(adxJ~eS z^ZM9dcJ>nAf$tsVreZC^ySTkWTCn8s7z?nk~qc>s;6~DT| z{hW{1FBGylbUQ3ov-L)pU8tS932_n<4*NZhCnTTTjqBZ7Cg~jCKx3cQhL|*=a#}&y852 zQ1h$BLLssXh3YzQRFv)%3evIDFd?Vysi9Go+ElopFr_f9a7Dhb&u2zZWq4{PP__P; z76rQ(C8mSTScDU=m+e8kr fjuNN|V4BFuW#zNR=>K}?zD}eX0A08lYybcN delta 102 zcmZojXh@hK&B!@X#+i|GW5N=CHb(xB4E!HA3kqE3pV%NW`J=or7Zd+o2LAW_clpl% wg%9!@3Nj!A0VZZ~_T-Gj%pwQPuZQ|$G`{gOGK;b&<>zNR=>K}?zD}eX0NS!0dH?_b From 453932f64e66007a48c66f68405446d83bda4964 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Fri, 21 Jan 2022 21:08:52 +0530 Subject: [PATCH 14/42] minor changes --- test/test.db | Bin 12288 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/test.db diff --git a/test/test.db b/test/test.db deleted file mode 100644 index fef2752436dc5b6d33dd3291e6afeca5141448ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12288 zcmeI$ze~eF6bJD8k*HPD6C`B4(Lxbw{j~^I2`E<3T9ATJQk&99n$sl3uKrQ}365_5 z37wn-K^z>s3v}+P@_q2$-MdEu`D~ZVi)NIEa-h;tkjpw*C6tl_DG4D=C)YU(syEiG z(W~j_1tS|*$By_SOei9hc$xPA<`94Y1Rwwb2tWV=5P$##AOL}XAaG3=Jn+Ln z{i3Z@*n+q3Q4%HnaFX4}`exq^#P`tys*xe#v+`I&JYp#D|!QSMjX32nav` z0uX=z1Rwwb2tWV=5P$##euF^Swy5XYH?jUbu)VXp=U5hXUF%j2lP%qsxf%0p6y?LZ zK5&>BvsA@{P2I0htF-3!RIjGHW&J(2m=f_VKE=Cu(^~`tAOHafKmY;|fB*y_009U< Y00Mtdz~+?NwyBn7D(9v$#(Amq18uZWrvLx| From 68588800f1b924b92db8285501ebf4375745bf10 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Mon, 24 Jan 2022 18:56:54 +0530 Subject: [PATCH 15/42] made seperate folders for item and user --- __pycache__/security.cpython-39.pyc | Bin 602 -> 593 bytes app.py | 4 +- database.py | 3 + models/__init__.py | 0 models/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 154 bytes models/__pycache__/item.cpython-39.pyc | Bin 0 -> 1517 bytes models/__pycache__/user.cpython-39.pyc | Bin 0 -> 1182 bytes models/item.py | 48 +++++++ models/user.py | 44 ++++++ requiements.txt | 3 +- resources/__init__.py | 0 resources/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 157 bytes resources/__pycache__/item.cpython-39.pyc | Bin 0 -> 2493 bytes resources/__pycache__/user.cpython-39.pyc | Bin 0 -> 1152 bytes item.py => resources/item.py | 79 ++--------- user.py => resources/user.py | 129 ++++++------------ security.py | 6 +- test/data.db | Bin 12288 -> 12288 bytes 18 files changed, 159 insertions(+), 157 deletions(-) create mode 100644 database.py create mode 100644 models/__init__.py create mode 100644 models/__pycache__/__init__.cpython-39.pyc create mode 100644 models/__pycache__/item.cpython-39.pyc create mode 100644 models/__pycache__/user.cpython-39.pyc create mode 100644 models/item.py create mode 100644 models/user.py create mode 100644 resources/__init__.py create mode 100644 resources/__pycache__/__init__.cpython-39.pyc create mode 100644 resources/__pycache__/item.cpython-39.pyc create mode 100644 resources/__pycache__/user.cpython-39.pyc rename item.py => resources/item.py (54%) rename user.py => resources/user.py (50%) diff --git a/__pycache__/security.cpython-39.pyc b/__pycache__/security.cpython-39.pyc index c398288b90f32042c7a50f6440c0306b27b74392..211b4693ecb25d635c2e170c05788150878eb2e4 100644 GIT binary patch delta 80 zcmcb`a*>5Mk(ZZ?0SJ<$-z3hT$eYf^8CslLL{nNQYde8n5ZoeNS`tXB%uFjg`k0>kI85<&E15P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;!H$er{fgzLTq= ztFfi48;Eo?a5L8rD9X=DO)e?c52!53$j=LM4Gu{x$kflxPf5)w){l?R%*!l^kJl@x Tyv1Py6fDh2wF4Ra8HgDGVZ0*% literal 0 HcmV?d00001 diff --git a/models/__pycache__/item.cpython-39.pyc b/models/__pycache__/item.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02879b2989df6bd7942663bc597f816ed474d5d4 GIT binary patch literal 1517 zcmchXOK;RL5P)sxvFY}~Q{p8=E(n597sLS&^aZ3)36`q6fV7uriM=9KHfimwpsV!M z9udEQ!|t(v$yZKX_yL@lah4Ve;)o@WC!Vo0{$}j%>};Jt3qF49ey|Dof*-#&3qF>i ztBW8w;WQ;3@|1AKt$o6+J=UR|wn$)q1v_cRD1U8->+iv=AmB|pj8o3`Nyp+Aw_&t} z!yWGKlResTd5wE8SL0K>4x`7XVfPt5wN{A!i=2xz#w~v~)EV^0(A7y0B`rxrN(K^s z1xso3fN4AH_Jww~WD<+O(zX(5Pr`ADbU+*sS%cyGgGN6q8jr%;;hp>8D$12xt9Kjg zGT#(&p&IKu#k)M)2wUy$R?_HWFshM2T=QEy+KZwjONuDM9To0Ps7r^Z596Ny=@3&i zP8#ZX2y=irP)f3i9jJb05I$>4G#e36>7tIoR>A{K+nXxSra)6jg3#gI(Ld1?n`tyb z!?hTk`QH;z3NnI!jp;5c*@%tFuEp7|U0Nfnv`gBfW47y*&d3If3m(pxP*S>bxdizM z02|0w^ZVRX<)s2@ge8i;uH_yV2(8pvfECsHvsqGXD zcXTbzvkVw(Hy%ipOI;HmL_8>j_Ijd-fjVR_PID!KnzrIpndIsEWLc7D64_~Idm!YF zcBN2*w9uB!-yf%Ss+VLu!f>LuI}wI53oA|S5LTxiv*{f3aAdN=G#uCp_ge>@vKv)@+3vZGWxVemOz- zdgb}6u;pK0y6G!gz=D@DX=ggt=S=5eIlbd8p z$l@}X6z)xE1?4P28J;`>Qn~#X3RhpPKUryq69BES?VF3du=IO9{hbA-r2=vuH3T6q zpg4Z+!4{ORxCW+wp1X2u22*(Bv3#OP*P{qtgF%Y=OcZSox@l#Rcr7KquzEU9yGr#1 zJRw}3!)bg`5;>># zi1-c3v43f=oVf4ito?TEdF;%$<8XeyMnLwy{0I+CLcZb4v>5R60+w0^ z;DpnJw8?A288?mzH;!1Fa=Jqr=6A4@R)jb$llc4aRS@tdZN@2Q$E0ltlN;PTCP%bw zfo^fT(rxZQMpw`5DIqsAE|LgJn+WO*);cV89iXHo2}sERX=wm5P9OnoH4M#SuH6Fi zq+wTR_aId2Fq6DtYEy}%qm3Zu5;IB*;DksQw$rCxH_g3Q{zHG|vA==%^1;Rhu+p*B2&#YsQSbC(DkITt({ zOQ{-r3rbE#!0wn1S;t9&R^fH->YwIzu&B5`KtcmjlbBda-Hym3U_@vuN-`zj@{A}^6|uVZD=f}Zc?r7G zR<97UuWcz*k>uKt*`YS^_=@~9m_f(OojB#eem_7X)`PREmH-H=QJ0x?fw|OVHDHLI5?z8Xb|XH5WoNz3F>n}&?~~^%p#F(JsTzP&vXS0JeN1Ij9Zgh u{;W#kEaU&KFwkYL#c1scqt*8Ylk*sdK2zOP8LGQ>pPg0loMHxabJkPmE literal 0 HcmV?d00001 diff --git a/models/item.py b/models/item.py new file mode 100644 index 0000000..8e8f0ff --- /dev/null +++ b/models/item.py @@ -0,0 +1,48 @@ +import sqlite3 + +class ItemModel: + + def __init__(self, name, price): + self.name = name + self.price = price + + def json(self): + return {"name": self.name, "price": self.price} + + # searches the database for items using name + @classmethod + def find_item_by_name(cls, name): + connection = sqlite3.connect('./test/data.db') + cursor = connection.cursor() + + query = "SELECT * FROM items WHERE name=?" + result = cursor.execute(query, (name,)) + + row = result.fetchone() + connection.close() + + if row: + # return cls(row[0], row[1]) + return cls(*row) # same as above + + # method to insert an item into + def insert(self): + connection = sqlite3.connect("./test/data.db") + cursor = connection.cursor() + + query = "INSERT INTO items VALUES (?, ?)" + cursor.execute(query, (self.name, self.price)) + + connection.commit() + connection.close() + + def update(self): + connection = sqlite3.connect("./test/data.db") + cursor = connection.cursor() + + query = "UPDATE items SET price=? WHERE name=?" + cursor.execute(query, (self.price, self.name)) + + connection.commit() + connection.close() + diff --git a/models/user.py b/models/user.py new file mode 100644 index 0000000..18263db --- /dev/null +++ b/models/user.py @@ -0,0 +1,44 @@ +import sqlite3 + +class UserModel: + + def __init__(self, _id, username, password): + self.id = _id + self.username = username + self.password = password + + @classmethod + def find_by_username(cls, username): + connection = sqlite3.connect('./test/data.db') + cursor = connection.cursor() + + query = "SELECT * FROM users WHERE username=?" + result = cursor.execute(query, (username,)) # parameters must be in the form of tuple. + + row = result.fetchone() + if row: + # user = cls(row[0], row[1], row[2]) + user = cls(*row) # is similar to passing all values of row + else: + user = None + + connection.close() + return user + + @classmethod + def find_by_id(cls, _id): + connection = sqlite3.connect('./test/data.db') + cursor = connection.cursor() + + query = "SELECT * FROM users WHERE id=?" + result = cursor.execute(query, (_id,)) # parameters must be in the form of tuple. + + row = result.fetchone() + if row: + # user = cls(row[0], row[1], row[2]) + user = cls(*row) # is similar to passing all values of row + else: + user = None + + connection.close() + return user diff --git a/requiements.txt b/requiements.txt index aaa2275..16bcdb4 100644 --- a/requiements.txt +++ b/requiements.txt @@ -1,3 +1,4 @@ Flask Flask-RESTful -Flask-JWT \ No newline at end of file +Flask-JWT +Flask-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 0000000000000000000000000000000000000000..fbe2f604657164d0db3d87cbaf02e08d2447230a GIT binary patch literal 157 zcmYe~<>g`k0@>%U5<&E15P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;!HMer{fgzLTq= ztFfi48;Eo?a5L8rD9X=DO)e?c52!53$j=LM4Gu{x$kZ=NEzU13N=_}-kB`sH%PfhH Y*DI*J#bJ}1pHiBWY6mj=GY~TX0MO7RMgRZ+ literal 0 HcmV?d00001 diff --git a/resources/__pycache__/item.cpython-39.pyc b/resources/__pycache__/item.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d96291831ed16e4a7059604a3d187fcc2208617f GIT binary patch literal 2493 zcmZ`*&2Jk;6rY*>^u|t;wv@J%vZc`C0(Pq&5E7J>)`3cGsFH}PD`Y%abQa)N91JI%rX<2AOKHX;mRXTSv2Uez=0r~BMy~1Gsh9bYZ`w{;%YrDN z3H+F49v^+bD8Yz>7fVZ>h+su;?i z&^`iXu>vWrfM6dd}sd!@)86kMb|Tx$Aqp|b$DS_UIXL^+9=u=eZ;i5%f< zx||A+Gj4JFN1GDi?-K5C_c__6QH^`te@>#njMjKyMrZg8ufu4aHz4>~ZR5waGmuG7 ztd7pFZX_z~CnDuxFV6F#4A(@smd5#$rlsw2dr)n~gs$xkkq&wcLOZ-HXyAPZZuKsh zlI+njozNXNX7bFK?Xd}2fwXXzP;T`Z#9%LJFB3||>tb{wfoMYfY`+-hyp{Y+A=#Gn zU@1M{PjcQxkM7!bH;*%+?M+qW7?~1jU)$9fdi+l0DR}3_t#+1|?JqkwIyXPa&r552s=3 z*yfRYm@u>X4QL#0US7lvkLfSKyWcF>xQ!bx*``V44lBu+jv05(kzKZ9jp}1-&zTTP z9+CVkg(t9*au?q7`phMd0l@17eB24}(Lh)`fKUL04u&3%PK0qPMa;KD@hnlL+W!fV zlr@NIG=Dn}g_MO1i(YRC9Bl3X4U_wSz+}@iSj$;xOWd<|4Q-_k%(ENIb*0@TS3;KZ z1ioR*Idq;!XPd`mEKgz|QLD^pGczbuc>!7qXA#NMEMT*Y(K>bMNj5SnXrb3JP#tha zU=66Kf+J8DOSWh618$)rb}Y_zYy@v?k7=Jy7$v|sfXO^Pe4&|i~M2da>g**e1jOs{(5Oga_!AEmd zugI^K73T6yaBBLm0M(w_N@1~^+Upg04g}OLBtR8Xd*Yes4NCz7MV2L{c6w<6)xwzq zH#m4HDRPP0(hk%ax&1oQZWYkS;89_12Q&cI+H{ur6n@rd219LCe*Z%Ih{E5)3MJX3 zs97K>kku&JDlJ1-YUm1hb221y8E9)C(st|F0d1i&Rdhe3ZGOf0nVd|~8_0j$57vTw zL3MDK3~6Npf*DT-Tb@Vr78F_af-z2b|vaTHL9scyFv26op}yD24n>V0DI3{$sBCL&Pv~9p-Gv=zOo5LM zx6To;_BlBBeq|F%$<}+IdBw&s8e_D|a_w_FK3WX2Q25_L~h8J#qp5mq?tE z78uuo1=5I&gyLL!9NawCl&wz literal 0 HcmV?d00001 diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3874b63e234027f8cd01369e05f477aa27e3b86 GIT binary patch literal 1152 zcmYjQ&2Aev5GJ|5mZiWgdT^2+_Rs=VQAllbND2f=+(Ib=xT+z^J?v$%UXHAF_eUnV zNF>;&=GX##1nHPZ=_~NsQ!aUcpoh+^Z9v@RaA$Ug^UcR$+Us=~j_?2ckY0O?{Y%R3 za8Y@IQ$Il=ndAkFX%z)8MJ!4ub~x!y;g(+ParQHluJqnA>4EseiG9hZtna_aXg2N( z8%{vijmp4=3NDwa($MF2?^J{OrIt`+Hg(%)>^z>i}Ypimc^kA5y+nHQmP4@=h}d>VS`$gmM~jCm@RT0&2uPZl%-Wwo9GN8 zD3@l{cdTnx%Nxv1u0UFM0magW%T(*vwUQY}baw-JWCZ&soVr3`*ahE+ExYChfnRaS z@3SpeUl8n^OLs1&%y=7LdRu-iuEZulUq|}a?v=9%uWlnSF1XL;Vk+>*AA^R zlzX^Shj!}kj}@i2K?z!?=dk|zsEVl2=yh%uky)gsRjKYMEfl136~P;1L=W=6v6A(@ z!O(ydPojsW6l}yf7$=wmzKwlQQ zfk!sT>Z*dwSU+o&u9Xeo4P=c0G}L988|!67jh*FLS3@zkE)`-sw_$l*sZS||ciDhi zy<_SDY9F5s%gPLY9DO%>^uy>S$)j&yJ|3Q^`T}$5;mOJ@>S{8Y&eCN*RJVPshlFpi zTv>NnYjcP@>CaFw-Vygi*X@W)8*~0s7hAyh!!$2^MVd{FFdj3&-vwRw(p<2x!l&4ckddv?|JM!>2cO PJ)?V~sXIe{z`NpqV+k(R literal 0 HcmV?d00001 diff --git a/item.py b/resources/item.py similarity index 54% rename from item.py rename to resources/item.py index 9a4c81d..e70727a 100644 --- a/item.py +++ b/resources/item.py @@ -4,6 +4,8 @@ from flask_restful import Resource, reqparse from flask_jwt import jwt_required +from models.item import ItemModel + class Item(Resource): parser = reqparse.RequestParser() @@ -12,89 +14,39 @@ class Item(Resource): required=True, help="This field cannot be blank" ) - - # HELPER METHODS---------------------------- - # searches the database for items using name - @classmethod - def find_item_by_name(cls, name): - connection = sqlite3.connect('./test/data.db') - cursor = connection.cursor() - - query = "SELECT * FROM items WHERE name=?" - result = cursor.execute(query, (name,)) - - row = result.fetchone() - connection.close() - - if row: - return { - "item": { - "name": row[0], - "price": row[1] - } - } - - # method to insert an item into - @classmethod - def insert(cls, item): - connection = sqlite3.connect("./test/data.db") - cursor = connection.cursor() - - query = "INSERT INTO items VALUES (?, ?)" - cursor.execute(query, (item["name"], item["price"])) - - connection.commit() - connection.close() - - - @classmethod - def update(cls, item): - connection = sqlite3.connect("./test/data.db") - cursor = connection.cursor() - - query = "UPDATE items SET price=? WHERE name=?" - cursor.execute(query, (item["price"], item["name"])) - - connection.commit() - connection.close() - - # HELPER METHODS----------------------------X # TO GET ITEM WITH NAME @jwt_required() def get(self, name): - item = self.find_item_by_name(name) + item = ItemModel.find_item_by_name(name) if item: - return item + return item.json() return {"message": "item not found."}, 404 # TO POST AN ITEM def post(self, name): # if there already exists an item with "name", show a messege, and donot add the item - if self.find_item_by_name(name): + if ItemModel.find_item_by_name(name): return {"messege": f"item {name} already exists"} ,400 data = Item.parser.parse_args() # data = request.get_json() # get_json(force=True) means, we don't need a content type header - item = { # flask will look into the content, and format the data. - "name": name, - "price": data["price"] - } + item = ItemModel(name, data["price"]) try: - self.insert(item) + item.insert() except: return {"messege": "An error occured."}, 500 - return item, 201 # 201 is for CREATED status + return item.json(), 201 # 201 is for CREATED status # TO DELETE AN ITEM @jwt_required() def delete(self, name): # check if there exists any item by name: "name" # if exists then delete it - if self.find_item_by_name(name): + if ItemModel.find_item_by_name(name): connection = sqlite3.connect("./test/data.db") cursor = connection.cursor() @@ -112,26 +64,23 @@ def delete(self, name): def put(self, name): data = Item.parser.parse_args() # data = request.get_json() - item = self.find_item_by_name(name) + item = ItemModel.find_item_by_name(name) - updated_item = { - "name": name, - "price": data["price"] - } + updated_item = ItemModel(name, data["price"]) # if item is not available, add it if item is None: try: - self.insert(updated_item) + updated_item.insert() except: return {"message": "An error occured while inserting."}, 500 # if item exists, update it else: try: - self.update(updated_item) + updated_item.update() except: return {"message": "An error occured while updating."}, 500 - return updated_item + return updated_item.json() class ItemList(Resource): diff --git a/user.py b/resources/user.py similarity index 50% rename from user.py rename to resources/user.py index dcca8df..379de51 100644 --- a/user.py +++ b/resources/user.py @@ -1,87 +1,44 @@ -import sqlite3 -from flask_restful import Resource, reqparse - -class User: - - def __init__(self, _id, username, password): - self.id = _id - self.username = username - self.password = password - - @classmethod - def find_by_username(cls, username): - connection = sqlite3.connect('./test/data.db') - cursor = connection.cursor() - - query = "SELECT * FROM users WHERE username=?" - result = cursor.execute(query, (username,)) # parameters must be in the form of tuple. - - row = result.fetchone() - if row: - # user = cls(row[0], row[1], row[2]) - user = cls(*row) # is similar to passing all values of row - else: - user = None - - connection.close() - return user - - @classmethod - def find_by_id(cls, _id): - connection = sqlite3.connect('./test/data.db') - cursor = connection.cursor() - - query = "SELECT * FROM users WHERE id=?" - result = cursor.execute(query, (_id,)) # parameters must be in the form of tuple. - - row = result.fetchone() - if row: - # user = cls(row[0], row[1], row[2]) - user = cls(*row) # is similar to passing all values of row - else: - user = None - - connection.close() - return user - -# New user registraction class -class UserRegister(Resource): - # Creating a request parser - parser = reqparse.RequestParser() - # Adding username argument to parser - parser.add_argument( - "username", - type=str, - required=True, - help="This field cannot be empty" - ) - # Adding password argument to parser - parser.add_argument( - "password", - type=str, - required=True, - help="This field cannot be empty" - ) - - # calls to post a new user (new user registration) - def post(self): - data = UserRegister.parser.parse_args() - # First check if that user is present or not - if User.find_by_username(data["username"]): - # if exists, then don't add - return {"message": "An user with that username already exists."}, 400 - - # else...continue - # 1. Connect to database - connection = sqlite3.connect('./test/data.db') - # 2. Create database cursor - cursor = connection.cursor() - # 3. SQL query to inser new useer information - query = "INSERT INTO users VALUES (NULL, ?, ?)" - - cursor.execute(query, (data["username"], data["password"])) - # 4. Commit the changes and close the connection to database - connection.commit() - connection.close() - +import sqlite3 +from flask_restful import Resource, reqparse +from models.user import UserModel +# New user registraction class +class UserRegister(Resource): + # Creating a request parser + parser = reqparse.RequestParser() + # Adding username argument to parser + parser.add_argument( + "username", + type=str, + required=True, + help="This field cannot be empty" + ) + # Adding password argument to parser + parser.add_argument( + "password", + type=str, + required=True, + help="This field cannot be empty" + ) + + # calls to post a new user (new user registration) + def post(self): + data = UserRegister.parser.parse_args() + # First check if that user is present or not + if UserModel.find_by_username(data["username"]): + # if exists, then don't add + return {"message": "An user with that username already exists."}, 400 + + # else...continue + # 1. Connect to database + connection = sqlite3.connect('./test/data.db') + # 2. Create database cursor + cursor = connection.cursor() + # 3. SQL query to inser new useer information + query = "INSERT INTO users VALUES (NULL, ?, ?)" + + cursor.execute(query, (data["username"], data["password"])) + # 4. Commit the changes and close the connection to database + connection.commit() + connection.close() + return {"messege": "User added successfully."}, 201 \ No newline at end of file diff --git a/security.py b/security.py index d8db52d..9fb76aa 100644 --- a/security.py +++ b/security.py @@ -1,12 +1,12 @@ from werkzeug.security import safe_str_cmp -from user import User +from models.user import UserModel def authenticate(username, password): - user = User.find_by_username(username) + user = UserModel.find_by_username(username) # if user and user.password == password: if user and safe_str_cmp(user.password, password): return user def identity(payload): # payload is the contents of jwt user_id = payload["identity"] - return User.find_by_id(user_id) + return UserModel.find_by_id(user_id) diff --git a/test/data.db b/test/data.db index 0e8e31df11f997bfb3bfade14b265bb42b48e6c6..c352345b1eca5906bb3c44aada19a42e31165bc3 100644 GIT binary patch delta 132 zcmZojXh@hK&B!@X##xY)K`-2kmw|zSi9d#ce;$9#W>|!XPeMoSRvakyxCP#?8pWAS{}cpJZrcYy#1Jmx2E-|9hZ@i~Pz0Ow8i!$%z@6 ZMGo4(9=fj+spe;97G+P$&(DSl007$8B%}ZU delta 147 zcmZojXh@hK%_uZc##vB^K`-2cmw|zSi9d#ce;$9#WRL3!XPY~l%Hg1WNgCA$ig5l8l0P1k^$xdbu;q6XW)MiglB WfuEUKlszdwKifh7*F*PpBGmw^79QUK From 815d0b0b3e9c292269b9a8ffdd065709c57d985b Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Tue, 25 Jan 2022 17:20:22 +0530 Subject: [PATCH 16/42] Added Store functions --- README.md | 18 +++++- __pycache__/database.cpython-39.pyc | Bin 0 -> 191 bytes app.py | 14 +++++ create_tables.py | 13 ----- {test => database}/data.db | Bin 12288 -> 16384 bytes models/__pycache__/item.cpython-39.pyc | Bin 1517 -> 1507 bytes models/__pycache__/store.cpython-39.pyc | Bin 0 -> 1559 bytes models/__pycache__/user.cpython-39.pyc | Bin 1182 -> 1204 bytes models/item.py | 65 +++++++++------------ models/store.py | 33 +++++++++++ models/user.py | 47 +++++---------- requiements.txt => requirements.txt | 6 +- resources/__pycache__/item.cpython-39.pyc | Bin 2493 -> 2226 bytes resources/__pycache__/store.cpython-39.pyc | Bin 0 -> 1521 bytes resources/__pycache__/user.cpython-39.pyc | Bin 1152 -> 979 bytes resources/item.py | 53 ++++++----------- resources/store.py | 38 ++++++++++++ resources/user.py | 14 +---- test/test.py | 34 ----------- 19 files changed, 168 insertions(+), 167 deletions(-) create mode 100644 __pycache__/database.cpython-39.pyc delete mode 100644 create_tables.py rename {test => database}/data.db (71%) create mode 100644 models/__pycache__/store.cpython-39.pyc create mode 100644 models/store.py rename requiements.txt => requirements.txt (93%) create mode 100644 resources/__pycache__/store.cpython-39.pyc create mode 100644 resources/store.py delete mode 100644 test/test.py diff --git a/README.md b/README.md index 1331531..3661f3e 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,20 @@ Contains <> branches Final and stable application is in the master branch -Use a virtual environment with python 3.9 to run the application \ No newline at end of file +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__/database.cpython-39.pyc b/__pycache__/database.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee989203d66d4a73022f0709e7b44a88769b5ec5 GIT binary patch literal 191 zcmYe~<>g`kf`2pKC0YUL#~=I;5=#=35{pyy3My}L*yQG?l;)(`F#>fK JgG}OK0sz7iE&l)j literal 0 HcmV?d00001 diff --git a/app.py b/app.py index 9c07fb1..e475db3 100644 --- a/app.py +++ b/app.py @@ -5,13 +5,24 @@ from security import authenticate, identity from resources.user import UserRegister from resources.item import Item, ItemList +from resources.store import Store, StoreList +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.secret_key = "komraishumtirkomchuri" api = Api(app) +@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 alraedy + # JWT() creates a new endpoint: /auth # we send an username and password to /auth # JWT() gets the username and password, and sends it to authenticate function @@ -21,8 +32,11 @@ jwt = JWT(app, authenticate, identity) 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') if __name__ == "__main__": + db.init_app(app) app.run(port=5000, debug=True) \ No newline at end of file diff --git a/create_tables.py b/create_tables.py deleted file mode 100644 index 6afb122..0000000 --- a/create_tables.py +++ /dev/null @@ -1,13 +0,0 @@ -import sqlite3 - -connection = sqlite3.connect('./test/data.db') -cursor = connection.cursor() - -create_table = "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username text, password text)" -cursor.execute(create_table) - -create_table = "CREATE TABLE IF NOT EXISTS items (name text, price real)" -cursor.execute(create_table) - -connection.commit() -connection.close() \ No newline at end of file diff --git a/test/data.db b/database/data.db similarity index 71% rename from test/data.db rename to database/data.db index c352345b1eca5906bb3c44aada19a42e31165bc3..b6cbb4bbceed794716e955ad0182f81d0283c66b 100644 GIT binary patch literal 16384 zcmeI&J#W)M7zgk>JH zd9LNTN_yV-v(Kx6E9y=5aP*=0kbZlgS!dSGyh4Hi1Rwwb2tWV=5P$##AOL|i7Fg}+ zhHcyY<9HcP-bMMHa*?T`)|0{w1O@IhPml^xt8T_K8v2142uc1ZApf=J-KV;F94;by ztOEHuMzHu-8-ZND5UOZ~zDs!9KKg3H^hJF6R9ajoWRz0)j8 zGgW98R#TxXxQg+Al7(4zmZY=Glz(rexSMn9JIfy=2tWV=5P$##AOHafKmY;|fB*!p zkH9TnV|N>qWYXU0JSpz~n)Q`gKk@+)l+kUz!5W4+Ns@W@Q5pILLW!f3 delta 284 zcmZo@U~EX3AT7wrz`(!^#4x}#QO6i4s2A?V3lw7Fk73}S#~-sJ5aj9W7!;}C?HZ}0P+FW?gseZYxVSvOCmAD3?LHK8T|k0v z@v;5~Jy=Kop&WS4$*0_V$_&>JHSsYNI2>|@+;3*SQ8E~G8IJzD56RPjvG=su96oLy z<1{~_00z8ZC6760zcUcvoiXqf&&69W_Q0RANpOXJY{IR8hk8S5gqso^xsKN#rs^%32(VOe6 zO!Pwjr50C*2>ME(KtpG)RhpTsu9h9s)LNPS4C3yTRQE_qiMtu3)5Rw^%>xuGzG9NC z1WLa0PzqpEcF7MWp7pDwR5rjbr^-g|ahbuyx4u!uOjEgZk3=8ECDYru{P*+IUycrS zovXAlM~92%bzS}WZ2BshXGbN)WR57N{rSR1Qf5`wNV$Xe3{`~j5P!?jZ36ahTvATv z>p?m)WFrWcvlYuRlpF)eJ>dTm1H{3lZ$$E4=gY^y2L9W+mTP0`scqYQD&dI^9H)4D9PM(?5N)65M`^0$&rEg@-=R(8)ON z@jhQZyhHk_j-g^<#A zr9@Jn7Nqy3d~=!<*Oq~ln1({N+cufule#!9E4%fgYSf9+nmA*_X`{31#8ux0&x<-~ zTvyrdb8O-4r20cG9D{9Nt0HNz8s>F2w}ER&J)~HPv9^~Mi7}abBP z$1mc#CO*(K#HdL4-jEOZNQ`+`L_y>W-VGz)c^zQSM>lnCV+q`Ui?iPMxQ%{o6Z0E2 JtD|dQd<0LJSdjn# literal 1517 zcmchXOK;RL5P)sxvFY}~Q{p8=E(n597sLS&^aZ3)36`q6fV7uriM=9KHfimwpsV!M z9udEQ!|t(v$yZKX_yL@lah4Ve;)o@WC!Vo0{$}j%>};Jt3qF49ey|Dof*-#&3qF>i ztBW8w;WQ;3@|1AKt$o6+J=UR|wn$)q1v_cRD1U8->+iv=AmB|pj8o3`Nyp+Aw_&t} z!yWGKlResTd5wE8SL0K>4x`7XVfPt5wN{A!i=2xz#w~v~)EV^0(A7y0B`rxrN(K^s z1xso3fN4AH_Jww~WD<+O(zX(5Pr`ADbU+*sS%cyGgGN6q8jr%;;hp>8D$12xt9Kjg zGT#(&p&IKu#k)M)2wUy$R?_HWFshM2T=QEy+KZwjONuDM9To0Ps7r^Z596Ny=@3&i zP8#ZX2y=irP)f3i9jJb05I$>4G#e36>7tIoR>A{K+nXxSra)6jg3#gI(Ld1?n`tyb z!?hTk`QH;z3NnI!jp;5c*@%tFuEp7|U0Nfnv`gBfW47y*&d3If3m(pxP*S>bxdizM z02|0w^ZVRX<)s2@ge8i;uH_yV2(8pvfECsHvsqGXD zcXTbzvkVw(Hy%ipOI;HmL_8>j_Ijd-fjVR_PID!KnzrIpndIsEWLc7D64_~Idm!YF zcBN2*w9uB!-yf%Ss+VLu!f>LuI}wI53oA|S5LTxiv*{f3aAdN=G#uCp_ge>@vKv)@+3vZGWxVemOz- zdgb}6u;pK0y6G!gz=D@DX=ggt=S=5eIlbd8p z$l@}X6z)xE1?4P28J;`>Qn~#X3RhpPKUryq69BES?VF3du=IO9{hbA-r2=vuH3T6q zpg4Z+!4{ORxCW+wp1X2u22*(Bv3#OP*P{qtgF%Y=OcZSox@l#Rcr7KquzEU9yGr#1 zJRw}3!)bg`8u~N>;x^^6iu6Uxy8sF7)F7lC^A$qs!RtL#Q7fOoWi@4$#Ve- z$`r2|y0?Hd`j6>9z_nAR4&6HSJzfi&sC_WW$f+T=&%;MHN^kXr10?sm0w+9AjE}NpF zSi*rN0(cUraN0s?)0J;U(ox|G_p1oPHDB{&2W?$wK^r<~?Lh=PS1jp67kao3)GqX4 zaK+y6WDj;>55Emz2>ZD1!vTW2=epy%GU~?)ly+@Px_yBldoz<4shP`HYX0s7LAjmh zwJI9F4$RB6$TRc{b-I{mVk;NYNuHuuqHxKVOd>;LcFjlEcm&U?Sh&zCJu!XsxMpS- zmw)1CPfkxvHC0({PfzCc%c}h8`|*o(mY)^`(4KmLhqJkhq|D2_mU2L0e0;vRcazKE z#-4F>*bLH%YLD$JRF-T?!hwUhB2BDF;Q{&*o8T==MBxYhi6^)NawcBs$zD$kNdw-*y5Y5Bww#TS!yyL)$96WcfMK{|xG@syU|H*ucnhR2`uel+9ZpAeeY>2YqZSPjdkLnqq zv*+vp72<4)2?Kx2S7MD^#A0N6XhB%5i%VtZZfBC~S{ZpZcdbcoY>hMonYIl+D)tr) z`;)u`Nok;Iet(as_uKFpW~)RoGYBB9gcX^NE1A1aq3n^V!N_>Bpn)JSu7niC2 z&oYn_iO{OqHgvY1R{F9iUFUgOt8-<1W4iXZHhFp8^laZKoz_UXeVNaEyBg|8-D1an;qwRcC4GKG)3-F;bdJ~P6&WfyiGPog2!kOXid_)}Q7Cw~9fjUw6o)R_l&t~U X@_#dLz3XBZ9W_}WQ)Bsd?2CT^tT1AS literal 0 HcmV?d00001 diff --git a/models/__pycache__/user.cpython-39.pyc b/models/__pycache__/user.cpython-39.pyc index 29d906712cc7e27ac853657cc95557b1341d66ff..de3c6aae46d3ea161ed52e57528f14dfa6b2ad96 100644 GIT binary patch literal 1204 zcmbVLy>1gh5Z<3V`}~)fC=}!gIEplM2%$&;Aqt`>4$aEa>Tgjm$Z2Y zyUauIB3sZ{l8V+w+y);SH$-$)7n%^=(nI<`CHI|E}m~g z4>4`o)+eL%#{yI;{3*KTG^C<4(McBRnf$C~_s0k*=4fhhJ~oCNqMAOMIh_+p<_s;u z9*{eF6fzsQ>+&d5Hkd@ld@VGD+AbMA#w2;Z)G>xb-imO3y@Vl%%E7@@!`PQKC(Wh4DBws|peN#s;Ex{Zs-^*RbsVjU|DYVn!{_ ze`wbp1|>FYffHU*y7KvwP^A5UXh+@9)2>e2G|HHy$SscmL?bS;EGe<80UmCG>zn9{ zl4T(wDx5;>># zi1-c3v43f=oVf4ito?TEdF;%$<8XeyMnLwy{0I+CLcZb4v>5R60+w0^ z;DpnJw8?A288?mzH;!1Fa=Jqr=6A4@R)jb$llc4aRS@tdZN@2Q$E0ltlN;PTCP%bw zfo^fT(rxZQMpw`5DIqsAE|LgJn+WO*);cV89iXHo2}sERX=wm5P9OnoH4M#SuH6Fi zq+wTR_aId2Fq6DtYEy}%qm3Zu5;IB*;DksQw$rCxH_g3Q{zHG|vA==%^1;Rhu+p*B2&#YsQSbC(DkITt({ zOQ{-r3rbE#!0wn1S;t9&R^fH->YwIzu&B5`KtcmjlbBda-Hym3U_@vuN-`zj@{A}^6|uVZD=f}Zc?r7G zR<97UuWcz*k>uKt*`YS^_=@~9m_f(OojB#eem_7X)`PREmH-H=QJ0x?fw|OVHDHLI5?z8Xb|XH5WoNz3F>n}&?~~^%p#F(JsTzP&vXS0JeN1Ij9Zgh u{;W#kEaU&KFwkYL#c1scqt*8Ylk*sdK2zOP8LGQ>pPg0loMHxabJkPmE diff --git a/models/item.py b/models/item.py index 8e8f0ff..987a584 100644 --- a/models/item.py +++ b/models/item.py @@ -1,48 +1,37 @@ -import sqlite3 +from database import db -class ItemModel: +class ItemModel(db.Model): # tells SQLAlchemy that it is something that will be saved to database and will be retrieved from database - def __init__(self, name, price): + __tablename__ = "items" + + # Columns + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(80)) + price = db.Column(db.Float(precision=2)) # precision: numbers after decimal point + + store_id = db.Column(db.Integer, db.ForeignKey("stores.id")) + store = db.relationship("StoreModel") + + def __init__(self, name, price, store_id): self.name = name self.price = price + self.store_id = store_id def json(self): - return {"name": self.name, "price": self.price} + return {"id": self.id,"store_id":self.store_id, "name": self.name, "price": self.price} # searches the database for items using name @classmethod def find_item_by_name(cls, name): - connection = sqlite3.connect('./test/data.db') - cursor = connection.cursor() - - query = "SELECT * FROM items WHERE name=?" - result = cursor.execute(query, (name,)) - - row = result.fetchone() - connection.close() - - if row: - # return cls(row[0], row[1]) - return cls(*row) # same as above - - # method to insert an item into - def insert(self): - connection = sqlite3.connect("./test/data.db") - cursor = connection.cursor() - - query = "INSERT INTO items VALUES (?, ?)" - cursor.execute(query, (self.name, self.price)) - - connection.commit() - connection.close() - - def update(self): - connection = sqlite3.connect("./test/data.db") - cursor = connection.cursor() - - query = "UPDATE items SET price=? WHERE name=?" - cursor.execute(query, (self.price, self.name)) - - connection.commit() - connection.close() - + # 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 + + # method to insert or update an item into database + def save_to_database(self): + 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): + db.session.delete(self) + db.session.commit() diff --git a/models/store.py b/models/store.py new file mode 100644 index 0000000..97d8a04 --- /dev/null +++ b/models/store.py @@ -0,0 +1,33 @@ +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)) + + 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 + def __init__(self, name): + self.name = name + + def json(self): + return {"id": self.id, "name": self.name, "items": [item.json() for item in self.items.all()]} + + # searches the database for items using name + @classmethod + def find_item_by_name(cls, name): + # 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 + + # method to insert or update an item into database + def save_to_database(self): + 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): + db.session.delete(self) + db.session.commit() diff --git a/models/user.py b/models/user.py index 18263db..f5bde4f 100644 --- a/models/user.py +++ b/models/user.py @@ -1,44 +1,27 @@ import sqlite3 +from database import db -class UserModel: +class UserModel(db.Model): - def __init__(self, _id, username, password): - self.id = _id + __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)) + password = db.Column(db.String(80)) + + def __init__(self, username, password): self.username = username self.password = password @classmethod def find_by_username(cls, username): - connection = sqlite3.connect('./test/data.db') - cursor = connection.cursor() - - query = "SELECT * FROM users WHERE username=?" - result = cursor.execute(query, (username,)) # parameters must be in the form of tuple. - - row = result.fetchone() - if row: - # user = cls(row[0], row[1], row[2]) - user = cls(*row) # is similar to passing all values of row - else: - user = None - - connection.close() - return user + return cls.query.filter_by(username=username).first() @classmethod def find_by_id(cls, _id): - connection = sqlite3.connect('./test/data.db') - cursor = connection.cursor() - - query = "SELECT * FROM users WHERE id=?" - result = cursor.execute(query, (_id,)) # parameters must be in the form of tuple. - - row = result.fetchone() - if row: - # user = cls(row[0], row[1], row[2]) - user = cls(*row) # is similar to passing all values of row - else: - user = None + return cls.query.filter_by(id=_id).first() - connection.close() - return user + def save_to_database(self): + db.session.add(self) + db.session.commit() \ No newline at end of file diff --git a/requiements.txt b/requirements.txt similarity index 93% rename from requiements.txt rename to requirements.txt index 16bcdb4..df44585 100644 --- a/requiements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Flask -Flask-RESTful -Flask-JWT +Flask +Flask-RESTful +Flask-JWT Flask-SQLAlchemy \ No newline at end of file diff --git a/resources/__pycache__/item.cpython-39.pyc b/resources/__pycache__/item.cpython-39.pyc index d96291831ed16e4a7059604a3d187fcc2208617f..779197585dc23ff0d4aff4633c79f1b376a0cf35 100644 GIT binary patch literal 2226 zcmZuy&2QX96rT^<>vg=%ri9W$smzyjea!>wo-} z{pJ($Cr-{k4oq&t&;p1cf)*sfFDq!t5?1nrQ=Id{Dc!^^y~ML~r|6V^;#=M=x@C|A zlzd8rCprg2bfo))CrdE*MHl8>I}ZijAaU>vZX#p`$Fai%~8HLw3$p4m^%e(AzhGG~|%Z=@H#$bEYoM*&#b38<0@J zaw_6B9IY)8(P+dII#Q9Z51ApeOH5_8poqs+=EuSAL(>Gob)Wu-LESl884 z@IcFAWSmxp8c&IO8TS19-klG(9;kXOhmGEPu-81StM$7Zo7p7aQpZ`*4IA5h@3V%? z>4@fJ&VHre(|tY*=lsw;B9weds>>9<0ADIx*y)X!N4|o1-5{Xj9RUVx#QfukNr;$> z#z(W|C@YlA#9k!7&2^)n{Q!ZRt$bWXQmI-+^>8=^oDTATK>qA^us815sH$b)6*_D@ z3vFcr`%N>oqfK9Dk7e4_smPjaJJVA2LG`JlF5NiY%H=;~cwd3R5@>F>4W2 zqim#@_1jrpz1y?_QddCf7+5w0$!e>cj#OQqdAkB?&%5Qdn(xDswtcKc3ibJH$zr8l zUvLTGg2TdP9hioUDHa0&0)SylgwMGkQ=$e9UxY}tI%k4j0T84vaLjTZK%#@Nfv=B6 z74tRKg_BheNZhqKRj;AoDiSQAHrc02>UET1Le(`S&!vw~rp-qn*Qh~gzyj{mi#(wC zJuVASS&J_iKehFkrY1&vf)|kvH-K1Oxnx6<(vTE0~@Yz<*aa zqiLZqU@fq^?E?IiEozb_K%p%30FEhJ>|>9!7rY2P#=qIZ_7>(*hd_MRr+pf-koE8V E4_`Xx!T^u|t;wv@J%vZc`C0(Pq&5E7J>)`3cGsFH}PD`Y%abQa)N91JI%rX<2AOKHX;mRXTSv2Uez=0r~BMy~1Gsh9bYZ`w{;%YrDN z3H+F49v^+bD8Yz>7fVZ>h+su;?i z&^`iXu>vWrfM6dd}sd!@)86kMb|Tx$Aqp|b$DS_UIXL^+9=u=eZ;i5%f< zx||A+Gj4JFN1GDi?-K5C_c__6QH^`te@>#njMjKyMrZg8ufu4aHz4>~ZR5waGmuG7 ztd7pFZX_z~CnDuxFV6F#4A(@smd5#$rlsw2dr)n~gs$xkkq&wcLOZ-HXyAPZZuKsh zlI+njozNXNX7bFK?Xd}2fwXXzP;T`Z#9%LJFB3||>tb{wfoMYfY`+-hyp{Y+A=#Gn zU@1M{PjcQxkM7!bH;*%+?M+qW7?~1jU)$9fdi+l0DR}3_t#+1|?JqkwIyXPa&r552s=3 z*yfRYm@u>X4QL#0US7lvkLfSKyWcF>xQ!bx*``V44lBu+jv05(kzKZ9jp}1-&zTTP z9+CVkg(t9*au?q7`phMd0l@17eB24}(Lh)`fKUL04u&3%PK0qPMa;KD@hnlL+W!fV zlr@NIG=Dn}g_MO1i(YRC9Bl3X4U_wSz+}@iSj$;xOWd<|4Q-_k%(ENIb*0@TS3;KZ z1ioR*Idq;!XPd`mEKgz|QLD^pGczbuc>!7qXA#NMEMT*Y(K>bMNj5SnXrb3JP#tha zU=66Kf+J8DOSWh618$)rb}Y_zYy@v?k7=Jy7$v|sfXO^Pe4&|i~M2da>g**e1jOs{(5Oga_!AEmd zugI^K73T6yaBBLm0M(w_N@1~^+Upg04g}OLBtR8Xd*Yes4NCz7MV2L{c6w<6)xwzq zH#m4HDRPP0(hk%ax&1oQZWYkS;89_12Q&cI+H{ur6n@rd219LCe*Z%Ih{E5)3MJX3 zs97K>kku&JDlJ1-YUm1hb221y8E9)C(st|F0d1i&Rdhe3ZGOf0nVd|~8_0j$57vTw zL3MDK3~6Npf*DT-Tb@Vr78F_af-z2b|vaTHL9scyFv26op}yD24n>V0DI3{$sBCL&Pv~9p-Gv=zOo5LM zx6To;_BlBBeq|F%$<}+IdBw&s8e_D|a_w_FK3WX2Q25_L~h8J#qp5mq?tE z78uuo1=5I&gyLL!9NawCl&wz diff --git a/resources/__pycache__/store.cpython-39.pyc b/resources/__pycache__/store.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e6d780a72b5d122e5720840fb5b8a36da60560c GIT binary patch literal 1521 zcmZux!EVz)5Zzrni3tg`P)fxCjHp0C$f`J^s4Aj}3q)0egs_S%*SkqwoH*TefLQ6N z{Q&rb=GaexBS&93^~3>AoS0cBX{vOi9nGv~cjmpBiC?Xj39P3-fBQcSLVlq!yG$@P zV5>R^PB@K7hq{!a&LY+^T!WHVgd5yECfpSC$Z$=t3%m$+QQHMxYLc3D1}~5r&8%&q z;=T+7=;dY-OYtV=A`0;H>|$VSz*egu1aT=RF5{H5W8xZlK-bIzx#JL7LBAK^)yV=> z1osQr>JEs6oX{a1(L*+5a(T#3*oZU%59CN2HAA9vw%7{0+zS)Y@pcDZ*YAkT+*fh; z)W}RFqE=?=vx36>Pl>z=m;OE3e73VC<9!h%YG-SZ?8V*fm(90+FWix1%xXv9aC(Ex zXp5wXNueOLOih|z(fK>@Wex*+X$)JSOfbl0BqXHVXt5@o4(So#N-H@kO5Ty~3MKdh zKNI{4EmkD&0qYt8A7cbH!ke{HBP(@;Qhr;cOF9YLkEG8Bw)hgNL^ct|dyxQXJNJ*^>y z62}*}?lFs51?###3*hX+nhx6%9M2CM(v#)Q`5M|GfYMGE>KH>e=L#suEH{pU_kD`P zwMmZXnBlCb$y65}z}Wae6Sl(mZ89VuDu*zoCuBsvu>-T2!>duh3g>$9< zV^~_f@OA@w9TCV|D3(#^DqU8a+wk=PZ2gi&CE8f}YAf>9M-Tonl2$*G7{{$Z3y4TL fn!S9QkTHE{hJtC~Gpn9A%{o>`SHA+&vvBV}Km$I_ literal 0 HcmV?d00001 diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc index d3874b63e234027f8cd01369e05f477aa27e3b86..571f4578475bf5683c246d2d28ca0455e8bac7c4 100644 GIT binary patch delta 309 zcmZqRyv)v*$ji&c00bMJyi0Us*~sV4$j1!ivH-C&5Er{mPG;0lW=>$_n4;9;{L-T2)Z!SB2EBsHTPy|n#U&CTxA6c821YI>J|-R}K1L3vD#1{onxNG5 z%;L$F%rT;3KxwcANQCI*SIj1yx7dpdb23X(jVCL!80vuBfUE?_y~SaZo1apelWNBZ PrjJQqW delta 468 zcmYjO!Aiq07*4Ww?Wz_Lg-t#5A~=-QsUV7MPN^b}DQ6HuM3!^#!)0 z7s0Eq;VsV~K7c1v@nHD>@8`>x@cn=CVLat*e`?ACYOOzi`9uB9K7zKcXjbKcN**Nv zTt3l9S*(xY5Dn1C7=vL3XNTG#GcpI-0Ag4G3XKA2qcMU&oym>CZ?)2jO`HoMw%Vu( ziWA8z+}nAuORgYXxY!qd312-d9XFcZdCNI&v`(Fv6ULp(TK&RnI?IiVdVSTYswgrG zB%g2vP9%9$el+-PClq8u8Uc+W5(t?MV#X zYqtDe=rY1-%mTvQwEa@AFSQ=!BA-6utO5WnWTQ#VLMGBs9+}BIx|v&4!mr&wSV*=r kn>w3TQgW9mucqDnmlc)fZkOV?Ln=@)p8l2zr{N^}0RuI7>i_@% diff --git a/resources/item.py b/resources/item.py index e70727a..1ecaecf 100644 --- a/resources/item.py +++ b/resources/item.py @@ -14,6 +14,11 @@ class Item(Resource): required=True, help="This field cannot be blank" ) + parser.add_argument("store_id", + type=int, + required=True, + help="Every item needs a store id." + ) # TO GET ITEM WITH NAME @jwt_required() @@ -32,10 +37,10 @@ def post(self, name): data = Item.parser.parse_args() # data = request.get_json() # get_json(force=True) means, we don't need a content type header - item = ItemModel(name, data["price"]) + item = ItemModel(name, **data) try: - item.insert() + item.save_to_database() except: return {"messege": "An error occured."}, 500 @@ -44,17 +49,9 @@ def post(self, name): # TO DELETE AN ITEM @jwt_required() def delete(self, name): - # check if there exists any item by name: "name" - # if exists then delete it - if ItemModel.find_item_by_name(name): - connection = sqlite3.connect("./test/data.db") - cursor = connection.cursor() - - query = "DELETE FROM items WHERE name=?" - cursor.execute(query, (name,)) - - connection.commit() - connection.close() + item = ItemModel.find_item_by_name(name) + if item: + item.delete_from_database() return {"messege": "Item deleted"} # if doesn't exist, skip deleting @@ -66,34 +63,22 @@ def put(self, name): # data = request.get_json() item = ItemModel.find_item_by_name(name) - updated_item = ItemModel(name, data["price"]) # if item is not available, add it if item is None: - try: - updated_item.insert() - except: - return {"message": "An error occured while inserting."}, 500 + item = ItemModel(name, **data) # if item exists, update it else: - try: - updated_item.update() - except: - return {"message": "An error occured while updating."}, 500 - - return updated_item.json() + item.price = data['price'] + item.store_id = data['store_id'] + + # whether item is changed or inserted, it has to be saved to db + item.save_to_database() + return item.json() class ItemList(Resource): # TO GET ALL ITEMS def get(self): - items = [] - connection = sqlite3.connect("./test/data.db") - cursor = connection.cursor() - - query = "SELECT * FROM items" - for row in cursor.execute(query): - items.append({"name": row[0], "price": row[1]}) - - connection.close() - return {"items": items} \ No newline at end of file + # return {"item": list(map(lambda x: x.json(), ItemModel.query.all()))} + return {"items": [item.json() for item in ItemModel.query.all()]} \ No newline at end of file diff --git a/resources/store.py b/resources/store.py new file mode 100644 index 0000000..9dad423 --- /dev/null +++ b/resources/store.py @@ -0,0 +1,38 @@ +from flask_restful import Resource +from models.store import StoreModel + +class Store(Resource): + + def get(self, name): + store = StoreModel.find_item_by_name(name) + if store: + return store.json() + + def post(self, name): + if StoreModel.find_item_by_name(name): + return {"message": "Store alrady exists."}, 400 + + store = StoreModel(name) + + try: + store.save_to_database() + except: + return {"message": "An error occured while creating the store."}, 500 + + return store.json(), 201 + + + + def delete(self, name): + store = StoreModel.find_item_by_name(name) + if store: + store.delete_from_database() + return {"message": "store deleetd."} + + return {"message": "store don't exist"} + + +class StoreList(Resource): + def get(self): + # return {"item": list(map(lambda x: x.json(), ItemModel.query.all()))} + return {"stores": [store.json() for store in StoreModel.query.all()]} \ No newline at end of file diff --git a/resources/user.py b/resources/user.py index 379de51..3f6eed1 100644 --- a/resources/user.py +++ b/resources/user.py @@ -28,17 +28,7 @@ def post(self): # if exists, then don't add return {"message": "An user with that username already exists."}, 400 - # else...continue - # 1. Connect to database - connection = sqlite3.connect('./test/data.db') - # 2. Create database cursor - cursor = connection.cursor() - # 3. SQL query to inser new useer information - query = "INSERT INTO users VALUES (NULL, ?, ?)" - - cursor.execute(query, (data["username"], data["password"])) - # 4. Commit the changes and close the connection to database - connection.commit() - connection.close() + user = UserModel(**data) # since parser only takes in username and password, only those two will be added. + user.save_to_database() return {"messege": "User added successfully."}, 201 \ No newline at end of file diff --git a/test/test.py b/test/test.py deleted file mode 100644 index 56eab91..0000000 --- a/test/test.py +++ /dev/null @@ -1,34 +0,0 @@ -import sqlite3 - -connection = sqlite3.connect('./test/test.db') - -cursor = connection.cursor() - -create_table = "CREATE TABLE IF NOT EXISTS users (id int PRIMARY KEY, username text, password text)" -cursor.execute(create_table) - -# user1 = (1, "bob", "1234") - -# insert_query = "INSERT INTO users VALUES (?, ?, ?)" - -# cursor.execute(insert_query, user1) - -users = [ - (2, "rolf", "2345"), - (3, "smith", "3456"), - (4, "john", "4567"), - (5, "clint", "5678") -] - -# cursor.executemany(insert_query, users) - -select_query = "SELECT username, password FROM users where username='gold'" -for row in cursor.execute(select_query): - print(row) - - - -# commiting the changes to database -connection.commit() -# closing the connection to database -connection.close() \ No newline at end of file From f235ca9869922c1184780d75755e46af693428c7 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Tue, 25 Jan 2022 20:52:35 +0530 Subject: [PATCH 17/42] initial commit --- resources/store.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/store.py b/resources/store.py index 9dad423..ffb88d8 100644 --- a/resources/store.py +++ b/resources/store.py @@ -27,7 +27,7 @@ def delete(self, name): store = StoreModel.find_item_by_name(name) if store: store.delete_from_database() - return {"message": "store deleetd."} + return {"message": "store deleted."} return {"message": "store don't exist"} From 02cb51e16309a1d409badbecbe127ee141c79bf2 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Wed, 26 Jan 2022 16:37:57 +0530 Subject: [PATCH 18/42] created login endpoint --- __pycache__/database.cpython-39.pyc | Bin 191 -> 191 bytes __pycache__/security.cpython-39.pyc | Bin 593 -> 574 bytes app.py | 19 +++- database/data.db | Bin 16384 -> 16384 bytes models/__pycache__/__init__.cpython-39.pyc | Bin 154 -> 135 bytes models/__pycache__/item.cpython-39.pyc | Bin 1507 -> 1656 bytes models/__pycache__/store.cpython-39.pyc | Bin 1559 -> 1707 bytes models/__pycache__/user.cpython-39.pyc | Bin 1204 -> 1532 bytes models/item.py | 11 ++- models/store.py | 10 +- models/user.py | 7 ++ requirements.txt | 2 +- resources/__pycache__/__init__.cpython-39.pyc | Bin 157 -> 138 bytes resources/__pycache__/item.cpython-39.pyc | Bin 2226 -> 2628 bytes resources/__pycache__/store.cpython-39.pyc | Bin 1521 -> 1517 bytes resources/__pycache__/user.cpython-39.pyc | Bin 979 -> 2205 bytes resources/item.py | 23 ++++- resources/store.py | 2 +- resources/user.py | 88 ++++++++++++++---- security.py | 12 --- 20 files changed, 131 insertions(+), 43 deletions(-) delete mode 100644 security.py diff --git a/__pycache__/database.cpython-39.pyc b/__pycache__/database.cpython-39.pyc index ee989203d66d4a73022f0709e7b44a88769b5ec5..ff40e8eff1b473f057917ac8e8f60f851f525992 100644 GIT binary patch delta 26 gcmdnbxSx?Xk(ZZ?0SL;TzfZK8$XmgfGO^7P0A7~|#{d8T delta 26 gcmdnbxSx?Xk(ZZ?0SNxhc$a83k+*`;Wn!Bp0AVf%-2eap diff --git a/__pycache__/security.cpython-39.pyc b/__pycache__/security.cpython-39.pyc index 211b4693ecb25d635c2e170c05788150878eb2e4..0289371ba947aa6c731075d50479986f9cf8c5ba 100644 GIT binary patch delta 52 zcmcb}vX6y3k(ZZ?0SL;Tzn{qcS6Vg2Dkh*PKPxr4q&Oy^vLquvFUU1GB(WefX0sWi GG$R1Mn-NI> delta 71 zcmdnTa*>5Qk(ZZ?0SJ<$-%RBGt7oB~n^&Um') 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') if __name__ == "__main__": db.init_app(app) diff --git a/database/data.db b/database/data.db index b6cbb4bbceed794716e955ad0182f81d0283c66b..9b81762182de102d680547eccbc48e560310e3df 100644 GIT binary patch delta 112 zcmZo@U~Fh$oFL7}IZ?)$k#l3h5`GRQ{wEClpZT9`78JO_uf)s5!XPeMoSRvaVQ6G* z!p+FSAS{}cp9JFYG5~=9P~9W`i4!EaAY29p{ delta 163 zcmZo@U~Fh$oFL7}J5k1&k#}Rl5`H#D{?82jpEnCCyyTbUW@KRy7EQ`eGBh$a;bmZ8 zVB&wp!2gy16;S#fzX~T4GlOJtNq$kP5ho*zX$VnwlY##&|4shGn*{}S@LP$Bvoj`V xBxV*l=>B@>zD}f?i9v{ol|h`HGdUwOvB*IaA}Ywp${@v-%sSWQPN4ViU}yn&q_@$DUJ!KEXl~v3vvw(Ni4{W$<0qm L%_)wVSR)4j?XeRl delta 76 zcmZo?oW;nU$ji&c00f55Urpq;F}2sv%`4G&ay4`{wsds^k&Xs#=K29e`B|ySCB^yy bl_eSZc|oqhA&CW<`nmZjsX4{^6Eo!i8^{+A diff --git a/models/__pycache__/item.cpython-39.pyc b/models/__pycache__/item.cpython-39.pyc index dd94649c3550ee28227735855567f18bfec9a586..06be40f2629fe1075d542397b758a5f4495fa19a 100644 GIT binary patch delta 292 zcmaFN{ey=$k(ZZ?0SG)~J|te5$eYDDV`4?MQmRl2e~Lg0Lo;KPNQz*JPzytpXo_%( z2#^*_5e;V06r0S+cz`iw@+rn+d|>@dER10IXYw?r$&9j-wU{l^nSkax1934(MGZq1 z!ve;I48aUVoItK7Q;{f;y2YHBlcUL4Bmra>@xcjxkQ7H+W?o7>P+l3xD+U?Hz*HsV zS(2LTo1c=JqX(0o9LU_js4@8xb1t6@NFN(eGPNYNNN%z(i!P(ij2nRE0icA(| zJizEO`5NOfHYSiB#>uOgCNoM*wqv%K{Ej)FQGRk9ORlgKQ05j}N@`AONotV{kW(Z) zIgm|w@<$d?M&-%uta^-Mlg(IHvB`p@Y8bK@7BDVk2xce(aW$EWq<~bB1c=R8Bs=*7tB6KgW?o8sVor`6P_P)Jn}MlH zIJhLgDAhMVB{fG6CO6|Wc338k^Dhy delta 174 zcmZ3@JDrC&k(ZZ?0SM-veVnLL5na`GD%DNfd7)n$~Q9K)*5C^Wf;wVO=|C|#s7xt>j2p@ zG6CV9(KS|cSi)MEXk9Px9mcby;VFU#;JW0QACO)XtsRBk0M?J1FD75X(^TQ6guLvT&5jpEfgfLn$V;+3D`8&3f>_sQ@u%crp{S>Eu2pA4b8+x0#}OZ!u-26!8E})nqE-pX|b{ z%qTiJgSm(&iY+-mH#f6Hld*_n@)KqiM#;&XEE>#yngWv_vUmYC>##~m-r`QqNh~hT zO)bgDPbm@rno=YLB19*jV-=s=z{({d0_1Wqu`mlT0wEIz3lk$3GYC&!#u^3ym{K!X diff --git a/models/item.py b/models/item.py index 987a584..d4fad3d 100644 --- a/models/item.py +++ b/models/item.py @@ -18,7 +18,12 @@ def __init__(self, name, price, store_id): self.store_id = store_id def json(self): - return {"id": self.id,"store_id":self.store_id, "name": self.name, "price": self.price} + return { + "id": self.id, + "store_id":self.store_id, + "name": self.name, + "price": self.price + } # searches the database for items using name @classmethod @@ -27,6 +32,10 @@ def find_item_by_name(cls, 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): + return cls.query.all() + # method to insert or update an item into database def save_to_database(self): db.session.add(self) # session here is a collection of objects that wil be written to database diff --git a/models/store.py b/models/store.py index 97d8a04..fe85208 100644 --- a/models/store.py +++ b/models/store.py @@ -14,7 +14,11 @@ def __init__(self, name): self.name = name def json(self): - return {"id": self.id, "name": self.name, "items": [item.json() for item in self.items.all()]} + return { + "id": self.id, + "name": self.name, + "items": [item.json() for item in self.items.all()] + } # searches the database for items using name @classmethod @@ -23,6 +27,10 @@ def find_item_by_name(cls, 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): + return cls.query.all() + # method to insert or update an item into database def save_to_database(self): db.session.add(self) # session here is a collection of objects that wil be written to database diff --git a/models/user.py b/models/user.py index f5bde4f..c0b6dfa 100644 --- a/models/user.py +++ b/models/user.py @@ -14,6 +14,9 @@ def __init__(self, username, password): self.username = username self.password = password + def json(self): + return {"id": self.id, "name": self.username} + @classmethod def find_by_username(cls, username): return cls.query.filter_by(username=username).first() @@ -24,4 +27,8 @@ def find_by_id(cls, _id): def save_to_database(self): db.session.add(self) + db.session.commit() + + def delete_from_database(self): + db.session.delete(self) db.session.commit() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index df44585..9ffc1ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ Flask Flask-RESTful -Flask-JWT +Flask_-JWT-Extended Flask-SQLAlchemy \ No newline at end of file diff --git a/resources/__pycache__/__init__.cpython-39.pyc b/resources/__pycache__/__init__.cpython-39.pyc index fbe2f604657164d0db3d87cbaf02e08d2447230a..a90037809627f3d14cba49dbdcb36d8919ebf5af 100644 GIT binary patch delta 60 zcmbQs*u}`5$ji&c00d>v-%sSWQPoeeiU}yn&q_@$DUJ!KEXl~v3vvw(Ni4{WDM~HQ OFD*(=EsmL3F9!fEXcUbA delta 79 zcmeBToXg0a$ji&c00gqnUrpq;v2fDQ%`4G&ay4`{wsds^k&Xs#=K29e`B|ySCB^yy el_eSZc|oqhA&CW<`bDY5`K3k4sm1yebL0SHq!?iU diff --git a/resources/__pycache__/item.cpython-39.pyc b/resources/__pycache__/item.cpython-39.pyc index 779197585dc23ff0d4aff4633c79f1b376a0cf35..038515709395517b32fa671f01a2f56e2a5c457f 100644 GIT binary patch delta 1247 zcmZux%}>-&5Pu)tZg;nQiUSm zy=Jl1IYfij@w4Z`LbVVUt3^r#5xJlg+Ets9YebkLzez-1<~De>19VFiKrd*$v-SM! z1kdb+CyA@%qh()7k>%?$alw*x{}xpZ346vk_WCPt}7^OjABF)(hfmL+wLLA3$aZFY(lV)cbefK>C$Wt{b3p zSXW@hR{;{TLDpE42|7sPgMta}Q(@HD3`yuK)}-5XjW@YE-{i0OHUSoL!u073zVqN| zp{G-r&MZH6JrVj1)eTu!2QQ&qUQ+&JKk(`@E^8i&K!o3-aInF2);PL&5PTg&<>94v zu9fu(8OTJsH5G*}-et~}CCCbOuWem0)nE`h4L z0GxutYO5iHLkK(Ds~smbj31C*br@j}nV@ETX*s!^zR5kLLutu8;tZhA2*NRhQH0$P zEge8xF}-J=c+jN}Fb{^m9xTp9STXJ`NtFsxWs(aj*cKIB7@K5+Rt=E@+Re&P6_))_P=4twg4qU@Dy1W#lH+4a`$yJDlf>Sjvc$57pHk1=Qnhd{UYrkae zCUC6j)XJYeg{OC@=HfNWSmoq@sV`0jaF+QfT)J>S4f7Lrmcw3-zkP&o$TPa0z!(jZ z7JFpC`KfMKctPV^?b!e(2DnIt+lX4g-S;qFLI82>EFD$pi zm4&jNCri(h7X)C%?pe?B%du22dF_DR^62fbf_GuXSaawb+RF`U8#ZM>4bFb7+JES- zzz9z}IW-1uY8=5qFcEYc>?YQd&R!v8Ke+=gF;1ZcO{u^{oUXKNow?at3%qy{e-N&G sn#hKbqMc6<2VSXK3M1(4xPqLv`&AQ%N%j!e-Y|V(4Vit+X5BY_0gzz>dU;3x0Z$$UXErvm=!5xoW_ISy%s20I;X{7H%VtvqPVw6h z@15D7n4`RZvs|Kt6K?ocGv%Zxc|>@ECwqh^g>k4kX|PRhfo;Whrhi^_dlQ0auP09`C7fj-FhgR?)JXh@|r^FjiB8cXi5)+UsL+fhYFB1)9cG zIf-CKpS0qQ9H;|bUO>-90Ht#;^kf0;sS!*^Nxc&NqS@6V_(F0*yY!U4VO=KYyX=IW z5*UnfR;Qcz)#{Y%HMU7?qbg^julmyZWyrHNIV$fIxbQ_N+?s4RU97q71w!UWCFA1L zV5-D0as~7t<}`X{9Bo(_nFX*dX`()hFo!TIB1b{;3jV-he znU7&R>-8Yqp%8R-P}f23g9I}Gn8mdoFyEla_5*TAk!xG8D#O4H2ho@0>I!yd(|Ass zDOOtkXOR$~QWYO7wwt?aPorhCc<^U2%Gm1&xvw%a6ugjHLYi{O?^2-gu}BFA@(nZtMV1OzWi5mS@su(Q!y>-Lsa^S$7u3(sCA!0V)& oZFmAf3FjOxH(oeq{BJADtUyA?7i-94kO44Rj^=2FWmxXvZ>Vy${Qv*} diff --git a/resources/__pycache__/store.cpython-39.pyc b/resources/__pycache__/store.cpython-39.pyc index 7e6d780a72b5d122e5720840fb5b8a36da60560c..ba56a097635e5c0b032ff94a1a441a6e3abb55b4 100644 GIT binary patch delta 76 zcmey!{g#_Ik(ZZ?0SF#QeMmgHk=KclF=cZA<9SA=lGM$L%m$1CVnE3h#uTO$<`#w$ dh6Rk1lUbJWG8Hicg>P}BW#*;CPnKZ42>=gz6gU6? delta 80 zcmaFM{gInDk(ZZ?0SJsPy-z%~k=Kcl(PeW0<9SA=)RN7L%m$1?5GD zcT|=~1C$Mz`X>;A2%3?chBRj(qo}it=U(W!HP8IK5jOH>*mSy=1$irMQ6kw<`pG)3?btlUm(dFL}C+M@G-h>oQ9dDsWND|(>!oW25jU#x(>;`D(S+$5vb z6NrwCm<_h1E-RHt8z^~a8Y?YF)OK||k&!kkO7bb(dM;5iHZqEnL~0$G^0qAO+Ucf} z6D9Rty$i;zueDTPl|p7Vs9;&dx!j(wZ||jgI7wwDhDlr$r5WCmLzz#_Y{ac+X45)^ zDpgWgZ%<}Z8%$%Z4@xBxjCOf2P&Q!dH4u`7RFIGfD%b-Oa={@(&vr46ExDU&BUM7- z;@RM^@L*npsjq`D-xwiP1YZW@fJ} zwm!YN9Z%C8=(K~2-a&)$bY{J2sm(f==`ILD1J-9<-iQDBDyDJPfid3OguVn(gds(d zZAVdFiYmi(H;V35aaNzGOW?`HvLs%>y&ec$XhcB3o&GO$V1GC8(i?w^kNY(A zV9ka1fWX=pKJ>0(J&ZL$uV(|j;JgM?UjczASC(u%_p(!G%7T7P_sBgXpYwZ}6)fohI-f=n9=bNL3x{Q{HDD@&*c9INeAeR6` zpltmnyeOd0N#W@P` z!RiTxd;`jVQ3w)++A`jP?fXD0>@vBJdIR((0`MU_9~<7ZYAiU>pt z0M>To(y7Ts;XAgCyqyYJnAFUy@7Q-l)!T3cC*5---8;w5C%?iShtT=j=f&MPOM#4m zZ_!np0S*0tFm0&;URyUUCpj&E@>kd}> zW0)EnMtH#c-Wm`^mov)#_Bi{?^XGlUf-CnaU-(bKLA?pK&*q1>;PgY78pA~dDBRf` zt=irMV01eI0yDs+3g2{vAP83vq`Ezq)$Ul!q*72*m#e){8|)vLNZvED05W!|yR^d& zFqedqRS>VMQrO#)cPj9H13#k5)exE?1oCo~Ly0YiFREiK2T2#%QjMO#*ajcewD=Kp egJ?`(K)ZARv>L!$57olopN+=SIvA`DF8%{VWcj@S literal 979 zcmYjP&2G~`5Z+z?#0^PRBtQjm@PSJ}%7sgX5NgHWiK-(?Ya>r-vCpoJNV{99@ z*+Qe3;yFv`EplFnL=;})aq_*~FM=fC>@`!q3NDxmKz#I)Q1N}%3$L)6?e>I=24Kos zkHAG5W|hlu(}XKlz+B zkzh5VLe;upNu(kKvgSI}Y5+%>v7lX4qc@cW9Gu=em}Dj%XOOFSB&XBT#)l9?QQ3LV zbH1HdOU!kqK{ENEmI|}Jd`+9)XYFWc0NU%PnqACzJ=v~ z9=&)n+|%VTjI0^%&F!R|4qomb$SND^|JTC|si0rYonMv4Zep3~pkcf%){H$@PB=t zfT?w!v0C53c!PVz+@=_5MBCg)6NyOpTr8izVQb|aHJQDZYnfS|S$OQa<6N3|DU!7) ryB72}g5+)>UdT Date: Thu, 27 Jan 2022 21:51:41 +0530 Subject: [PATCH 19/42] Added blocklist and other jwt configs --- __pycache__/blacklist.cpython-39.pyc | Bin 0 -> 155 bytes app.py | 53 +++++++++++++++++++++- blacklist.py | 1 + database/data.db | Bin 16384 -> 16384 bytes resources/__pycache__/item.cpython-39.pyc | Bin 2628 -> 2647 bytes resources/__pycache__/user.cpython-39.pyc | Bin 2205 -> 2614 bytes resources/item.py | 3 +- resources/user.py | 17 ++++++- 8 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 __pycache__/blacklist.cpython-39.pyc create mode 100644 blacklist.py diff --git a/__pycache__/blacklist.cpython-39.pyc b/__pycache__/blacklist.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc810dd404a761e8bbb2e418049bf322b5d33139 GIT binary patch literal 155 zcmYe~<>g`k0?SRG5-oxBV-N=!FabFZKwQiNBvKes7&Dln7*d#m88n$+g5+K@0|`G( z##@|DK90`bKAyoLD;bJdfP!G+mzs-JOh8e7R%&udaZEsENk)ENkZW*AVnJq1QchxW dc1~t-iC#hFEe@O9{FKt1R69nX*3Ur9004n0BM$%o literal 0 HcmV?d00001 diff --git a/app.py b/app.py index 1f5b99c..752d83f 100644 --- a/app.py +++ b/app.py @@ -1,10 +1,11 @@ -from flask import Flask +from flask import Flask, jsonify from flask_restful import Api from flask_jwt_extended import JWTManager -from resources.user import UserRegister, User, UserLogin +from resources.user import UserRegister, User, UserLogin, TokenRefresh from resources.item import Item, ItemList from resources.store import Store, StoreList +from blacklist import BLACKLIST from database import db app = Flask(__name__) @@ -13,6 +14,9 @@ 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" @@ -40,6 +44,50 @@ def add_claims_to_jwt(identity): 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 + +# 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["sub"] in BLACKLIST + +@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') @@ -47,6 +95,7 @@ def add_claims_to_jwt(identity): api.add_resource(UserRegister, '/register') api.add_resource(User, '/user/') api.add_resource(UserLogin, '/login') +api.add_resource(TokenRefresh, '/refresh') if __name__ == "__main__": db.init_app(app) diff --git a/blacklist.py b/blacklist.py new file mode 100644 index 0000000..d6e7c77 --- /dev/null +++ b/blacklist.py @@ -0,0 +1 @@ +BLACKLIST = {2, 3} # user ids that will be denied access \ No newline at end of file diff --git a/database/data.db b/database/data.db index 9b81762182de102d680547eccbc48e560310e3df..21ada3baaa2a07ab916701414f8eefbc5da747ff 100644 GIT binary patch delta 118 zcmZo@U~Fh$oFL7}J5k1&k#}Rl5`7*P{tXQLZ~1TXALHM!Sx{gBe}D)JD}y9EV?kqM%V1esYGgu$W?s!$;zCRPSzNRX#RTWzD}eX E06+Z>JOBUy diff --git a/resources/__pycache__/item.cpython-39.pyc b/resources/__pycache__/item.cpython-39.pyc index 038515709395517b32fa671f01a2f56e2a5c457f..4b85e3022593835d2c61b9729e01f66a32d4b3d9 100644 GIT binary patch delta 355 zcmX>ia$STsk(ZZ?0SK}ed`g_Tk(Zy5F>kUgqpMyjX9`maXD?$4R|c!em2MVOHjX(h}#%=4^3{vXiH= zX>conq|`u!`s9302`-RRIf1xXW%6q_1tvzW&D`uNjEqr}4LD-VYZy})BpD_$6|w-G z#8@N((k2EX4B;#=R~*DO1`s|5)dUe* olT$gBg!O^UP#`X50TLWc9E?T!lV@=jGwK7KDn412OODY80Ch`7g#Z8m delta 317 zcmcaEazunTk(ZZ?0SJ24KPJ{}!;nVsJtn$hyT`P+HF>tZdpaCK@Cs%MP3F`rwAwXQr0wg$? UI2eocCa>izX4C^Z$CS|<0Gqu(KmY&$ diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc index ad58679d161cfc38124d7aefb3511ea592f9ee96..8db7b3911b7fbaad6afa5cd0e99949b947014285 100644 GIT binary patch delta 1150 zcmZ`&OHUL*5bo}I?99$SVbNvfu>?hi$HsWjgN8sz^ag@>8BI1Dx)&Drp*^!98ptNX zmBZR5UNli}+&r21157-7oSR2(#>9&UW7QC0No11#roQUxn)<41-ey1LQk7&fPT+aH z^0RUj@1}<6$L{oOr(tt?>=$+yI*Ih-u*RQ z3=>=1L0O8Y?V1!k%xs9DjGh`7&7c-MQ$_lR#?>f$L2dPc&C$I2&Th~VmDiT13ZPKt z&%z+mG{7ZTJ_E2cnO0q`JCXtNJN>{Hve;||#cHeF5;!#dKCNxetJD^7b zs9=~&FM|FFJ$dyPc-oUEZyx*sitoDxV+9xH;r-tC<3sIjHApxwTJT+O{77E;lb}iV zCd+5e5=&UZ>9c_!`^1v;UGcpYmt<+TEL_QjCkleScEE!cU-HSD`eJa+5@o?(T7paR z!j3DT2ciOcCD%g{-nOFZUK1Qk@H7K3N=cDXqM9(|=N6h&8E_|}8S4r8IPIn5%?Fr?95V`6E8f%DraS=)##I(h{JG{ze zGcKIFdT>H@3WNW9vjy;8f~T_(MpQLd{PlKR8Vq51dSwKw&D)LnE%{_5b-Ix# qw1wuAKf8*TSGb=%`>)piY%gOMq131^*9e4Z{7Fd4B&4QRoc|3mJCH;G diff --git a/resources/item.py b/resources/item.py index 3bbb063..1256555 100644 --- a/resources/item.py +++ b/resources/item.py @@ -30,6 +30,7 @@ def get(self, name): return {"message": "item not found."}, 404 # TO POST AN ITEM + @jwt_required(fresh=True) def post(self, name): # if there already exists an item with "name", show a messege, and donot add the item if ItemModel.find_item_by_name(name): @@ -94,6 +95,6 @@ def get(self): # else display only item name return { - "items": [Item["name"] for item in items], + "items": [item["name"] for item in items], "message": "Login to view more data." }, 200 \ No newline at end of file diff --git a/resources/user.py b/resources/user.py index 90ce528..ee1d0bf 100644 --- a/resources/user.py +++ b/resources/user.py @@ -1,7 +1,11 @@ from flask_restful import Resource, reqparse from werkzeug.security import safe_str_cmp -from flask_jwt_extended import create_access_token, create_refresh_token - +from flask_jwt_extended import ( + create_access_token, + create_refresh_token, + jwt_required, + get_jwt_identity +) from models.user import UserModel # extracted parser variable for global use, and made it private @@ -82,3 +86,12 @@ def post(cls): }, 200 return {"message": "Invalid credentials."}, 401 # Unauthorized + + +class TokenRefresh(Resource): + @jwt_required(refresh=True) + def post(self): + 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 \ No newline at end of file From 60d5cf8d7d3afea54bab4e571c81d6d3fe79e86d Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Thu, 27 Jan 2022 23:05:56 +0530 Subject: [PATCH 20/42] added logout feature --- __pycache__/blacklist.cpython-39.pyc | Bin 155 -> 148 bytes app.py | 9 +++++---- blacklist.py | 3 ++- resources/__pycache__/user.cpython-39.pyc | Bin 2614 -> 3004 bytes resources/user.py | 12 +++++++++++- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/__pycache__/blacklist.cpython-39.pyc b/__pycache__/blacklist.cpython-39.pyc index bc810dd404a761e8bbb2e418049bf322b5d33139..1a3d5041a96f174e49017ed012f9c3d0c0ad15e1 100644 GIT binary patch delta 100 zcmbQuIE67Lk(ZZ?0SMGCd`h$d(vLwLWWWgIH~?`mNLeaFGeZ<(3PUi1CZnGw(=F!W f)RJ4APCky#-aekeAuAb*n1QOm#4oj!iOCiKK4lWV delta 107 zcmbQjIGZs!k(ZZ?0SGKNeM+hpc2MVgU+*iC=0i6Jsm@gryRY diff --git a/app.py b/app.py index 752d83f..be88c3b 100644 --- a/app.py +++ b/app.py @@ -2,7 +2,7 @@ from flask_restful import Api from flask_jwt_extended import JWTManager -from resources.user import UserRegister, User, UserLogin, TokenRefresh +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 @@ -56,7 +56,7 @@ def expired_token_callback(): @jwt.token_in_blocklist_loader def check_if_token_in_blacklist(jwt_header, jwt_data): # print("Log Message:", jwt_data) - return jwt_data["sub"] in BLACKLIST + return jwt_data["jti"] in BLACKLIST @jwt.invalid_token_loader def invalid_token_callback(error): @@ -74,7 +74,7 @@ def missing_token_callback(error): # when no jwt is sent @jwt.needs_fresh_token_loader def token_not_fresh_callback(self, callback): - print("Log:", callback) + # print("Log:", callback) return jsonify({ "description": "The token is not fresh.", "error": "fresh_token_required" @@ -82,7 +82,7 @@ def token_not_fresh_callback(self, callback): @jwt.revoked_token_loader def revoked_token_callback(self, callback): - print("Log:", callback) + # print("Log:", callback) return jsonify({ "description": "The token has been revoked.", "error": "token_revoked" @@ -95,6 +95,7 @@ def revoked_token_callback(self, callback): 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__": diff --git a/blacklist.py b/blacklist.py index d6e7c77..ffab0a7 100644 --- a/blacklist.py +++ b/blacklist.py @@ -1 +1,2 @@ -BLACKLIST = {2, 3} # user ids that will be denied access \ No newline at end of file +BLACKLIST = set() # user ids that will be denied access + diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc index 8db7b3911b7fbaad6afa5cd0e99949b947014285..10c5a803505ae4f06d77ea85a3ca2123793b842c 100644 GIT binary patch delta 1343 zcmZ`&OKTHR6rOu$CYekoX&Td)R!v)cq^+@36a^68b<*gW3EH9* ztXmi5{sCS32Xv!b*Mj0=R<7K+(4`=F&aD!a8n|EX`OcX$-+9c-((8eIkjuFS{w}`% z7M%9h^TX`j`ozK2mg~FB5QebAY|HaKeYeA0EAQu-A^EeO=Z0U9g~eWxl0}ggj>tT< z&_j&-Cv(OSS>Zh~geTdW>2EoYLc5RClZlDCrqnB|aid7y5rR>Kl^m%oqevM`QLUIj6M!)UzqG;D8qjXlL=XqJ z0*u{7{(rKkHX@IxWqa%*34|R4I}ws>Jq)6#CF9#|0egtvOVG`rZ@RoPI;wag2f^W- zxejMg#vceFVCge!5u% z^gP%I32zEW;iY23ZXyQ>bmK?>>?i0-)aqy|r`2+L`uN`!LWP3=JF{{0p28E60xAl| zX=zBW(X$u6Jw3}mN0Rtti+|s%(fTWow&|0`1g-yZ{pEO%Xo`G{I+aF@AkrKc;qdbb_Lb1!W8r(tOf-uM5GO{4oJ zo?jia7)U8!k~U9Y(}x} zqMy4Y?{#DpUkHF=O6pB!;0%4rP$r-S0jgOi-%Lm19L1M(LV1k#AeuxdFpmwVJ*GDB2#HH1 zK)v-4_P_xl!Lb+0i4(uX|9}HRaOA|yMk1l2k@nNfH?ysku@-md=& zzghiknSAcgU+NKsDpYS;EjzG@!qgAWM%ngNF)PAi{ zn~_&q;DPT@7kpRxdGNC|2Yyca1@JwZ2R|?UA}!ogd{TTv7Kknu$t~9>NoFHr{C0=3 zrg%qw5=-o>FG)st+H!gvwEv#JE;h6!G9~u3)wxMvIMO39fo=}q6r4x}Fg2bNRkI?> zdQ3dg*UM1wlRi5!hN;8rcr(# zL0|z+WCEC~N-R?)d`dRCVjLK_W}X#4j7c<aJ^!zL>6V7LYC?NbM!GbeW2f0!Y-y zlpA39DSj5olL&H7_#R(G7%Gxta0%DMVfw<#zYX#8n0)MtY7o5&Cqf0-1FuQ_dzs#< zk2mFmb+CMuW1mtW%>4w|iL>41oVDXd7bYEK+U$8F?mT7f|B2fY_pLKGp`#=E(pdCZ zeM=DCgfq}bOu~`5 Date: Thu, 27 Jan 2022 23:08:28 +0530 Subject: [PATCH 21/42] files exclusion --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4a25664..e4a1c96 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ test/test.py -test/test.db \ No newline at end of file +test/test.db +__pycache__ +.vscode \ No newline at end of file From f4b1c8345b9c36a8814a93fe97e0e56c7e7e4caf Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Thu, 3 Feb 2022 10:41:51 +0530 Subject: [PATCH 22/42] minor changes --- .gitignore | 6 +- .vscode/settings.json | 4 +- blacklist.py | 4 +- database.py | 4 +- models/item.py | 92 +++++----- models/store.py | 82 ++++----- models/user.py | 66 +++---- requirements.txt | 6 +- resources/__pycache__/user.cpython-39.pyc | Bin 3004 -> 3037 bytes resources/item.py | 198 ++++++++++---------- resources/store.py | 74 ++++---- resources/user.py | 213 +++++++++++----------- 12 files changed, 375 insertions(+), 374 deletions(-) diff --git a/.gitignore b/.gitignore index e4a1c96..120273b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -test/test.py -test/test.db -__pycache__ +test/test.py +test/test.db +__pycache__ .vscode \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 7e7f74b..6be470f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ -{ - "python.pythonPath": "/usr/sbin/python3.9" +{ + "python.pythonPath": "/usr/sbin/python3.9" } \ No newline at end of file diff --git a/blacklist.py b/blacklist.py index ffab0a7..ec72982 100644 --- a/blacklist.py +++ b/blacklist.py @@ -1,2 +1,2 @@ -BLACKLIST = set() # user ids that will be denied access - +BLACKLIST = set() # user ids that will be denied access + diff --git a/database.py b/database.py index 2e1eeb6..fa42112 100644 --- a/database.py +++ b/database.py @@ -1,3 +1,3 @@ -from flask_sqlalchemy import SQLAlchemy - +from flask_sqlalchemy import SQLAlchemy + db = SQLAlchemy() \ No newline at end of file diff --git a/models/item.py b/models/item.py index d4fad3d..2f6930f 100644 --- a/models/item.py +++ b/models/item.py @@ -1,46 +1,46 @@ -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)) - price = db.Column(db.Float(precision=2)) # precision: numbers after decimal point - - store_id = db.Column(db.Integer, db.ForeignKey("stores.id")) - store = db.relationship("StoreModel") - - def __init__(self, name, price, store_id): - self.name = name - self.price = price - self.store_id = store_id - - def json(self): - return { - "id": self.id, - "store_id":self.store_id, - "name": self.name, - "price": self.price - } - - # searches the database for items using name - @classmethod - def find_item_by_name(cls, name): - # 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): - return cls.query.all() - - # method to insert or update an item into database - def save_to_database(self): - 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): - db.session.delete(self) - db.session.commit() +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)) + price = db.Column(db.Float(precision=2)) # precision: numbers after decimal point + + store_id = db.Column(db.Integer, db.ForeignKey("stores.id")) + store = db.relationship("StoreModel") + + def __init__(self, name, price, store_id): + self.name = name + self.price = price + self.store_id = store_id + + def json(self): + return { + "id": self.id, + "store_id":self.store_id, + "name": self.name, + "price": self.price + } + + # searches the database for items using name + @classmethod + def find_item_by_name(cls, name): + # 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): + return cls.query.all() + + # method to insert or update an item into database + def save_to_database(self): + 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): + db.session.delete(self) + db.session.commit() diff --git a/models/store.py b/models/store.py index fe85208..d226c87 100644 --- a/models/store.py +++ b/models/store.py @@ -1,41 +1,41 @@ -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)) - - 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 - def __init__(self, name): - self.name = name - - def json(self): - return { - "id": self.id, - "name": self.name, - "items": [item.json() for item in self.items.all()] - } - - # searches the database for items using name - @classmethod - def find_item_by_name(cls, name): - # 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): - return cls.query.all() - - # method to insert or update an item into database - def save_to_database(self): - 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): - db.session.delete(self) - db.session.commit() +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)) + + 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 + def __init__(self, name): + self.name = name + + def json(self): + return { + "id": self.id, + "name": self.name, + "items": [item.json() for item in self.items.all()] + } + + # searches the database for items using name + @classmethod + def find_item_by_name(cls, name): + # 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): + return cls.query.all() + + # method to insert or update an item into database + def save_to_database(self): + 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): + db.session.delete(self) + db.session.commit() diff --git a/models/user.py b/models/user.py index c0b6dfa..7cfa703 100644 --- a/models/user.py +++ b/models/user.py @@ -1,34 +1,34 @@ -import sqlite3 -from database import db - -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)) - password = db.Column(db.String(80)) - - def __init__(self, username, password): - self.username = username - self.password = password - - def json(self): - return {"id": self.id, "name": self.username} - - @classmethod - def find_by_username(cls, username): - return cls.query.filter_by(username=username).first() - - @classmethod - def find_by_id(cls, _id): - return cls.query.filter_by(id=_id).first() - - def save_to_database(self): - db.session.add(self) - db.session.commit() - - def delete_from_database(self): - db.session.delete(self) +import sqlite3 +from database import db + +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)) + password = db.Column(db.String(80)) + + def __init__(self, username, password): + self.username = username + self.password = password + + def json(self): + return {"id": self.id, "name": self.username} + + @classmethod + def find_by_username(cls, username): + return cls.query.filter_by(username=username).first() + + @classmethod + def find_by_id(cls, _id): + return cls.query.filter_by(id=_id).first() + + def save_to_database(self): + db.session.add(self) + db.session.commit() + + def delete_from_database(self): + db.session.delete(self) db.session.commit() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 9ffc1ca..e773535 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -Flask -Flask-RESTful -Flask_-JWT-Extended +Flask +Flask-RESTful +Flask_-JWT-Extended Flask-SQLAlchemy \ No newline at end of file diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc index 10c5a803505ae4f06d77ea85a3ca2123793b842c..682ed374f6dbf78825c3467aadb840fd62133873 100644 GIT binary patch delta 260 zcmdlZepj3~k(ZZ?0SHt=z9nXDos zUukh_kwQ*>dU|S#LT27%UUpYTuF1jdI_$St3yL!HN+#E{n+QsPoWab&%*P_a%*V*X z$g%klyEY@E$>et&;*1HC**F7)je#oSfVh|iNN_N*Fcuk4R^*VK+{Ss9F@16fmolT- oWIIj?VRN8#8eAPC2V;@>#5Gmt-dBb$^=4RZ}k3fml}6!tWxV1^Wq6wXY>6fO{3lY8=eHnqvB?5>QQle5`% zCeLFx;THqh!pyZPI1Qg$?BW|!bU*(SRgKD0TLWcER01) zlm9czPF~J=mN9K|DVH*%>Ew7$31Ks!bShjOBL`!V+2kW!d)Ywxfy{VL@yYYJZJ4D5 FH~@W6FX{jQ diff --git a/resources/item.py b/resources/item.py index 1256555..9ce54de 100644 --- a/resources/item.py +++ b/resources/item.py @@ -1,100 +1,100 @@ -import sqlite3 - -from flask import Flask, request -from flask_restful import Resource, reqparse -from flask_jwt_extended import jwt_required, get_jwt, get_jwt_identity - -from models.item import ItemModel - -class Item(Resource): - - parser = reqparse.RequestParser() - parser.add_argument("price", - type=float, - required=True, - help="This field cannot be blank" - ) - parser.add_argument("store_id", - type=int, - required=True, - help="Every item needs a store id." - ) - - # TO GET ITEM WITH NAME - @jwt_required() - def get(self, name): - item = ItemModel.find_item_by_name(name) - if item: - return item.json() - - return {"message": "item not found."}, 404 - - # TO POST AN ITEM - @jwt_required(fresh=True) - def post(self, name): - # 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": f"item {name} already exists"} ,400 - - data = Item.parser.parse_args() - # data = request.get_json() # get_json(force=True) means, we don't need a content type header - item = ItemModel(name, **data) - - try: - item.save_to_database() - except: - return {"messege": "An error occured."}, 500 - - return item.json(), 201 # 201 is for CREATED status - - # TO DELETE AN ITEM - @jwt_required() - def delete(self, name): - 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 don't exist"}, 400 - - # TO ADD OR UPDATE AN ITEM - def put(self, name): - data = Item.parser.parse_args() - # data = request.get_json() - item = ItemModel.find_item_by_name(name) - - # if item is not available, add it - if item is None: - item = ItemModel(name, **data) - # if item exists, update it - else: - item.price = data['price'] - item.store_id = data['store_id'] - - # whether item is changed or inserted, it has to be saved to db - item.save_to_database() - return item.json() - - -class ItemList(Resource): - - @jwt_required(optional=True) - def get(self): - user_id = get_jwt_identity() - items = [item.json() for item in 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." +import sqlite3 + +from flask import Flask, request +from flask_restful import Resource, reqparse +from flask_jwt_extended import jwt_required, get_jwt, get_jwt_identity + +from models.item import ItemModel + +class Item(Resource): + + parser = reqparse.RequestParser() + parser.add_argument("price", + type=float, + required=True, + help="This field cannot be blank" + ) + parser.add_argument("store_id", + type=int, + required=True, + help="Every item needs a store id." + ) + + # TO GET ITEM WITH NAME + @jwt_required() + def get(self, name): + item = ItemModel.find_item_by_name(name) + if item: + return item.json() + + return {"message": "item not found."}, 404 + + # TO POST AN ITEM + @jwt_required(fresh=True) + def post(self, name): + # 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": f"item {name} already exists"} ,400 + + data = Item.parser.parse_args() + # data = request.get_json() # get_json(force=True) means, we don't need a content type header + item = ItemModel(name, **data) + + try: + item.save_to_database() + except: + return {"messege": "An error occured."}, 500 + + return item.json(), 201 # 201 is for CREATED status + + # TO DELETE AN ITEM + @jwt_required() + def delete(self, name): + 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 don't exist"}, 400 + + # TO ADD OR UPDATE AN ITEM + def put(self, name): + data = Item.parser.parse_args() + # data = request.get_json() + item = ItemModel.find_item_by_name(name) + + # if item is not available, add it + if item is None: + item = ItemModel(name, **data) + # if item exists, update it + else: + item.price = data['price'] + item.store_id = data['store_id'] + + # whether item is changed or inserted, it has to be saved to db + item.save_to_database() + return item.json() + + +class ItemList(Resource): + + @jwt_required(optional=True) + def get(self): + user_id = get_jwt_identity() + items = [item.json() for item in 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 \ No newline at end of file diff --git a/resources/store.py b/resources/store.py index 17f719e..a8392cf 100644 --- a/resources/store.py +++ b/resources/store.py @@ -1,38 +1,38 @@ -from flask_restful import Resource -from models.store import StoreModel - -class Store(Resource): - - def get(self, name): - store = StoreModel.find_item_by_name(name) - if store: - return store.json() - - def post(self, name): - if StoreModel.find_item_by_name(name): - return {"message": "Store alrady exists."}, 400 - - store = StoreModel(name) - - try: - store.save_to_database() - except: - return {"message": "An error occured while creating the store."}, 500 - - return store.json(), 201 - - - - def delete(self, name): - store = StoreModel.find_item_by_name(name) - if store: - store.delete_from_database() - return {"message": "store deleted."} - - return {"message": "store don't exist"} - - -class StoreList(Resource): - def get(self): - # return {"item": list(map(lambda x: x.json(), ItemModel.query.all()))} +from flask_restful import Resource +from models.store import StoreModel + +class Store(Resource): + + def get(self, name): + store = StoreModel.find_item_by_name(name) + if store: + return store.json() + + def post(self, name): + if StoreModel.find_item_by_name(name): + return {"message": "Store alrady exists."}, 400 + + store = StoreModel(name) + + try: + store.save_to_database() + except: + return {"message": "An error occured while creating the store."}, 500 + + return store.json(), 201 + + + + def delete(self, name): + store = StoreModel.find_item_by_name(name) + if store: + store.delete_from_database() + return {"message": "store deleted."} + + return {"message": "store don't exist"} + + +class StoreList(Resource): + def get(self): + # return {"item": list(map(lambda x: x.json(), ItemModel.query.all()))} return {"stores": [store.json() for store in StoreModel.find_all()]} \ No newline at end of file diff --git a/resources/user.py b/resources/user.py index ee7fb9c..0d3cc9b 100644 --- a/resources/user.py +++ b/resources/user.py @@ -1,107 +1,108 @@ -from flask_restful import Resource, reqparse -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 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" -) - -# New user registraction class -class UserRegister(Resource): - - # calls to post a new user (new user registration) - def post(self): - data = _user_parser.parse_args() - # First check if that user is present or not - if UserModel.find_by_username(data["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(**data) # since parser only takes in username and password, only those two will be added. - user.save_to_database() - - return {"messege": "User added successfully."}, 201 - - -class User(Resource): - - @classmethod - def get(cls, user_id): - - user = UserModel.find_by_id(user_id) - if not user: - return {"message": "User not found."}, 404 - - return user.json() - - @classmethod - def delete(cls, user_id): - user = UserModel.find_by_id(user_id) - if not user: - return {"message": "User not found."}, 404 - - user.delete_from_database() - return {"message": "User deleted."} - - -class UserLogin(Resource): - - @classmethod - def post(cls): - # get data from parser - data = _user_parser.parse_args() - - # find user in database - user = UserModel.find_by_username(data["username"]) - - # check password - # this here is what authenticate() function used to do - if user and safe_str_cmp(user.password, 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) - - 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 - @jwt_required() - def post(self): - 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): - @jwt_required(refresh=True) - def post(self): - 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. - +from flask_restful import Resource, reqparse +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 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" +) + +# New user registraction class +class UserRegister(Resource): + + # calls to post a new user (new user registration) + def post(self): + data = _user_parser.parse_args() + # First check if that user is present or not + if UserModel.find_by_username(data["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(**data) # since parser only takes in username and password, only those two will be added. + user.save_to_database() + + return {"messege": "User added successfully."}, 201 + + +class User(Resource): + + @classmethod + def get(cls, user_id): + + user = UserModel.find_by_id(user_id) + if not user: + return {"message": "User not found."}, 404 + + return user.json() + + @classmethod + def delete(cls, user_id): + user = UserModel.find_by_id(user_id) + if not user: + return {"message": "User not found."}, 404 + + user.delete_from_database() + return {"message": "User deleted."} + + +class UserLogin(Resource): + + @classmethod + def post(cls): + # get data from parser + data = _user_parser.parse_args() + + # find user in database + user = UserModel.find_by_username(data["username"]) + + # check password + # this here is what authenticate() function used to do + if user and safe_str_cmp(user.password, 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 + @jwt_required() + def post(self): + 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): + @jwt_required(refresh=True) + def post(self): + 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 \ No newline at end of file From 447a3ecf0c14dfed4d8bce51db1e39efbcfc5ede Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Wed, 23 Feb 2022 12:44:21 +0530 Subject: [PATCH 23/42] added type-hinying to all methods --- app.py | 11 ++++++----- database/data.db | Bin 16384 -> 16384 bytes models/item.py | 16 +++++++++------- models/store.py | 16 +++++++++------- models/user.py | 14 +++++++------- resources/item.py | 13 ++++++------- resources/store.py | 12 ++++++------ resources/user.py | 8 ++++---- 8 files changed, 47 insertions(+), 43 deletions(-) diff --git a/app.py b/app.py index be88c3b..46b1338 100644 --- a/app.py +++ b/app.py @@ -37,6 +37,12 @@ def create_tables(): # 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: # insted of hardcoding this, we should read it from a config file or database @@ -52,11 +58,6 @@ def expired_token_callback(): "error": "token_expired" }), 401 -# 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.invalid_token_loader def invalid_token_callback(error): diff --git a/database/data.db b/database/data.db index 21ada3baaa2a07ab916701414f8eefbc5da747ff..b604776a4f4f3510b7d8eb24cfc1434e00ca74b1 100644 GIT binary patch delta 146 zcmZo@U~Fh$oFL68I#I@%QFLR%LV0ffFAR+Q&l&ii^FQA#sBoKKo12N5K{mItxFo+Q z)rf(Cfd`1efEP%z^3P)6f6IT9{}}%U{#ly^1v>b}L|9oFB-t606LV9G5*@UEJ#=3u jQq44ZgT1~YLvlu9W)VzGkdc)^l${|dKR??+6QT?N2ofnB delta 93 zcmZo@U~Fh$oFL7}J5k1&k#}RlLU~pOAQ0FrsBn#c;sg}|ULcQ!e***mTmGB;$M`pF r78F>(KY4?_z9MIGMq*}>gZ8h7?(0OV1sPcxMA Dict: return { "id": self.id, "store_id":self.store_id, @@ -27,20 +29,20 @@ def json(self): # searches the database for items using name @classmethod - def find_item_by_name(cls, name): + def find_item_by_name(cls, name: str): # 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): + def find_all(cls) -> List: return cls.query.all() # method to insert or update an item into database - def save_to_database(self): + 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): + def delete_from_database(self) -> None: db.session.delete(self) db.session.commit() diff --git a/models/store.py b/models/store.py index d226c87..f50749e 100644 --- a/models/store.py +++ b/models/store.py @@ -1,3 +1,5 @@ + +from typing import Dict, 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 @@ -6,14 +8,14 @@ class StoreModel(db.Model): # tells SQLAlchemy that it is something that will # Columns id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(80)) + name = db.Column(db.String(80), 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 - def __init__(self, name): + def __init__(self, name: str): self.name = name - def json(self): + def json(self) -> Dict: return { "id": self.id, "name": self.name, @@ -22,20 +24,20 @@ def json(self): # searches the database for items using name @classmethod - def find_item_by_name(cls, name): + def find_store_by_name(cls, name: str): # 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): + def find_all(cls) -> List: return cls.query.all() # method to insert or update an item into database - def save_to_database(self): + 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): + def delete_from_database(self) -> None: db.session.delete(self) db.session.commit() diff --git a/models/user.py b/models/user.py index 7cfa703..1946af1 100644 --- a/models/user.py +++ b/models/user.py @@ -1,4 +1,4 @@ -import sqlite3 +from typing import Dict from database import db class UserModel(db.Model): @@ -10,25 +10,25 @@ class UserModel(db.Model): username = db.Column(db.String(80)) password = db.Column(db.String(80)) - def __init__(self, username, password): + def __init__(self, username: str, password: str): self.username = username self.password = password - def json(self): + def json(self) -> Dict: return {"id": self.id, "name": self.username} @classmethod - def find_by_username(cls, username): + def find_by_username(cls, username: str): return cls.query.filter_by(username=username).first() @classmethod - def find_by_id(cls, _id): + def find_by_id(cls, _id: int): return cls.query.filter_by(id=_id).first() - def save_to_database(self): + def save_to_database(self) -> None: db.session.add(self) db.session.commit() - def delete_from_database(self): + def delete_from_database(self) -> None: db.session.delete(self) db.session.commit() \ No newline at end of file diff --git a/resources/item.py b/resources/item.py index 9ce54de..bec2003 100644 --- a/resources/item.py +++ b/resources/item.py @@ -1,5 +1,4 @@ -import sqlite3 - +from typing import Dict from flask import Flask, request from flask_restful import Resource, reqparse from flask_jwt_extended import jwt_required, get_jwt, get_jwt_identity @@ -22,16 +21,16 @@ class Item(Resource): # TO GET ITEM WITH NAME @jwt_required() - def get(self, name): + def get(self, name: str) -> Dict: item = ItemModel.find_item_by_name(name) if item: - return item.json() + return item.json(), 200 return {"message": "item not found."}, 404 # TO POST AN ITEM @jwt_required(fresh=True) - def post(self, name): + def post(self, 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": f"item {name} already exists"} ,400 @@ -49,7 +48,7 @@ def post(self, name): # TO DELETE AN ITEM @jwt_required() - def delete(self, name): + def delete(self, name: str): claims = get_jwt() if not claims["is_admin"]: @@ -64,7 +63,7 @@ def delete(self, name): return {"messege": "Item don't exist"}, 400 # TO ADD OR UPDATE AN ITEM - def put(self, name): + def put(self, name: str): data = Item.parser.parse_args() # data = request.get_json() item = ItemModel.find_item_by_name(name) diff --git a/resources/store.py b/resources/store.py index a8392cf..e016bfa 100644 --- a/resources/store.py +++ b/resources/store.py @@ -3,13 +3,13 @@ class Store(Resource): - def get(self, name): - store = StoreModel.find_item_by_name(name) + def get(self, name: str): + store = StoreModel.find_store_by_name(name) if store: return store.json() - def post(self, name): - if StoreModel.find_item_by_name(name): + def post(self, name: str): + if StoreModel.find_store_by_name(name): return {"message": "Store alrady exists."}, 400 store = StoreModel(name) @@ -23,8 +23,8 @@ def post(self, name): - def delete(self, name): - store = StoreModel.find_item_by_name(name) + def delete(self, name: str): + store = StoreModel.find_store_by_name(name) if store: store.delete_from_database() return {"message": "store deleted."} diff --git a/resources/user.py b/resources/user.py index 0d3cc9b..b074b24 100644 --- a/resources/user.py +++ b/resources/user.py @@ -46,22 +46,22 @@ def post(self): class User(Resource): @classmethod - def get(cls, user_id): + def get(cls, user_id: int): user = UserModel.find_by_id(user_id) if not user: return {"message": "User not found."}, 404 - return user.json() + return user.json(), 200 @classmethod - def delete(cls, user_id): + 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."} + return {"message": "User deleted."}, 200 class UserLogin(Resource): From b251732b71b0c82c6d8585b0e795272c76c8012f Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Fri, 25 Feb 2022 19:01:39 +0530 Subject: [PATCH 24/42] added marshmallow serialization and deserialization --- .idea/.gitignore | 3 + .idea/PythonRESTapi.iml | 11 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .vscode/settings.json | 3 +- app.py | 105 +++++----- database/data.db | Bin 16384 -> 16384 bytes ma.py | 3 + models/__pycache__/item.cpython-39.pyc | Bin 1656 -> 1922 bytes models/__pycache__/store.cpython-39.pyc | Bin 1707 -> 2003 bytes models/__pycache__/user.cpython-39.pyc | Bin 1532 -> 1588 bytes models/item.py | 10 +- models/store.py | 11 +- models/user.py | 14 +- requirements.txt | 5 +- resources/__pycache__/item.cpython-39.pyc | Bin 2647 -> 2900 bytes resources/__pycache__/store.cpython-39.pyc | Bin 1517 -> 1626 bytes resources/__pycache__/user.cpython-39.pyc | Bin 3037 -> 3278 bytes resources/item.py | 188 ++++++++++-------- resources/store.py | 15 +- resources/user.py | 64 +++--- schemas/user.py | 14 ++ 24 files changed, 294 insertions(+), 179 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/PythonRESTapi.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 ma.py create mode 100644 schemas/user.py 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 index 6be470f..2c6c77b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "python.pythonPath": "/usr/sbin/python3.9" + "python.pythonPath": "/usr/sbin/python3.9", + "python.formatting.provider": "black" } \ No newline at end of file diff --git a/app.py b/app.py index 46b1338..07fea44 100644 --- a/app.py +++ b/app.py @@ -2,9 +2,10 @@ from flask_restful import Api from flask_jwt_extended import JWTManager -from resources.user import UserRegister, User, UserLogin,UserLogout, TokenRefresh -from resources.item import Item, ItemList -from resources.store import Store, StoreList +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 @@ -12,21 +13,25 @@ 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["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.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.before_first_request def create_tables(): - db.create_all() - # above function creates all the tables before the 1st request is made - # unless they exist alraedy + 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 an username and password to /auth @@ -35,64 +40,71 @@ def create_tables(): # 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 +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 + 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 + +@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: # insted of hardcoding this, we should read it from a config file or database - return {"is_admin": True} - - return {"is_admin": False} + 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 + 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 + 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 + 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 + # 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') + # 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') @@ -100,5 +112,6 @@ def revoked_token_callback(self, callback): api.add_resource(TokenRefresh, '/refresh') if __name__ == "__main__": - db.init_app(app) - app.run(port=5000, debug=True) \ No newline at end of file + db.init_app(app) + ma.init_app(app) # tells masrshmallow that it should be communicating with this flask app + app.run(port=5000, debug=True) diff --git a/database/data.db b/database/data.db index b604776a4f4f3510b7d8eb24cfc1434e00ca74b1..b252df8f75f917bd63695760bdbe60f5cb4fd1c1 100644 GIT binary patch delta 19 acmZo@U~Fh$oFL68K2gS*QG8><0($^CVFkVb delta 19 acmZo@U~Fh$oFL68I#I@%QFLR%0($^CLj}43 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/__pycache__/item.cpython-39.pyc b/models/__pycache__/item.cpython-39.pyc index 06be40f2629fe1075d542397b758a5f4495fa19a..6050267c09de74188b7724e220e4a8a0218aa262 100644 GIT binary patch literal 1922 zcmb_cOK%%D5GJ_~JuKO>EI(uiX1Qb?*K2AkZAWe$2i5pbOWiN~M4rQ;pyHdzi z3K_n|R|EY4K3GTpmHq@?d-Ao%qKD3K{ZL~c%LQjS!y)<2$KgcHW}RVs``7yZ?>=LH zlCs#)#=gU@pQ8W)^0*^Dvr7s;>L0KI-T{aY57Sw)V;DCF= zz?I&p{>}-Oz#Fihe~CM?o@3nalejSM^F$ZMdz~layvL1z{g~z#+YOdK&?;HLfdvA1 z;7E7mVfR6Rb1H&Lsh?NDopZE#Xsb#0oP6rwL*YUF-(8TdF)Npo1 zw&bd8L;cKwC1{+u!J6zq^NhnXtf00oH)IQAZ=DHPh4zX2jt84)+XB{Q)^80u*4l-2 zt8c(2uG_xSwxEmJ4y+>++otxcklBkd$kccV4)rUfU}>U~EK;-ZS2??Ui6NuSpXSM% zsk{`WTAL^tCmJa`s_9~^q~1*+s1Impx4S@k^C*+X!_8u8s@5w^Aa)m+r7`XJ7`xs; zam+5KGZP)wm-)&bM%+?K=`Kx8FZU%eUyrr6sK>V8zyNXRk%Mh-Y`+R=n3P*sV+|;sun=5 z4wD>0A{6~Y%7+ipL0cH^(atT>JAQX)yh#%Zm!O_=f~Zze#Ta++XPhWa5%5DCzfV`y zo`Cb8;$#UxEMDaG^xmC=Z#de_<_kf-gB|sROdKltUo2E2wgyEZMlwQp=!w^|WWvP~2`j5xy zG|NrpSzgEksT3u>@dt%U@&lW7#(SEMqrw&hQ-6vjC^^V~k~907Ohd`k)&PB&OpIsi zka2XOFn?*8agw}H>$GW=g7|Up^PZx+HcN3DX`M-|5uiRmi=tdml)~y05|m2|iDr|Z z9;c#WQNgYspr{JhY4Hwkiw>`gwzpgpyzL3zahF}|xz}qLznD!Z9!m4_7|H=ty-o|u nh5CYKzasG^iLc4Uv-Q?)bRQN|{iY=QTPjAjjCDjqY>0mWADOxn literal 1656 zcmb_cOK%%D5GJ|1^6F*Fvh21=nxf4m0s{Ylq9_6cNgEV3j4G#v0b;#FIjinNA=d>Y z=oVjn&mjlt=>L-wuRZzJbJ0U*D9ch~AIk-Y!=2&oH{Z-KZnql@$Ip-cia!O6{Yjh6 z;p65xPV*fKV8An$^N4fy69WO>1p`my=mUOso~m!E%Xvm8})4n z(Ygb5Xk4(U15Ie*x~sa-hV2XXo=1DA??A`tJ=ldV+V^1(dbl3IK8A2$>#r-7|5}2| zY>=XA-e5q@u}eTniLvsE@TuQj?a&v~H?WD|47a)EJQJK9UfLt3f&) ze1X#(pqTL)lWZnX@|lNH02{C?e$@A@U&Ohx0sc8rHgq4CDfE5o84~im)2vcjo=$8qOtq={!g@(&mPm=r4Pd*&v;axep}(ZWe2osq!SJUK*XZu}-J$L_ z{}duYopXYy>$vR;y^o9a;w(eJU!wjwMWOox+Ml3t2|#S7Y-PUl-&=|v;K2$Bnq#sS z*vwA%7K~$yuWPLjOhV`v5rH={UeIR>F2RYPbehc*3e}Fz-1HI4rsQ(2u4i$iuwjm`p z)psPN&IYftGOk=# z+r}%rLejJ17d3GmWLsKgafR71Z_}|2+=ZY!6f1R%Z6;Z4Os=ryL4QTBACmZn#J42Y zCG9k7I88-C(-487@VyT2@UH0bh6sbOE_frr>4(1aJi^N-TqSrJRNyW-XT9$SHvD19 N(2sO$xN>cYe*vJ@a?$_* diff --git a/models/__pycache__/store.cpython-39.pyc b/models/__pycache__/store.cpython-39.pyc index e5808788b6b59be6eb8b39ab24c3d0c377c16954..2b6321c99d4d826603df8ce1c9705391fe2cbd5a 100644 GIT binary patch literal 2003 zcmbVNOK%%D5GJ|Y)mye@*@@GpF_1Qh(8B7Wy%|P;Bq&m}u2ChIMT^B+r0iAqA(N{X z65Nx0&7pT6WS|%SiT)5?d&)Vt0tGt5wPN{kDuLn742R^*H#6L@(Wnv_Ywy?g|MCd= z8->MD0^u2u{s|^Q1dT~TeVQ6G5V4W#23{vzt^Gk(1D+_*3LFa<50=la3L`jrE zs>!;joKaB~wNvglWK+~ZYKSICOL7@jiC8*gVp*)5@)PQ>fUYIT2C;T6v1+xeqHX0h zu@3v%m(o3PAEY(WhIltjwU=i~exC^$8&4yzUqg_!k&2Q~O@d$KG13GV`4d?1hX97toPkLfW9AZa~vL3h;>XgbVrE#rY& z2H}FJRrvg~+fAg1!mb|gABIB8QTL@7_rn~Lu^FafvzHC>-$Et3dny~sey+QFll(AC ze|*_{9gd=If5cY$mi zo4_2CArcjk#8c$h%sD47$O#qBnFH|h-D?Nnl~^M^Al-)P05<(x|D7Jw8966^up{0p zVn;lO8j6k?w)Lgq5g=|sDJI-&s@pMCXFp3uI~yRs2O`tQ3*q1aTibEg4`aOp%-kBv zn?=GxZCpHAcewf(c3YKt2%K@kIKH3?k-BlOx(B*7AYF!uFitsb{X1Q{adC$gr>%p{ zt!i*UX;H-U5yc@uAn02DaOkf0aR=1tQGD{ZPxNV5G4;39ZxVM++$3w7iAU|L{~jfs2d>9V#KQ zc=w%NwhMz`pt9tHr(b~em8XvuJiW`hvyPpQRS*QG8U*kxjAP{ML2xt<QjsvkyD@H)WPXMM%%o} zt<`R)VZ3}YLJzTaiz_g%raVt|5nZi#Ah+z}0{>m2zCtS}N^|u!a;|;k?WuP;Wy;$H RmOEIj_Bm*?I<2z@>_7ch${+v$ literal 1707 zcmb_cJ8v6D5Z>LpBTsxt)FaNzi47ZpagE?Q5Cj8`5x58;gsu(>7AMY9KIR_O?#TcN zrm@w!NS}f%^_R9uk;+|wbO8cnW-qj;N0|d=Z}w((=G$*(F6nk#44=36|4e=l82f`Z z>yM9{XDH?oF2I0iEax%j?0W_RybA`N;$!j7i#_lMEDAoLAB(sZa55xy{lNrvus5hE z7IR>+03LhF9|tH63i)2dP313ezX-u!@+FTq(AI(g8qh>*8$#H)U~vap(8hOHZ9)gS z7wjF6w@}}NEvNUO2is`hh8@_&_YUkKz)x&zP?cK!P=d;A!;o%XA>j5zr+K2M@@F;u zaD)Ku#>-0OwOQ<*SIHzj&IzD7b^!M$QyWT|7HK7Am(sZW ze71Xam;IG}$kAc-kWREaY!6YHvoQ$=4&s6|vGA7cJ0uUh-#jG9?_Uc*iKqz%C}t0r zQ#|xj=NCTb3$|pxiZd*`y9{wNHf4bq75h)EpCWtCP%m3}*0v6^)KtSVpBz%Gzfjoh z@!BOQ!2Us24wK9rqO$UiJS$Rg+}VI4o5%+RXuce%rc9{CI%>eBkN`)lBCIbJIf)%be9GVCM^rB7=UeHJLGAlKfeQ4&m?b@tV#(m-$DePtf}c>4P<-cX{`tU7B!e0x4}vN*w?5jP#C_XXi=w zpQS4$5|OETThrNiUS{WcVVf_DN}Ve0=E61xl}?M(dSp6UWl4pUo7d^YO^c>+aC2eX z!z?i-SNQhLneL%~}CiXZyUs~`C` bT-C2e9Js%#v)&clh6nZa52>|XJ9fmsjY@k$ diff --git a/models/__pycache__/user.cpython-39.pyc b/models/__pycache__/user.cpython-39.pyc index 1807a90bcfd423268ef2009b51372dae8d3221ff..2c7d39a2afca9b91c378d827ea492ea881057fcc 100644 GIT binary patch literal 1588 zcmb_c&5j#I5bo~z@%VQguYnLyw1)_34#Y2jBBT%ype!q#T>8=)PqmX-=EvzCCvxOl z_L>{V9M(ra2rtoBPI(1RRE^`^#pHx&tIJi@H9cQ_RW(V!-(k4E{^R!SXP>b@$+)=! zG`_`czefQKc+Ltgc_F0WWQ*J@eCczhxem0@p$_Kab4FXXeqi8%f5E`l{(1DqlWhp5 zEDqn}T`U&P|1L{w7d$Pqs*Jf4FiYv==DK?S@95P`a$r&bmmc02;0{56_g2W3_Ak-C zjKJUU4VUc$yQ90h3&A@9A+#=h+0%Wr8ARx5L+8SO!{q>dN9gOKuLu3cH-rHkz2h*1 zTlmzi57r2d(Hg-3vl+YgQ>)G63Un@mC93vG%w>0BvLZ1n^_yP3KSS_a;UYRMlR~>_ zky!hxGLU*VvL#*Meu&!+QLOozDYh0UG4Uze@>A17f9yHm>U?e@G)x=CmYE*@{q=ZK zXvmU@UCy2-p!H(%2$pG5Bf}?22`AHPUcX9=o}8KLT&K02oUQ8TRr%|q>62uUO$tiG zP6+7z#mYrWWo1??MQAKG2xC0tuSZuI-`@wsq5cfn)qLpO82}*V(@|gD>%Tk8AvI zTp17%4H}M-`$hT{g?G@Xk-$rAj1A{I-P*l=tD?y2 zEjL{}JOnwk$xE&3N$dEaZA;Hf+l9VU;h7 z(zSjpYyC_c^J#;5TAQqVc0G*Fv$Zh;I_su6PI{qn#=wlp<7H)SK10n=Cz!h=u5)ZO zYG^}WKALq)?}&u&4LSbAFcN&^(H{6s%xTLpv(K-@2?0Ss2vK1Q8ahBI5<-ZAD8ivxMOs}v>u}~C$?h3&<-AjyaIGKQ0!+^nn?~! z3gFVy{wP4HfdKDV$h!7V&_4;mpYa)&jRn660o0C|Y(X7D+}pYX4QL**FI+C6-GX*$ zFGB~G@VpDl(8YZPRuJQ=YrM1Cyvac)G8m(2w|iW+_D!5dW}-gm$@eV;I6z`97TE&j z1&ZyWn(`@AY${OWYxLPE-|Pt&(&Mry)h^tRto@W5=y}e!IvJW8-ZFJmr_3~P`}qK# z>};8Qqz8rF*_ssZ^X%=*{&ut<@1z9I?$Fa7?N3~&RGh_yQXRZ!sW^<+_~GisdptT1 z7;ri~TVxaP67&)pQ#K;uz`qJ)LJ(vTgYyY;Pnb5wlsHeh3CSj$m7;!e{>bLp3R;%9 zVEk}tj)0B^<==FKi|mW+tLRYn5ualAV}2rLNE#vFRYK1N2V-p}vN4R4LL0R^altS) zwm^*DAh8vH3xK-AI0Fn-=e)0Bo(h~eT%Utm4Er~rB|TJpr0N=O(o-=ka@0J}6~oO- zVAlQw6T{!DwT@Q{Wa|VQY{q$Ido#vy3j55^B=vkl6uY|B*2a0}ya>S626>vsg`sjU zLhIV-qgF+(AS$BW$m%;7bkfsBKXUwTx*cvq^BCk7YwzZxlZa9soSLHEn8YNNQUNu#@G@Kq42#f z$DauO&=b5_O6ZqSy+GFOK@u1Gu?x@ZR$&dwPgogsRT39It6uy$eFw@ihrBI*0cQM3 A&Hw-a diff --git a/models/item.py b/models/item.py index f5fe323..4155aad 100644 --- a/models/item.py +++ b/models/item.py @@ -1,7 +1,9 @@ -from typing import Dict, List +from typing import Dict, List, Union from database import db +ItemJSON = Dict[str, Union[int, str, float]] + class ItemModel(db.Model): # tells SQLAlchemy that it is something that will be saved to database and will be retrieved from database __tablename__ = "items" @@ -19,7 +21,7 @@ def __init__(self, name: str, price: float, store_id: int): self.price = price self.store_id = store_id - def json(self) -> Dict: + def json(self) -> ItemJSON: return { "id": self.id, "store_id":self.store_id, @@ -29,13 +31,13 @@ def json(self) -> Dict: # searches the database for items using name @classmethod - def find_item_by_name(cls, name: str): + 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: + def find_all(cls) -> List["ItemModel"]: return cls.query.all() # method to insert or update an item into database diff --git a/models/store.py b/models/store.py index f50749e..4fa92af 100644 --- a/models/store.py +++ b/models/store.py @@ -1,6 +1,9 @@ +from typing import Dict, List, Union -from typing import Dict, List from database import db +from models.item import ItemJSON + +StoreJSON = Dict[str, Union[int, str, List[ItemJSON]]] class StoreModel(db.Model): # tells SQLAlchemy that it is something that will be saved to database and will be retrieved from database @@ -15,7 +18,7 @@ class StoreModel(db.Model): # tells SQLAlchemy that it is something that will def __init__(self, name: str): self.name = name - def json(self) -> Dict: + def json(self) -> StoreJSON: return { "id": self.id, "name": self.name, @@ -24,13 +27,13 @@ def json(self) -> Dict: # searches the database for items using name @classmethod - def find_store_by_name(cls, name: str): + 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: + def find_all(cls) -> List["StoreModel"]: return cls.query.all() # method to insert or update an item into database diff --git a/models/user.py b/models/user.py index 1946af1..5439438 100644 --- a/models/user.py +++ b/models/user.py @@ -1,6 +1,9 @@ -from typing import Dict +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 @@ -14,15 +17,16 @@ def __init__(self, username: str, password: str): self.username = username self.password = password - def json(self) -> Dict: - return {"id": self.id, "name": self.username} + # 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): + def find_by_username(cls, username: str) -> "UserModel": return cls.query.filter_by(username=username).first() @classmethod - def find_by_id(cls, _id: int): + def find_by_id(cls, _id: int) -> "UserModel": return cls.query.filter_by(id=_id).first() def save_to_database(self) -> None: diff --git a/requirements.txt b/requirements.txt index e773535..242289e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,7 @@ Flask Flask-RESTful Flask_-JWT-Extended -Flask-SQLAlchemy \ No newline at end of file +Flask-SQLAlchemy +Marshmallow +Flask-Marshmallow +Marshmallow-SQLAlchemy \ No newline at end of file diff --git a/resources/__pycache__/item.cpython-39.pyc b/resources/__pycache__/item.cpython-39.pyc index 4b85e3022593835d2c61b9729e01f66a32d4b3d9..6fd759d9ef2c669b3efde90a6c1f5919c5ff7dc3 100644 GIT binary patch literal 2900 zcma)8&2JmW6`z^?;EEz8N>*LVaNBL#)MkrP?x9GEI<8fVHPpzoK`LmxK(JkLhSJLW zLC*|ri=j^P)kO{T=mQ1h;%onk=3m%r(Sy$IsYrsnH!E6l>_e8A$2V`@%+CDY@4eZm zUat~({`u#NJO8N>@^9>%e{AU7f~GVGPB=|SN&`w!X9-KKz)J1FHhn8`Qa5nZN>E9? zz%w>GnM>ig#;Fp~auP6MP3>b6IKtU?ITaPogg2ez^q6w%IiPOrwy2)k?br5sQ9B}{E*7}+ zl5v+;p4mtAhz5%SMmXc%b1Le5?j+vBT?Fyz+T!G_#)bZW!`|uEEP7M zsBnfd?u%^*f~^mRWmw8E*n1)w7M{|%6c7Ojo}ERhC|oJ@NM?NoN1Z(uDA;a8Q@;SA z$&^muq=#(6nt>ZIQ^R{R1$l|KR}`C|}GS7NZw!Mjpck8mLK^bD+;A@fFitG(UckUzkg z=aF*zNff8*omjc$f;V7}YJeb?%RJyCveKiTHJ&%TEWO@B3+ZU6*abzCeL%&~4XV}* z9Zp6>wlom)JOQ(n64i+nI-s#tAY16pND2SmF;K8+btGO-asfnh&hSURhlZb_!2B_+ zJ9CmhM;mfQVhQ;!GpylA-vxVlR3Jsw^5`<8ApW1GAh>jz7YZ*lxNDlvo<&Wfa#?{2E0I#TnU+hKx7BAV%ss2oC~iQ)YhNSFU?b z_ZW<?p? z26tLnmb}gzFF@3A=RIf&X|PKsI2 zJRRN?u+o2_k+F~IfQQ6V-MW$F{U}j4L7BN04%T@km%#h`ZjF=G+19SWv@*oMf32Ox z`6IBUXukL`5P1;pg=~K zJU(0f>_ESR(Z51dSm!*TtVboA@(GNTM?RmdgSB z`V&vJ$hau8sH~iL^k5#+S0t*1fhmi|Z*e--N$I0_AH}aw;03WRd<_WE4oFK;K|-jiQndwDDp8dLRg8j0leIUQbUrq= zH*Hp%Q+h3;5*GwWiNvM<3H%Fl<IN>xWG1e@n1&di>#TLcBmD`0AJB1s&rf=u1!i&A49k&ZV z_KnWTr-~pBDEWeLm$x1g-V)B96;DIo<8A1-P5%_9%Ovuj!j5DyVzu*mo~nCwONy-H@ziZ3fJ z@*eK;{O3Yv0g5U?2oh6HVkWG8`+&rba5h~|MT;|Tar==?iP1R11C4vAY43q&+o>QB+zB&SF)W4PDojH&7-qcN zW8mJ=XF&tYbtvi`5Sr}MF+HHWY|P}DG23ScWEpOsvy5`9&)}@~V%;i)Qt7%F%`|(! z@%q)UK$2aik?=hcXsqfRW99JUP%Ypsyw`UwTctEgPq!f3qFe{_dpQlQ=58hzbgv&^5ylUg}CNz#*mT#h)WBMbI`ezH=wb5P8HVp-KnI>b}rx_WuU+A}V*BS+5 zYu`B_l-wodSqe3fopJ|ex_#!7uK~DA1Z>;`poM|XJA_UEbPjqEj%LC%mm=jmp?Huf zt)6}d2##j2m7$Qbl3~^B4T06&>@Uzi{SD3=wG6y+7TOYnxOPn-$l5n`lFD^ecT~D9 z5?v)c)#+NQgq#D@C$>C|)8=uSjYIMk>?8J#c-4#yDy83smcmKI^65PDS%)ziP?yfJ zkqM(NTD^enF`aI}OilI(Qi;=ZM4iXrImVUl1J*z^Yi!9+0VNBOGPm zUjw7amMy3zfY$TPgrqO4;ut$Ho51Gw@-!>dOM!En*_UBWg(oGe zV6qsUs=dS{fF!B?Bq=IB%muWAB-t9KIi`Umkq&H8PcJpe{to7LX^vM1Dd))QVF7s@ zVmhx@-TMCCSD|raTAOUAp8g8f|MzH|oI5-koC;6fTH86-r@rhy$$_K$6`XHDWcLSr#<EizWr&lVbLKSrFCUl_q+xB7;=`!ebrUfd?XBo!kEc2QWz} diff --git a/resources/__pycache__/store.cpython-39.pyc b/resources/__pycache__/store.cpython-39.pyc index ba56a097635e5c0b032ff94a1a441a6e3abb55b4..f830863bbb075acc04f336d96d44a68e0863220c 100644 GIT binary patch delta 575 zcmZ9Jy-piJ5XW!7Y=b$32=PaxNG>d-E5a2#04NHiNg}|hoWfb(F5wWqvv$^soQ5Ja zBvK@6n-qBgiWKR23}}{)o)@6XtRs;qE6qS11}0R(k0oDNXco*zS5TrkhF6EFf>dojOo5t%wh_xtHct6Ziq86HWc^*G&8 zlT6yYEIP7KonCopKT4z1JyRa*%tE?1wMS)fu+w`h$9ZZrloOL_NTC@TZ;Yp&ebBZN zo*EEQ_m$j|iYsU%sbXwu0ipRHETQN5xC9$KSV3UUPddY#E%t=eT%UCbb*HR_{&&`0 z_4VvT0*o{cqC5j@+$DSVGNjiiB`X3gro@CV&qBF>55r&^4O)o5dr-K8vj{i9-8%n5 zs_qw0NaAkz*6spc{BK?pqqZKZP(;K&#q(Xf%E4~n()R?~LpVw1Gpd(m`PqD4Bt0p)##9{>OV delta 497 zcmZ8dJxc>Y5WTtEy&UF4gE1#TurLTG8VkFCsD+(~7^`q_T(TRzV6w8i3b7NcL_qt8Eh?_O;b^h(nOBh+yLCib<|z1zw62MQ2P%!-*LU7N}4Aejzid5PC`_ zje497evNYK!Kv3^3J`eU*_VavkB!Ofe&DKCr>^GF+NRm)*&g<$Inr&{xc`lxgH|;A z|Dfs?-GHln9Kf>L!CT;|E9`HW+Y2MK?8{=dcrOvMaZKfYfVeT@a|WsgiU#spH(T~~ VyQDMIdcb}4fj7WcEABchegn^;cANkJ diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc index 682ed374f6dbf78825c3467aadb840fd62133873..0dddd94e4fe09536052a2243e59bb73a4f4e2f21 100644 GIT binary patch literal 3278 zcmb_eTW=f372cWMC70xqA|=_8TsLKt-q;J}6fMxAL7~)jiy*FGSW1H~3M^Kfp|tW| zl$oU!5ma8}R~Idi$2`bDU;Hcj+V&^xYoGKN0<_3?W=V-^Z1klR=4j5D%g#CHJC~^4 zZd&-={oBU=I~~jV7ct8}2h28x{wIjA1WT-x1&q<0Cp@(S+sy66N!`FTb0?{#^`LI% zZsMhlppiC%X4(o`sUP@hJ7}A=TGB~Zf)z8bC*8Cc^vv8#R@1d$jajmHZUyVVwM0WS zpIM?Q?Q=W01->PG@V?=1gKvut_>SS<0KX!-;Jb$30N)d<;8zX5Db{wa!TKfiu?DQH zEBWz6>H<^mvDEoQjU?tPzmJkwL`9ru50uIkM81y3GSr0%N9pmvE!Rd$MuiNc(MW0? z7WosImFsh%l4B+H;aut;ofIK`C{|LG-2+*KB#DL0inur}>vIXd()dtI^}Afi1Wc2d z-O-^;BU<{^!}sog@bLZJy%9Zr`DcUK#?Tue(h8Wc0xoUA1b=3ov%nEHly*v=E_^Hx zVqHizV%WU+k)sI;_c8RRAk)>tx?lyruwPg&*ctoC%7%_rIH%jpI^)cm-k$LzSFkbv z$f{X?03gozr+jz*|DJWvDry(?8D6l7^XiYn7Ea7)|1MU>R&34KU)i77nLV>FycZT+ z?}{3B)M>}S+qwKR-dB1Pyf}+eS=LjSIXaNj@4uJzN!mY&i^G0#7!`)4P5mf=Q^aXs zegYNsFffNC4sDR;{YVHY`g&qqHl8HO>G1L&PB7$s?gSR!YzKM(3F# z6P4cl65E5OsT6Uvr6>D`k&yCu>w%bz3zTM%z-C-V|_CCbU1saC5d*`KFdSOVW z48yV+hG{M)3C;a5{CE;2^Oe>piL_279w5}4xZLQlM7~ZkBoymy3{95vY|f12E8xA# zG54_T8obgG>~p|J*v}k5*p>EN;t1DBYN7_9)JuodKz!6maRk+f{I>W}WYD;ep}z-0 zsGcz(n$NhtLlFYZ*#?!s`i#F|!Z8SRcT^AizgVH``&nM}$N3}^!^_Wr%geuGY2Z{K zXf8ky3$+G$>4-^s{L)qwjq2M(zD9)nToE!)X#-imz(ji>mfd6y^ZC@H&keEkdNqdF z{(3cL{DPZsf+A4OHOkZ~WfZ|deHUcfHfkZy$U=&tBGU|PqmLS39;{bO;aKJA6_(Wx z==^`_QLA?T8cW(#6JDv}v1!XFQ?2g|>Z(u2ze(gRB5#9~cAORJhs6Gf$o1uBW`Oi9 zEc_h9Sjq#CyB@$q(h012XS^I4c2hmwio8VglxPH#hoWeI;?KwMhi?*nrquS1#nJc`{xoD6#MN_okS>JfJy`xs} zPSph|y<>#sNv_1+OI~_c^~E*CXu#BuNu$n$0G;Fq2grqSHsGayqlUF_6fc7e6A9Dx z_p_%|7W*i2rgTM#9>#w`I1D-l_-dVALgA-w5uwOdw~0`)RBsS*iBO~~vUSN3Gj*D{ z#|l6g)T*G_qz!k7n8^7F&3;Pc8Xg82lzSdwXqs3^G4mAXtRuzwjFJxjk7M(HJMOeY zU^FhTa-I4Hjx1exjkhf<>|j)h3wTo``3lxZy=~e?A74&f=V+9w%1eoBUVBY3$LI4& zaRYK>E<){nx~UIRSag2VI%5U9;FPOS8<7U#@DoU*aIRLvG%#W$`(n&7P3 zAf=7+H{g|lDV&yczcOY9xhBt0LTa+9$$r(V=M|#%swh^J!(LwreumWq!`w^(>@Y>L z*PHY$tS+0B4Eh`qBOGdIj9Fb||61wqQRDTv>bLf&49wws!^mV{a+fhOxr>ZU21XRp z&HM}oo>3`SnD{s99c)tuc_uk>)gS@c28ohVd+CoRN}-1{)jw!t@}z3`US-&)I50PC zjq-eN`Q39sQZ-mQdkx5Uu`pR&;4#iTrbxCkxKp}g)Z!;qv(h$mw3@m=ccVyaG~JQX zhiQ}~`H6|hpm!qGlc}5>47G#?=s-YEt!( zl8FiLz^|I8xyZ++vaz2;qbCV^F@wM=Nl;s0lDCN}g9D??t28gYcdHiUm&h`fri|bt VuIcZtXa4tUcK3GoR`<Y}T7@nD3+iPdzI%)b{z?Aag78xWE2!v2eTTn%bsw7-i70bpmb~f3y-I;aM zSjj297JjXw;7|eLd1sv_X%n&5jOU%t_v3k=nP_^t#_(NU`YT%X z8T*rjgI^iKH5Bs)D#;{ISjq#Qia>CZMdGAx;M%s6l+togPAfqr^#U)g2Gz6{)a;m> zOr?I{+jc3LPU}IPGbMIT?6RPt8mlw1tY&3Jdb0W$yV;)F!Je;}oRa<{CVj=Xo!}(o zX<3I{xAGk16S4uhVdYbhXXGs8Su3B0d{WLqp0n~9d1{fhPCtcJtR-@9L7BlwcU10a zb$=LXqgp)oP1IGP$#mFBhpke6vZGa$sW9qvlrdp8SX2FcZqlh$S1YqJ>GfCFvk+U2 zVy$G}=&3BEo>;1W7H1oIWzvIFs^1!=zZpoCknqKw>o>l>b9-?q_eSXNN2yvGpI=&u zO|u)TL^eB7zdy*D%c`l;VYbn7ayQ!;7K#_AxhpCe=H4(eW__S#hip9f2?*Cv%p5Ak z0xnq~B$wh53moadFK+IWQVXgVn@s5r$HcwQLBmD4fnsi=%GeIyWm`NGJI*GT{2bfm z`jQmViMe#UVv%K{%VM_4f8{^$EwL$f++7AFlw=t*Dm0_z&1=Aq<&_kkje2T)<$AwK z4rs2&*-A58i83paKbld3ILM8rdI?0*>Pp3#&Gd7~TmWw^XyXVG~ECq zhOB4Y6LsM`b^MQK$c()Rv}xWv;^KuNsT78JEez9v93|BD!|?tnN{SJE5j$C34pbMY zZwA#XD25Q=IaYoN9TeYdH0d&`J#;9iz{fTZT(lkOK4NH>WC^h==Pv2mpRgYQ0##RvC9Z`^LiFDpPiuHngsIhVAPlGr#~aUEvJZ1Z7({p z%-vNp=s$6Cr<0hVLOw-;>2uiQ1>GnRU;-O+rjl0|zV~-AMW9WF*uVDKBsSYRu z=rM;0rNRXLI+kfNdpvE8L2|21$@atw{RW1DxuQ4h>OuM(TKY{|N=Pay3S3?;W`2UU zv3xBG3p_ravNjas^R2R``07`wdW}|2brR4(ssKZ&-=^+&sM_D(wk$fD7-*wdA9+IX znkTrX-VVy4k0N^w#hgWT6hNE{!ci1LM4^Ms-4v@O(26+6 zwn;8St`Gnpi!CSftWncYliO#A`)cXfhz`?mpiLj zT*Tq*;sM^@3nb2HYa#(8VHJOR3RUjlqG$IlbvkA6dOVaU50A6tTg8)-*N z%uinOV+E6s_u~f`{SL(xessB}p5y&U`MmE(^5SEV=Y)ja2?_g4^d4C#o*_$kOH?K> zJ>*&P>hs*AeaW-rRl2&duh@cDH;J6>)eXOXsjp(1w$QWg6Pj4mA{MSB&*gq+q%{C< z@64duSL?+)=qPXYuwdfNGO_T?LA?uq>W}Dv{YcoB#rek=AjT*>$33p8XTEhdpYDR` z*6`*s8LoRx2-0M1Fte`o+E|TxZKI$jUSBrt^4VemJSJsM4hz+FAxi)V0rk diff --git a/resources/item.py b/resources/item.py index bec2003..682871c 100644 --- a/resources/item.py +++ b/resources/item.py @@ -1,99 +1,111 @@ from typing import Dict -from flask import Flask, request from flask_restful import Resource, reqparse from flask_jwt_extended import jwt_required, get_jwt, get_jwt_identity from models.item import ItemModel -class Item(Resource): - - parser = reqparse.RequestParser() - parser.add_argument("price", - type=float, - required=True, - help="This field cannot be blank" - ) - parser.add_argument("store_id", - type=int, - required=True, - help="Every item needs a store id." - ) - - # TO GET ITEM WITH NAME - @jwt_required() - def get(self, name: str) -> Dict: - item = ItemModel.find_item_by_name(name) - if item: - return item.json(), 200 - - return {"message": "item not found."}, 404 - - # TO POST AN ITEM - @jwt_required(fresh=True) - def post(self, 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": f"item {name} already exists"} ,400 - - data = Item.parser.parse_args() - # data = request.get_json() # get_json(force=True) means, we don't need a content type header - item = ItemModel(name, **data) - - try: - item.save_to_database() - except: - return {"messege": "An error occured."}, 500 - - return item.json(), 201 # 201 is for CREATED status - - # TO DELETE AN ITEM - @jwt_required() - def delete(self, 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 don't exist"}, 400 - - # TO ADD OR UPDATE AN ITEM - def put(self, name: str): - data = Item.parser.parse_args() - # data = request.get_json() - item = ItemModel.find_item_by_name(name) - - # if item is not available, add it - if item is None: - item = ItemModel(name, **data) - # if item exists, update it - else: - item.price = data['price'] - item.store_id = data['store_id'] - - # whether item is changed or inserted, it has to be saved to db - item.save_to_database() - return item.json() +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" -class ItemList(Resource): +class Item(Resource): - @jwt_required(optional=True) - def get(self): - user_id = get_jwt_identity() - items = [item.json() for item in ItemModel.find_all()] + parser = reqparse.RequestParser() + parser.add_argument( + "price", + type=float, + required=True, + help=BLANK_ERROR.format("price"), + ) + parser.add_argument( + "store_id", + type=int, + required=True, + help=BLANK_ERROR.format("store_id"), + ) + + # 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.json(), 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 + + data = Item.parser.parse_args() + # data = request.get_json() # get_json(force=True) means, we don't need a content type header + item = ItemModel(name, **data) + + try: + item.save_to_database() + except: + return {"messege": ERROR_INSERTING}, 500 + + return item.json(), 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): + data = Item.parser.parse_args() + # data = request.get_json() + item = ItemModel.find_item_by_name(name) + + # if item is not available, add it + if item is None: + item = ItemModel(name, **data) + # if item exists, update it + else: + item.price = data["price"] + item.store_id = data["store_id"] + + # whether item is changed or inserted, it has to be saved to db + item.save_to_database() + return item.json() - # 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 \ No newline at end of file +class ItemList(Resource): + @classmethod + @jwt_required(optional=True) + def get(cls): + user_id = get_jwt_identity() + items = [item.json() for item in 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 index e016bfa..4fbc37e 100644 --- a/resources/store.py +++ b/resources/store.py @@ -3,12 +3,14 @@ class Store(Resource): - def get(self, name: str): + @classmethod + def get(cls, name: str): store = StoreModel.find_store_by_name(name) if store: return store.json() - def post(self, name: str): + @classmethod + def post(cls, name: str): if StoreModel.find_store_by_name(name): return {"message": "Store alrady exists."}, 400 @@ -21,9 +23,8 @@ def post(self, name: str): return store.json(), 201 - - - def delete(self, name: str): + @classmethod + def delete(cls, name: str): store = StoreModel.find_store_by_name(name) if store: store.delete_from_database() @@ -33,6 +34,8 @@ def delete(self, name: str): class StoreList(Resource): - def get(self): + + @classmethod + def get(cls): # return {"item": list(map(lambda x: x.json(), ItemModel.query.all()))} return {"stores": [store.json() for store in StoreModel.find_all()]} \ No newline at end of file diff --git a/resources/user.py b/resources/user.py index b074b24..9e810ec 100644 --- a/resources/user.py +++ b/resources/user.py @@ -1,4 +1,6 @@ -from flask_restful import Resource, reqparse +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, @@ -7,37 +9,46 @@ 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_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 registraction class class UserRegister(Resource): # calls to post a new user (new user registration) - def post(self): - data = _user_parser.parse_args() + @classmethod + def post(cls): + try: + user_data = user_schema.load(request.get_json()) + except ValidationError as error: + return error.messages, 400 + # First check if that user is present or not - if UserModel.find_by_username(data["username"]): + if UserModel.find_by_username(user_data["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(**data) # since parser only takes in username and password, only those two will be added. + user = UserModel(**user_data) # since parser only takes in username and password, only those two will be added. user.save_to_database() return {"messege": "User added successfully."}, 201 @@ -52,7 +63,7 @@ def get(cls, user_id: int): if not user: return {"message": "User not found."}, 404 - return user.json(), 200 + return user_schema.dump(user), 200 @classmethod def delete(cls, user_id: int): @@ -69,14 +80,17 @@ class UserLogin(Resource): @classmethod def post(cls): # get data from parser - data = _user_parser.parse_args() + try: + user_data = user_schema.load(request.get_json()) + except ValidationError as error: + return error.messages, 400 # find user in database - user = UserModel.find_by_username(data["username"]) + 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, data["password"]): + 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 @@ -93,15 +107,17 @@ def post(cls): class UserLogout(Resource): # Loggig out requirees jwt as if user is not logged in they cannot log out + @classmethod @jwt_required() - def post(self): + 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(self): + 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. diff --git a/schemas/user.py b/schemas/user.py new file mode 100644 index 0000000..820fe50 --- /dev/null +++ b/schemas/user.py @@ -0,0 +1,14 @@ +from marshmallow import Schema, fields + +class UserSchema(Schema): + # 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: + load_only = ("password", ) # makes 'password' field load_only + dump_only = ("id", ) # makes 'id' field dump_only. + + id = fields.Int() + username = fields.Str(required=True) + password = fields.Str(required=True) + \ No newline at end of file From a4eaa65cd931835b754ab6ed3fb912be76dc2f09 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Fri, 25 Feb 2022 21:32:18 +0530 Subject: [PATCH 25/42] added flask-marshmallow to user resource --- app.py | 4 +- database.py | 2 +- models/item.py | 87 +++++++++++++------------ models/store.py | 75 ++++++++++----------- models/user.py | 50 +++++++------- resources/store.py | 57 ++++++++-------- resources/user.py | 159 +++++++++++++++++++++++---------------------- schemas/user.py | 23 +++---- 8 files changed, 231 insertions(+), 226 deletions(-) diff --git a/app.py b/app.py index 07fea44..009bd7a 100644 --- a/app.py +++ b/app.py @@ -34,7 +34,7 @@ def create_tables(): # JWT() creates a new endpoint: /auth -# we send an username and password to /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 @@ -113,5 +113,5 @@ def revoked_token_callback(self, callback): if __name__ == "__main__": db.init_app(app) - ma.init_app(app) # tells masrshmallow that it should be communicating with this flask 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/database.py b/database.py index fa42112..d43f174 100644 --- a/database.py +++ b/database.py @@ -1,3 +1,3 @@ from flask_sqlalchemy import SQLAlchemy -db = SQLAlchemy() \ No newline at end of file +db = SQLAlchemy() diff --git a/models/item.py b/models/item.py index 4155aad..603f2c8 100644 --- a/models/item.py +++ b/models/item.py @@ -1,50 +1,51 @@ -from typing import Dict, List, Union +from typing import Dict, List, Union from database import db ItemJSON = Dict[str, Union[int, str, float]] + 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), unique=True) - price = db.Column(db.Float(precision=2)) # precision: numbers after decimal point - - store_id = db.Column(db.Integer, db.ForeignKey("stores.id")) - store = db.relationship("StoreModel") - - def __init__(self, name: str, price: float, store_id: int): - self.name = name - self.price = price - self.store_id = store_id - - def json(self) -> ItemJSON: - return { - "id": self.id, - "store_id":self.store_id, - "name": self.name, - "price": self.price - } - - # 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() + __tablename__ = "items" + + # Columns + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(80), unique=True) + price = db.Column(db.Float(precision=2)) # precision: numbers after decimal point + + store_id = db.Column(db.Integer, db.ForeignKey("stores.id")) + store = db.relationship("StoreModel") + + def __init__(self, name: str, price: float, store_id: int): + self.name = name + self.price = price + self.store_id = store_id + + def json(self) -> ItemJSON: + return { + "id": self.id, + "store_id":self.store_id, + "name": self.name, + "price": self.price + } + + # 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 index 4fa92af..5064b0a 100644 --- a/models/store.py +++ b/models/store.py @@ -5,42 +5,43 @@ StoreJSON = Dict[str, Union[int, str, List[ItemJSON]]] + 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), 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 - def __init__(self, name: str): - self.name = name - - def json(self) -> StoreJSON: - return { - "id": self.id, - "name": self.name, - "items": [item.json() for item in self.items.all()] - } - - # 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() + __tablename__ = "stores" + + # Columns + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(80), 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 + def __init__(self, name: str): + self.name = name + + def json(self) -> StoreJSON: + return { + "id": self.id, + "name": self.name, + "items": [item.json() for item in self.items.all()] + } + + # 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 index 5439438..100f098 100644 --- a/models/user.py +++ b/models/user.py @@ -1,38 +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 + __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) - # table columns for users table - id = db.Column(db.Integer, primary_key=True) - username = db.Column(db.String(80)) - password = db.Column(db.String(80)) + # def __init__(self, username: str, password: str): + # self.username = username + # self.password = password - 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} + # 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_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() + @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 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 + def delete_from_database(self) -> None: + db.session.delete(self) + db.session.commit() \ No newline at end of file diff --git a/resources/store.py b/resources/store.py index 4fbc37e..24ce513 100644 --- a/resources/store.py +++ b/resources/store.py @@ -1,41 +1,42 @@ from flask_restful import Resource from models.store import StoreModel + class Store(Resource): - @classmethod - def get(cls, name: str): - store = StoreModel.find_store_by_name(name) - if store: - return store.json() + @classmethod + def get(cls, name: str): + store = StoreModel.find_store_by_name(name) + if store: + return store.json() + + @classmethod + def post(cls, name: str): + if StoreModel.find_store_by_name(name): + return {"message": "Store alrady exists."}, 400 - @classmethod - def post(cls, name: str): - if StoreModel.find_store_by_name(name): - return {"message": "Store alrady exists."}, 400 + store = StoreModel(name) - store = StoreModel(name) + try: + store.save_to_database() + except: + return {"message": "An error occured while creating the store."}, 500 - try: - store.save_to_database() - except: - return {"message": "An error occured while creating the store."}, 500 - - return store.json(), 201 + return store.json(), 201 - @classmethod - def delete(cls, name: str): - store = StoreModel.find_store_by_name(name) - if store: - store.delete_from_database() - return {"message": "store deleted."} + @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"} + 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.json() for store in StoreModel.find_all()]} \ No newline at end of file + + @classmethod + def get(cls): + # return {"item": list(map(lambda x: x.json(), ItemModel.query.all()))} + return {"stores": [store.json() for store in StoreModel.find_all()]} \ No newline at end of file diff --git a/resources/user.py b/resources/user.py index 9e810ec..95ce023 100644 --- a/resources/user.py +++ b/resources/user.py @@ -3,11 +3,11 @@ 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 + create_access_token, + create_refresh_token, + jwt_required, + get_jwt_identity, + get_jwt ) from models.user import UserModel @@ -31,94 +31,97 @@ user_schema = UserSchema() -# New user registraction class + +# New user registration class class UserRegister(Resource): - # calls to post a new user (new user registration) - @classmethod - def post(cls): - try: - user_data = user_schema.load(request.get_json()) - except ValidationError as error: - return error.messages, 400 - - # First check if that user is present or not - if UserModel.find_by_username(user_data["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. - user.save_to_database() - - return {"messege": "User added successfully."}, 201 + # calls to post a new user (new user registration) + @classmethod + def post(cls): + try: + user = user_schema.load(request.get_json()) + except ValidationError as error: + return error.messages, 400 + + # 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): + @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 - 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 + @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 + user.delete_from_database() + return {"message": "User deleted."}, 200 class UserLogin(Resource): - @classmethod - def post(cls): - # get data from parser - try: - user_data = user_schema.load(request.get_json()) - except ValidationError as error: - return error.messages, 400 - - # 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 + @classmethod + def post(cls): + # get data from parser + try: + user_data = user_schema.load(request.get_json()) + except ValidationError as error: + return error.messages, 400 + + # 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 + # 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. + @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 \ No newline at end of file + return {"access_token": new_token}, 200 \ No newline at end of file diff --git a/schemas/user.py b/schemas/user.py index 820fe50..224e387 100644 --- a/schemas/user.py +++ b/schemas/user.py @@ -1,14 +1,11 @@ -from marshmallow import Schema, fields +from ma import ma +from models.user import UserModel -class UserSchema(Schema): - # 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: - load_only = ("password", ) # makes 'password' field load_only - dump_only = ("id", ) # makes 'id' field dump_only. - - id = fields.Int() - username = fields.Str(required=True) - password = fields.Str(required=True) - \ No newline at end of file +class UserSchema(ma.ModelSchema): + # 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_only = ("password",) # makes 'password' field load_only + dump_only = ("id",) # makes 'id' field dump_only. From 1005ec10b4dd12a05439b721f1ac03b21efcb4cf Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Fri, 25 Feb 2022 21:36:11 +0530 Subject: [PATCH 26/42] removed unnecessary directories --- templates/index.html | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 templates/index.html diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index fb11bf2..0000000 --- a/templates/index.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - -
- Hello, world! -
- - \ No newline at end of file From 8ac40063248d0cc22e49eff23c7a91c36837faf4 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Sat, 26 Feb 2022 11:45:42 +0530 Subject: [PATCH 27/42] added marshmallow to item and store --- app.py | 94 +++++++++++++++++++++++++++------------------- models/item.py | 29 ++++---------- models/store.py | 20 ++-------- resources/item.py | 55 +++++++++++++-------------- resources/store.py | 20 +++++++--- resources/user.py | 33 ++++------------ schemas/item.py | 17 +++++++++ schemas/store.py | 13 +++++++ 8 files changed, 146 insertions(+), 135 deletions(-) create mode 100644 schemas/item.py create mode 100644 schemas/store.py diff --git a/app.py b/app.py index 009bd7a..effbe0e 100644 --- a/app.py +++ b/app.py @@ -1,11 +1,18 @@ 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 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 @@ -14,11 +21,12 @@ 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["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.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" @@ -26,6 +34,11 @@ 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() @@ -61,55 +74,60 @@ def add_claims_to_jwt(identity): # JWT Configurations @jwt.expired_token_loader def expired_token_callback(): - return jsonify({ - "description": "The token has expired.", - "error": "token_expired" - }), 401 + 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 + 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 + 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 + 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') + 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) diff --git a/models/item.py b/models/item.py index 603f2c8..6b87894 100644 --- a/models/item.py +++ b/models/item.py @@ -1,41 +1,26 @@ -from typing import Dict, List, Union +from typing import List from database import db -ItemJSON = Dict[str, Union[int, str, float]] - -class ItemModel(db.Model): # tells SQLAlchemy that it is something that will be saved to database and will be retrieved from database +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), unique=True) - price = db.Column(db.Float(precision=2)) # precision: numbers after decimal point + 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")) + store_id = db.Column(db.Integer, db.ForeignKey("stores.id"), nullable=False) store = db.relationship("StoreModel") - def __init__(self, name: str, price: float, store_id: int): - self.name = name - self.price = price - self.store_id = store_id - - def json(self) -> ItemJSON: - return { - "id": self.id, - "store_id":self.store_id, - "name": self.name, - "price": self.price - } - # searches the database for items using name @classmethod - def find_item_by_name(cls, name: str) -> 'ItemModel': + 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 + return cls.query.filter_by(name=name).first() # SELECT name FROM __tablename__ WHERE name=name LIMIT 1 @classmethod def find_all(cls) -> List["ItemModel"]: diff --git a/models/store.py b/models/store.py index 5064b0a..deae614 100644 --- a/models/store.py +++ b/models/store.py @@ -1,37 +1,25 @@ -from typing import Dict, List, Union +from typing import List from database import db -from models.item import ItemJSON -StoreJSON = Dict[str, Union[int, str, List[ItemJSON]]] - -class StoreModel(db.Model): # tells SQLAlchemy that it is something that will be saved to database and will be retrieved from database +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), unique=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 - def __init__(self, name: str): - self.name = name - - def json(self) -> StoreJSON: - return { - "id": self.id, - "name": self.name, - "items": [item.json() for item in self.items.all()] - } # 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 + return cls.query.filter_by(name=name).first() # SELECT name FROM __tablename__ WHERE name=name LIMIT 1 @classmethod def find_all(cls) -> List["StoreModel"]: diff --git a/resources/item.py b/resources/item.py index 682871c..9d972a4 100644 --- a/resources/item.py +++ b/resources/item.py @@ -1,39 +1,34 @@ from typing import Dict -from flask_restful import Resource, reqparse -from flask_jwt_extended import jwt_required, get_jwt, get_jwt_identity +from flask import request +from flask_restful import Resource +from flask_jwt_extended import ( + jwt_required, + get_jwt, + get_jwt_identity, +) +from marshmallow import ValidationError 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): - parser = reqparse.RequestParser() - parser.add_argument( - "price", - type=float, - required=True, - help=BLANK_ERROR.format("price"), - ) - parser.add_argument( - "store_id", - type=int, - required=True, - help=BLANK_ERROR.format("store_id"), - ) - # 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.json(), 200 + return item_schema.dump(), 200 return {"message": ITEM_NOT_FOUND}, 404 @@ -45,16 +40,18 @@ def post(cls, name: str): if ItemModel.find_item_by_name(name): return {"messege": NAME_ALREADY_EXISTS.format(name)}, 400 - data = Item.parser.parse_args() - # data = request.get_json() # get_json(force=True) means, we don't need a content type header - item = ItemModel(name, **data) + 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.json(), 201 # 201 is for CREATED status + return item_schema.dump(item), 201 # 201 is for CREATED status # TO DELETE AN ITEM @classmethod @@ -76,21 +73,23 @@ def delete(cls, name: str): # TO ADD OR UPDATE AN ITEM @classmethod def put(cls, name: str): - data = Item.parser.parse_args() - # data = request.get_json() + item_json = request.get_json() + item = ItemModel.find_item_by_name(name) # if item is not available, add it if item is None: - item = ItemModel(name, **data) + item_json["name"] = name + item = item_schema.load(item_json) + # if item exists, update it else: - item.price = data["price"] - item.store_id = data["store_id"] + 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.json() + return item_schema.dump(item), 200 class ItemList(Resource): @@ -98,7 +97,7 @@ class ItemList(Resource): @jwt_required(optional=True) def get(cls): user_id = get_jwt_identity() - items = [item.json() for item in ItemModel.find_all()] + items = [item_list_schema.dump(ItemModel.find_all())] # if user id is given, then display full details if user_id: diff --git a/resources/store.py b/resources/store.py index 24ce513..15b7a15 100644 --- a/resources/store.py +++ b/resources/store.py @@ -1,28 +1,37 @@ +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): +class Store(Resource): @classmethod def get(cls, name: str): store = StoreModel.find_store_by_name(name) if store: - return store.json() + 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) + 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.json(), 201 + return store_schema.dump(store), 201 @classmethod def delete(cls, name: str): @@ -35,8 +44,7 @@ def delete(cls, name: str): class StoreList(Resource): - @classmethod def get(cls): # return {"item": list(map(lambda x: x.json(), ItemModel.query.all()))} - return {"stores": [store.json() for store in StoreModel.find_all()]} \ No newline at end of file + return {"stores": [store_list_schema.dump(StoreModel.find_all())]}, 200 diff --git a/resources/user.py b/resources/user.py index 95ce023..4134435 100644 --- a/resources/user.py +++ b/resources/user.py @@ -2,13 +2,7 @@ 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 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 @@ -38,10 +32,7 @@ class UserRegister(Resource): # calls to post a new user (new user registration) @classmethod def post(cls): - try: - user = user_schema.load(request.get_json()) - except ValidationError as error: - return error.messages, 400 + user = user_schema.load(request.get_json()) # First check if that user is present or not if UserModel.find_by_username(user["username"]): @@ -57,7 +48,6 @@ def post(cls): class User(Resource): - @classmethod def get(cls, user_id: int): @@ -78,14 +68,10 @@ def delete(cls, user_id: int): class UserLogin(Resource): - @classmethod def post(cls): # get data from parser - try: - user_data = user_schema.load(request.get_json()) - except ValidationError as error: - return error.messages, 400 + user_data = user_schema.load(request.get_json()) # find user in database user = UserModel.find_by_username(user_data.username) @@ -99,12 +85,9 @@ def post(cls): refresh_token = create_refresh_token(identity=user.id) print("user logged in") - return { - "access_token": access_token, - "refresh_token": refresh_token - }, 200 + return {"access_token": access_token, "refresh_token": refresh_token}, 200 - return {"message": "Invalid credentials."}, 401 # Unauthorized + return {"message": "Invalid credentials."}, 401 # Unauthorized class UserLogout(Resource): @@ -112,7 +95,7 @@ class UserLogout(Resource): @classmethod @jwt_required() def post(cls): - jti = get_jwt()["jti"] # jti is JWT ID, unique identifier for a JWT + jti = get_jwt()["jti"] # jti is JWT ID, unique identifier for a JWT BLACKLIST.add(jti) return {"message": "Successfully logged out."}, 200 @@ -122,6 +105,6 @@ class TokenRefresh(Resource): @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. + 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 \ No newline at end of file + return {"access_token": new_token}, 200 diff --git a/schemas/item.py b/schemas/item.py new file mode 100644 index 0000000..0f80388 --- /dev/null +++ b/schemas/item.py @@ -0,0 +1,17 @@ +from xml.etree.ElementInclude import include +from ma import ma +from models.item import ItemModel +from models.store import StoreModel + + +class ItemSchema(ma.ModelSchema): + # 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_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..575aaca --- /dev/null +++ b/schemas/store.py @@ -0,0 +1,13 @@ +from ma import ma +from models.store import StoreModel +from models.item import ItemModel +from schemas.item import ItemSchema + + +class StoreSchema(ma.ModelSchema): + items = ma.Nested(ItemSchema, many=True) + + class Meta: + model = StoreModel + dump_only = ("id",) # makes 'id' field dump_only. + include_fk = True From c430faa6591dc0892795462ae4adc34d64c7e04a Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Sat, 26 Feb 2022 14:03:40 +0530 Subject: [PATCH 28/42] changed ModelSchema to SQLAlchemyAutoSchema --- __pycache__/database.cpython-39.pyc | Bin 191 -> 228 bytes database/data.db | Bin 16384 -> 16384 bytes models/__pycache__/item.cpython-39.pyc | Bin 1922 -> 1503 bytes models/__pycache__/store.cpython-39.pyc | Bin 2003 -> 1410 bytes models/__pycache__/user.cpython-39.pyc | Bin 1588 -> 1492 bytes resources/__pycache__/item.cpython-39.pyc | Bin 2900 -> 2805 bytes resources/__pycache__/store.cpython-39.pyc | Bin 1626 -> 1715 bytes resources/__pycache__/user.cpython-39.pyc | Bin 3278 -> 3080 bytes resources/item.py | 3 +-- resources/user.py | 10 ++++++++-- schemas/item.py | 7 +++---- schemas/store.py | 3 ++- schemas/user.py | 5 ++++- 13 files changed, 18 insertions(+), 10 deletions(-) diff --git a/__pycache__/database.cpython-39.pyc b/__pycache__/database.cpython-39.pyc index ff40e8eff1b473f057917ac8e8f60f851f525992..eeefcfc8a0d100e2769cd027a0cc5eb100411bf7 100644 GIT binary patch delta 104 zcmdnb_=J%+k(ZZ?0SFE}lSs0g$m`E|YhtXd7b{St2t@qy($7sz$xPHQE=|fvOi4{G z(05HKO-?My%+FIu%u7)S&QB{TPb^B+4=BpdN=+^))(@yG$;i(Oat#hiEXdTKI9~w( DW$hv? delta 67 zcmaFDxSx?Xk(ZZ?0SL;TzfZK8$m`D-H8ECJl?5nL1R{Q^rdY)U6y;~7CYKb)1XPw} SIVG-dz* delta 148 zcmZo@U~Fh$oFL68K2gS*QG8><5`GRQ{wEClpZT9`7F4*#uf@Q?%f!MUE?S(MS(0IB zWNgCC$ig5jnv|ae;_&i+VPNEc&cOei|2bIw!~oXG&-Hob7#J9MfEWyTfg~&cEC&9! r{5ScJ@o(UtwOLT0gMacY`(R-~Mpgzr!Uk}~aiBtms>G>ub diff --git a/models/__pycache__/item.cpython-39.pyc b/models/__pycache__/item.cpython-39.pyc index 6050267c09de74188b7724e220e4a8a0218aa262..d73f2ccc83ad0b94468d05c68a56f84566f83105 100644 GIT binary patch literal 1503 zcmb_b&2Aev5GJ`lUdfVU%d!(EXd3jUi}eCUQ6PwmAdO*EIqhY!+M(=K_eUYA7BYN` zuR>oS2kGd0A0W@cYfpZKoHE0Wf8gX;0_4sNzuEa_IFoj}ZH8<4>0ta%z}SDJ++RK_ zzv9-1cmM-lu#(4|lPn5Rda=jZZwx%}FB$mKoBAKTIDlZp2H`dOumQLJ@3}Vk6)?`o zbbn#cCcwWaNEUNou>c->(w_#nLlEFCMZD4Is|frxhXAeN(u-SPQEk|OXi4@KVmEJm zrN8Px+u>V??xMa0n`<6FlG}0zQ|NpY(1oo_{{xSEXxjyLz~IqG4%@KftbOP?eHZ$d ze!TZp+k<`7_MwMqKDMpbMwV}DkcAC$6!kkyc5|lkGF6M@Pr0~0!Q_qx!ba7+DAI8u zZ8)#;_j7qI@CMPXnUYzq^ST;v6`}3CrA@7*KFlF*k4SU@@E?AamgujfnX4)z|C>d! zhx;XN-NWO8P1%%RaNtY65^IKR#W4ox;N8Hp0TNIP+nVHskt!K4Y%s|cK9#UuR%i#i zVv7Fs`R%Zjkf%dEAD^Wlcx@r$&A7o&P&{!Wz~o~U{%Goyzm3v*Uif4myK zOK178r1Iz?mFIA_usf5yf`l{-n@~o(MA~KJpFg`T=;1dwq~Aqo#__mWB$KO4oEn-E zUvWzKiXRVz+CrQ4(xO1`9h6;8%|UT(WyI7TUJralaJ!+Url&?m05;6(vdqmD&WV=A+itU@6hwJB%#vM%OjWj9_|MxIJ#MUGV+ z8I@P3u7_=KRMe@l0ghxQZTkqPDL<{=$c2sElqKlUQL=FDtk3e94V>L>W<{!XDY28m zdfF(}r^Kn()MGk+pyNk6?keIWYAZ#r6ynxY#Yp(xHs9y_;t6kig7*T!`~H^iP{)JN lhGsD%%Jg>|-8^@bW8kLWLBAKtMn5+N`-Miq6}=-m-WR6&Z~*`S literal 1922 zcmb_cOK%%D5GJ_~JuKO>EI(uiX1Qb?*K2AkZAWe$2i5pbOWiN~M4rQ;pyHdzi z3K_n|R|EY4K3GTpmHq@?d-Ao%qKD3K{ZL~c%LQjS!y)<2$KgcHW}RVs``7yZ?>=LH zlCs#)#=gU@pQ8W)^0*^Dvr7s;>L0KI-T{aY57Sw)V;DCF= zz?I&p{>}-Oz#Fihe~CM?o@3nalejSM^F$ZMdz~layvL1z{g~z#+YOdK&?;HLfdvA1 z;7E7mVfR6Rb1H&Lsh?NDopZE#Xsb#0oP6rwL*YUF-(8TdF)Npo1 zw&bd8L;cKwC1{+u!J6zq^NhnXtf00oH)IQAZ=DHPh4zX2jt84)+XB{Q)^80u*4l-2 zt8c(2uG_xSwxEmJ4y+>++otxcklBkd$kccV4)rUfU}>U~EK;-ZS2??Ui6NuSpXSM% zsk{`WTAL^tCmJa`s_9~^q~1*+s1Impx4S@k^C*+X!_8u8s@5w^Aa)m+r7`XJ7`xs; zam+5KGZP)wm-)&bM%+?K=`Kx8FZU%eUyrr6sK>V8zyNXRk%Mh-Y`+R=n3P*sV+|;sun=5 z4wD>0A{6~Y%7+ipL0cH^(atT>JAQX)yh#%Zm!O_=f~Zze#Ta++XPhWa5%5DCzfV`y zo`Cb8;$#UxEMDaG^xmC=Z#de_<_kf-gB|sROdKltUo2E2wgyEZMlwQp=!w^|WWvP~2`j5xy zG|NrpSzgEksT3u>@dt%U@&lW7#(SEMqrw&hQ-6vjC^^V~k~907Ohd`k)&PB&OpIsi zka2XOFn?*8agw}H>$GW=g7|Up^PZx+HcN3DX`M-|5uiRmi=tdml)~y05|m2|iDr|Z z9;c#WQNgYspr{JhY4Hwkiw>`gwzpgpyzL3zahF}|xz}qLznD!Z9!m4_7|H=ty-o|u nh5CYKzasG^iLc4Uv-Q?)bRQN|{iY=QTPjAjjCDjqY>0mWADOxn diff --git a/models/__pycache__/store.cpython-39.pyc b/models/__pycache__/store.cpython-39.pyc index 2b6321c99d4d826603df8ce1c9705391fe2cbd5a..878dd17598371bef84a141386179d786132bc60e 100644 GIT binary patch delta 671 zcmYjPO=}cE5S{Mnnf;vI&3>81s0m2OfaIY506|a$jfb2Z56jF}Z!)20CUo~60)k-n zAi^G`ues<&@-IAl&8=_ZLHrk1Z$whiZ~DEisyAKqRrf>deHukg4`cLwl6?(4@0TMt zgCTi>OYI|o2Sj)UNeFR!Drn&+KJlJ=-~(HFz_@?H&iy2S;LwZ1KUm|%#Il#U()gv2 z&7AZ&X&|b20l5|k(f;aIX zx**Vj?pp0ZZ_PKLzhueAKQ@3NvOe_iuSVt_iKAyRwe@l#M4Ab1zLEPq6j+%S+=i0t zg)Gfa{}GFcjLewrlE_5#?XW-3OPDHMNj}ZarY^m2fW)<`iitP<9$w5qI7pe7A zA@Hbgey}aFX@0Xe@fh`(N#7mU6jv&*O3VYW;jAi(Twmh66c=+d4|d2M^D)To-Kdch zR~P~pTyuFFiQF=caF6Vmhv7aMm^6GmTQ?7%R$@_eyD>Nr2OL9ThBY-=V`r3$W(E3m%OQjVMnb+GB+u7hp!rFQGJbgv`CrG^M~%KRP~ x(+;DgA5b!69k$Lmh(jCdvr}j6`q(<8I?YnWug4I)s_;INVb9&VYuTo4|1V%fof-fD literal 2003 zcmbVNOK%%D5GJ|Y)mye@*@@GpF_1Qh(8B7Wy%|P;Bq&m}u2ChIMT^B+r0iAqA(N{X z65Nx0&7pT6WS|%SiT)5?d&)Vt0tGt5wPN{kDuLn742R^*H#6L@(Wnv_Ywy?g|MCd= z8->MD0^u2u{s|^Q1dT~TeVQ6G5V4W#23{vzt^Gk(1D+_*3LFa<50=la3L`jrE zs>!;joKaB~wNvglWK+~ZYKSICOL7@jiC8*gVp*)5@)PQ>fUYIT2C;T6v1+xeqHX0h zu@3v%m(o3PAEY(WhIltjwU=i~exC^$8&4yzUqg_!k&2Q~O@d$KG13GV`4d?1hX97toPkLfW9AZa~vL3h;>XgbVrE#rY& z2H}FJRrvg~+fAg1!mb|gABIB8QTL@7_rn~Lu^FafvzHC>-$Et3dny~sey+QFll(AC ze|*_{9gd=If5cY$mi zo4_2CArcjk#8c$h%sD47$O#qBnFH|h-D?Nnl~^M^Al-)P05<(x|D7Jw8966^up{0p zVn;lO8j6k?w)Lgq5g=|sDJI-&s@pMCXFp3uI~yRs2O`tQ3*q1aTibEg4`aOp%-kBv zn?=GxZCpHAcewf(c3YKt2%K@kIKH3?k-BlOx(B*7AYF!uFitsb{X1Q{adC$gr>%p{ zt!i*UX;H-U5yc@uAn02DaOkf0aR=1tQGD{ZPxNV5G4;39ZxVM++$3w7iAU|L{~jfs2d>9V#KQ zc=w%NwhMz`pt9tHr(b~em8XvuJiW`hvyPpQRS*QG8U*kxjAP{ML2xt<QjsvkyD@H)WPXMM%%o} zt<`R)VZ3}YLJzTaiz_g%raVt|5nZi#Ah+z}0{>m2zCtS}N^|u!a;|;k?WuP;Wy;$H RmOEIj_Bm*?I<2z@>_7ch${+v$ diff --git a/models/__pycache__/user.cpython-39.pyc b/models/__pycache__/user.cpython-39.pyc index 2c7d39a2afca9b91c378d827ea492ea881057fcc..c7f78dbc0cda6f72986d89916d78f74f3eee425a 100644 GIT binary patch delta 838 zcmZ8eOK;Oa5cWRoII*2NX(?@^Qh_QU2+ENIkP8wBB+4V=;)~_5laR7@!rBH!9C~Q3 zMT&evDiRzkBu@MYE}Y=Bf*;U709SSm;kD9!JM-<%%(pY|-Or8}d0x#>3%;-PKFz%K z!F}a0gCTCjC_qTtnB$lPB&Go^eZtMy3M^y*feKKtq0+~zhTcK-hQTN^rv@{@+Ntf4 zz+qO`XjhJOl{S)Ao(!MlFnfLxy_mf{$#+bkuWd%(AZ9YmtbGzxf&Rl*_Hj@H2VAAa zKESNXYE!z00#9*u#W{*|nOAZPtj-n=5c64MN(29w*JO*zYZM*Sx+I;k7{-y9gpXlz zbXTdm-p0~Sa?Yb32Z2kSqN)$Y=P8IJilMlNJ9XWvLn2M}0Wp!qE4*r#lpz|$N7QZl z{b9nw-XxqytrcJJ7IF%QTs`lW8HKM7v818#8=A_JwR!N6ohUN0slr~8%aud4-KN4U zzLM5iNmJnc{{#IrdJ17S3R#pzy(ooK#RiSGj;o|PQde2g1uO;}G7xK;X=(UNLGg~R zqSM6(dI2?yZ*+54dSR&2anQdH!!nPwu8nveC$e%g$zT9N94ixdGcimCLia-2<0ws^ zjRcdwxxM{q8=)PqmX-=EvzCCvxOl z_L>{V9M(ra2rtoBPI(1RRE^`^#pHx&tIJi@H9cQ_RW(V!-(k4E{^R!SXP>b@$+)=! zG`_`czefQKc+Ltgc_F0WWQ*J@eCczhxem0@p$_Kab4FXXeqi8%f5E`l{(1DqlWhp5 zEDqn}T`U&P|1L{w7d$Pqs*Jf4FiYv==DK?S@95P`a$r&bmmc02;0{56_g2W3_Ak-C zjKJUU4VUc$yQ90h3&A@9A+#=h+0%Wr8ARx5L+8SO!{q>dN9gOKuLu3cH-rHkz2h*1 zTlmzi57r2d(Hg-3vl+YgQ>)G63Un@mC93vG%w>0BvLZ1n^_yP3KSS_a;UYRMlR~>_ zky!hxGLU*VvL#*Meu&!+QLOozDYh0UG4Uze@>A17f9yHm>U?e@G)x=CmYE*@{q=ZK zXvmU@UCy2-p!H(%2$pG5Bf}?22`AHPUcX9=o}8KLT&K02oUQ8TRr%|q>62uUO$tiG zP6+7z#mYrWWo1??MQAKG2xC0tuSZuI-`@wsq5cfn)qLpO82}*V(@|gD>%Tk8AvI zTp17%4H}M-`$hT{g?G@Xk-$rAj1A{I-P*l=tD?y2 zEjL{}JOnwk$xE&3N$dEaZA;Hf+l9VU;h7 z(zSjpYyC_c^J#;5TAQqVc0G*Fv$Zh;I_su6PI{qn#=wlp<7H)SK10n=Cz!h=u5)ZO zYG^}WKALq)?}&u&4LSbAFcN&^(H{6s%xTrU!rt)K;ZEtp|$Fw5$}9BTygtQjou);Q-&P?GC4#fE zw!>8jmU8Y$2@}S1m8d1&m?GMK^4 zd)7YPr$G%zn8ob-l-taCL|KiwM`P}EmbTYfHiYXR~zrG-aq8>N*; z8802d)kx&A0SB2rCMeiGg(lB|P~?D4=pnspObl^a83*RXIHZgYj6SeRSqWt(>TRp{ z)iNwC@wminP>aqOV|N{gVF<%% z4`ABvW2(>d3}~2m4m1tgbx@|DiPMZ~$N)e9%0Qr$bzmP7fRj!j%omAr4r&PLU32VB z%meq3Q1TVYPf>V7+EG=9nbyE?$Ty@<&ZB>0VnPUP4ZG8;5a&Dm$q#_IK3(z$Xit8D zleC*U0AddGaBH#7m%+ z*07N33}_Ok5UW8K441k_!!)Qz9q5g9Qg_hrKUm{YHP&mOD6)@e8jLOz`7)+Xg%==A z4*+FEc4CSPfUSwxG8;(?T+G6%&1B`MHZ`qNsJ zJV2OV0Vi=9MB5P<`x7uPRW%?Sh$1`2%+usv>uz+nx@%$)XPyA8bmBBhGWlGzoNB=f zFh^n_iRl1Zo`Ea{)XZ^H^R)8%f3&`anaE6J-;|Yz4I?8WI+~9Z_z3*0_?RyNs>U*z zn4yDQ)mm-JQShs49s)NZ{q0_rr#2uK^U{^75FCoMn1Nl}4NWiMgMJK@=9qX5ZSSCX z7scx+kWS(a6#wFlIUK2VuuJqH1`-AUpdJtiwWEXur(U~Rx}mN#VOTc8Fe}(7<)C|E zcykn`Sc9O~1lXm_I8+BFur!yZR6<}$6sJ(UiQ;V(@1Zz8kJgaPq6q?f5`_y4w&;>; zz_At6r_?hSFg^hpu4V9E3+n{A1d2`&ipikN1M83+(jBbkRJ_HY1Wn6Qwb{g2gAEzp9uWFzYS@^} zQb+Qctq>*Lst-*k@(!7hZ@s&4VKA~x9vC;RJ`pd&sKyO09;0*qxeRYyOyN((MK-*2 z1KfXyM#es}8viU-;LgRgh@(_q0%httKDANoK$5D$zm@RYeg>5Du}F2=hWHS-#Z-nn zs0OccPM0K{99FkZMKgUrx%z&J(zI>rB39N$aKR&e^)km%*8xXjgwQG$nhu>aE#RO> zjlV6^_^bL7m>#?b1)>AaVg0XkoI`;ZhWH2t=7T=$^RlIMFIK5|2}vulf_hN$ TJ-Xz8G%QG%>gPa4&wc(60+o2` literal 2900 zcma)8&2JmW6`z^?;EEz8N>*LVaNBL#)MkrP?x9GEI<8fVHPpzoK`LmxK(JkLhSJLW zLC*|ri=j^P)kO{T=mQ1h;%onk=3m%r(Sy$IsYrsnH!E6l>_e8A$2V`@%+CDY@4eZm zUat~({`u#NJO8N>@^9>%e{AU7f~GVGPB=|SN&`w!X9-KKz)J1FHhn8`Qa5nZN>E9? zz%w>GnM>ig#;Fp~auP6MP3>b6IKtU?ITaPogg2ez^q6w%IiPOrwy2)k?br5sQ9B}{E*7}+ zl5v+;p4mtAhz5%SMmXc%b1Le5?j+vBT?Fyz+T!G_#)bZW!`|uEEP7M zsBnfd?u%^*f~^mRWmw8E*n1)w7M{|%6c7Ojo}ERhC|oJ@NM?NoN1Z(uDA;a8Q@;SA z$&^muq=#(6nt>ZIQ^R{R1$l|KR}`C|}GS7NZw!Mjpck8mLK^bD+;A@fFitG(UckUzkg z=aF*zNff8*omjc$f;V7}YJeb?%RJyCveKiTHJ&%TEWO@B3+ZU6*abzCeL%&~4XV}* z9Zp6>wlom)JOQ(n64i+nI-s#tAY16pND2SmF;K8+btGO-asfnh&hSURhlZb_!2B_+ zJ9CmhM;mfQVhQ;!GpylA-vxVlR3Jsw^5`<8ApW1GAh>jz7YZ*lxNDlvo<&Wfa#?{2E0I#TnU+hKx7BAV%ss2oC~iQ)YhNSFU?b z_ZW<?p? z26tLnmb}gzFF@3A=RIf&X|PKsI2 zJRRN?u+o2_k+F~IfQQ6V-MW$F{U}j4L7BN04%T@km%#h`ZjF=G+19SWv@*oMf32Ox z`6IBUXukL`5P1;pg=~K zJU(0f>_ESR(Z51dSm!*TtVboA@(GNTM?RmdgSB z`V&vJ$hau8sH~iL^k5#+S0t*1fhmi|Z*e--N$I0_AH}aw;0_k!66v0i$d|5gh4+j`-7kpf z2>+Z2U-B~^^}z0m9@sr=_eFn3M!^-FL`JmfDEXw8+JNdmmb$7{DnakhOr_*3UC1H< zvuDk(Dk1Y@Fr6YPS1InW-*#Sn2BVThRFH^CE*(LCqk;*3?wrxc-HkZHwIkkc#1kDm z0{4eIb{p3odkF`YNg-3VcNzyB`{&U02Otc&pc}fSKd}u{_YJ$?8@8o_&e;qg3Z7Bn zpgwY^SAW9%rc+3*lT*1K+Ec=^GU2?cOEJ!VrNG8fE>1g~XQhZW;)qXHFyt$yq+CuOiaJe9R+V8=if~rV&C^85 z$s<*LCsU&*k5*<;m5(3Jj+13JQP*M76T9trxoY^SGy&|beGr88s6!9fx;w*Lj^W>P zs37ZFRL7v)pi-|0hzo$>2*!{NAbJJ>*TIHeuq~nF8&ck-@P&+{!iAaMoO$G1G9w=m z!!I0*xMQ*UQ`LvP*GKjeVUnvvtU~!?ri~tFzW`B%mtBAOX&FkTDiu~~S}Q5Sr;99? zVX7n`DNjSQkYVd+_7AMO`U_T#TpJ99J~u<1{2*gf#Ue4uNus41z_2<%aR&ubZ^g@? zHO{iq<_I)B1c9^y)^ivQs7DXky3Oq|PI?a8F2mk~g*Vu2hP`=%s6_}l1{gC6`Q==L zIIvEHP%_d;F1{k?~0ATPkjiSg+Fd5 zOh$2Mc*xaq}lQHeU2pa*gMq)u|oWw82bo@iZ2$T`t)ZInV->Qxl?P~7sg8bVba z5EyTbA@M1r!w&R2jM_gb4F0bn$Oq8%T@bf#1HwBe@G3ZhBf6$d;V&U+Qi%Pop^nvP zs~xux6r+Jvz9n0FN+C6wO+!9yJPTSYWCL5a+~XYfjN6KBQ^i)dZ>u}W^AS~;9eci7 zH<82yy0&i*Uk@x`ydRd?6!P*lT=hDN`zYQ(Vfnp?+FS5<3|-?}iPRL1Oh-ZE&LIfj zD?FwdSUrZiH}g7I$SxYRSJz|foWk`@00EWx%@F$2R>Iq;VSw!JdoJz5*Z3c|$?yr1 R)mSRiXOfA-qh4f2i}kOX5Qc5yf1|;C`N@sp1|2{mV0Y_UfiVh z`Mr(Z$3!@SpAf+%ojDy2wktfaJ!`un*CwI=4I@LE`Ol?J4pm=*o^Pj#lFdZOxIbTA zpZ|PHL-T-E_bcF=l2baR3wp$+OqHkXlr2b`2#}DrLPt5^lU*B(CZek`O}F>HJBh|J zf!+-U^D@|06EWZU$a`N+2D%zS2*yL?22J76B! zhhjSfN;?afX276yu9fA+QtN0a&3Cq80_T2{+g=oA%t|_XC%b9V6;T@XA}v)8u9c6l zasdIl@`FUDsOb^__7X6knNQA3S~S0$(8CFpXR5rid0D&%w8>Of%pUJF3h29va2?@) zDXU=>#Q{(>T0Ud6u_LK2J zBLw?52>DR8tMUXMYD1aU>T%MKV%-4c!k3+|gl|_Oieo5s1wxnk|D{*R)E)EH-8#lb z)lCGPSgj#g9xr2W9bRvPXq*$-Q+U$~y{s^ZBmJhUq)rEiu|h67!5EiX*DP1(>w$4L cX4%j-*cZQ(`E>|6jAa9Pwc8oMK9p|#1+I{_E&u=k diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc index 0dddd94e4fe09536052a2243e59bb73a4f4e2f21..d9c38d2fb01af3d7224075221d116788b51fa2a6 100644 GIT binary patch delta 1172 zcmZ`&&rcIU6yBNHZMQ!uP-qb;)gn;QN;DYJn;u9A_M%+Wq$$i6me3!*T|yvjNaUag zNlYeQj4|zzc=E3C>dAQCgYn=$;8kPdd)tJHLYwzx-h1#!Dc^|sMu8-WgkNy#L)nG_X%PiwMvu%j_e z>e9eifMWSzQN87JhW&zRn)=4`%v52KOGofjFHx~;vb3^O+Hx$Ym8+I#szouH;GS6B zbUdBHhZ>~SQ}M8Wikk5o<#7?^QA7O_izh3T=6Vz7$e9Kj3ao$uDLKxZl0jmp}+E9n8m~m zG{;0R8D|NTGsvpPdQyF2)9Pm+wP#@JNXd0{PE)eQn;agw!6x4@QCoALybyX}Quu(k zgd6caall%-82sJ}MRSM~lXIpsnDeiH72eNWqFSE;e_T0)C_r0rbcfZg$XN0OR%&W9k}J@3{l!`D zz4Mr;qIIvt_nHjm)rZI=^VIjq^MbD@eSwfAAbNn>1S*719$){HGG(KNF7K An*aa+ delta 1373 zcmb_bOKTHR6uys{$xL3RwXbN6vC(Q!(^joibRj5+qERWLR-sJ2V@Yk3dS+^|BrVd7 z3l;IYbdiDxx)Zm8KR|FTvk(t9*Cw~VJp1BFSBaNV1aS>IA;yP*+N%cOqq(`6qlLB zt}=zzMKQYmA7q0pu$r+tS0an~a9v4dR5-7usVOvAMu8)%ckA8!Z=f=vM?k@ zAw|3VQ zfji;)UeO5UjcGr0=#t19njSz6LW3+X8T9Htc`e&duWmAqXF@r|85LIv|p5=J2eQ!UeZE s9Mj+8{d`R45)Vod6=<`8HUg-bK;;7^{&A!JnAq*eOjo8elgl_i0nw%dhyVZp diff --git a/resources/item.py b/resources/item.py index 9d972a4..82306b3 100644 --- a/resources/item.py +++ b/resources/item.py @@ -6,7 +6,6 @@ get_jwt, get_jwt_identity, ) -from marshmallow import ValidationError from models.item import ItemModel from schemas.item import ItemSchema @@ -28,7 +27,7 @@ class Item(Resource): def get(cls, name: str) -> Dict: item = ItemModel.find_item_by_name(name) if item: - return item_schema.dump(), 200 + return item_schema.dump(item), 200 return {"message": ITEM_NOT_FOUND}, 404 diff --git a/resources/user.py b/resources/user.py index 4134435..75ff8fe 100644 --- a/resources/user.py +++ b/resources/user.py @@ -2,7 +2,13 @@ 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 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 @@ -35,7 +41,7 @@ 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 UserModel.find_by_username(user.username): # if exists, then don't add return {"message": "An user with that username already exists."}, 400 diff --git a/schemas/item.py b/schemas/item.py index 0f80388..8df7cc0 100644 --- a/schemas/item.py +++ b/schemas/item.py @@ -4,14 +4,13 @@ from models.store import StoreModel -class ItemSchema(ma.ModelSchema): +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_only = ( - "store", - ) # makes 'store' field load_only. store_id will be displayed + 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 index 575aaca..a6a7444 100644 --- a/schemas/store.py +++ b/schemas/store.py @@ -4,10 +4,11 @@ from schemas.item import ItemSchema -class StoreSchema(ma.ModelSchema): +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 index 224e387..0ff2e2f 100644 --- a/schemas/user.py +++ b/schemas/user.py @@ -1,11 +1,14 @@ from ma import ma from models.user import UserModel -class UserSchema(ma.ModelSchema): + +# 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. From e9c4b971ed3edbd5b43f2bda68c1bc6193c3bf8f Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Sat, 26 Feb 2022 14:15:11 +0530 Subject: [PATCH 29/42] modified gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 120273b..643b9c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ test/test.py test/test.db __pycache__ -.vscode \ No newline at end of file +.vscode +.idea \ No newline at end of file From d6bc3b0652667ad1b3f27bbe7716ab229ed9f06b Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Mon, 28 Feb 2022 11:51:20 +0530 Subject: [PATCH 30/42] Added user confirmation --- README.md | 7 +++++- app.py | 2 ++ database/data.db | Bin 16384 -> 28672 bytes models/__pycache__/item.cpython-39.pyc | Bin 1503 -> 1503 bytes models/__pycache__/user.cpython-39.pyc | Bin 1492 -> 1579 bytes models/user.py | 6 +++-- resources/__pycache__/user.cpython-39.pyc | Bin 3080 -> 3546 bytes resources/user.py | 29 +++++++++++++++++----- schemas/user.py | 5 +++- 9 files changed, 39 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3661f3e..7eca575 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # REST API application made with flask. + Contains <> branches Final and stable application is in the master branch @@ -19,4 +20,8 @@ Run the following command to start the application: ``` python app.py -``` \ No newline at end of file +``` + +## IMPORTANT + +Make sure to delete the `data.db` file from `database` directory before fresh run. diff --git a/app.py b/app.py index effbe0e..dde98a9 100644 --- a/app.py +++ b/app.py @@ -5,6 +5,7 @@ from ma import ma from resources.user import ( + UserConfirm, UserRegister, User, UserLogin, @@ -124,6 +125,7 @@ def revoked_token_callback(self, callback): api.add_resource(ItemList, "/items") api.add_resource(StoreList, "/stores") api.add_resource(UserRegister, "/register") +api.add_resource(UserConfirm, "/activate/") api.add_resource(User, "/user/") api.add_resource(UserLogin, "/login") api.add_resource(UserLogout, "/logout") diff --git a/database/data.db b/database/data.db index a7101859cb15485c9e8767480edc9c0296e25e6d..b242f12f2fd696cc828fbe233923cb387e1a0434 100644 GIT binary patch delta 623 zcmZo@U~G86I6<0~je&uIeWHTBBpZXCe=RTn4+b_~D+YcJ{zu0e3g$%*_T0wA5Cex8A$t_m7?iMgqhuksnYYqGJ4+ln(X zWag!$RumWJ0IiHqEG@|g^W(t|h&KdN?2YWq?BbG=jE$LK7lJJ-hL9|imHF*BQLOVq zw@#BwQ=5fN++GrwWe}s{4IwmZqZ1=IK$?;#H}VJC!wm-qKw@!md45rfLYQNavxj4l zhJ}G9ih{)ClFYKilGGFhCx3q*S4Tfk2!tU!skAt?2owrHXK6Cw3j>hC;&?+a#SDx~ z4j^XW1!5-tM-2R*_#bT+RJg{k#mB_TATG*LoSRvaVQ6G*!o$eQAS}w6l%E9Rae>vp rXW;+I{~oM(x? delta 491 zcmZp8z}V2hI6<0KmVtqRWuk(;uq=a~e=RSN$IR!!z+cYK!RN7AP~aM`RHH33ySTVG zV^d^FVp2|OW=U#pF_>VP9Kvg>tfRoiSx}UjoT}jF3;8uby-+6T7&iBx9o~*d(yBVhG7RIhW5)0i-z~$kW#`C{n@OHBvz%Gewh2 zvyqJv?10AX$wK^rlXLhbRIn=qyPp3V1OI3KC;Zodu0O$V#>33QAS#-apOj>1WNgCA z#KIshTAZ6%k^$m!GqNxUgCxOnK)*BbKWE^7&i`ezpu%l_U2Z032HD(7unUd2n3)-* zp&VZRw+w9jc?|q*{5ScJ@o(Ut#h(YXE{eZijE$8+o}Du}F*iBCC?&PX!QgMf;_F~~J&F^P?Y1{5hwV>PBTlhr0_TahcU9x0%KzEVJEbZQ^rzyV`l z>C#_P{WB^JV3TkW=j(TO(8u-n-RESgF*3CTi+)f8n>oxCJ!{7*m|(5Q5en)e=ZTyx zat^pvZUgGz9V6I)#)<@uKT#8W5jCMMI<|_Z=(a;d*Dm6gcyYB@qdjC7KhZU6OE(H{ z7LE$2Q*gD9oZ^#uujfjF${UifDWQ1^zSx!KB$~l+v>ZloxiImzpxwj|kWswBSGLa7 zdRchDhIu-KFb_vz#cC=_Gv1MzixSQV`6mU#2~U$#vFpVP zvV;8MJ-H>?e)WCANq$(P8 z-O^@B7{&d08b{+L$AVE3Hd`cI-cYIM%egeIiGb@eL;ud~WKKKspf4bx&oah$_n!<( lZ7&sbsXA2tdjGks?uy<`Qdq?7J`%)CTD6glJk?fx{2S#*s8;|0 delta 650 zcmZutJ#W-N5cTY?eLmag_%4@_3tu8ak(@vpKBP#1gi@d&k%DxlIQf`OICQq7wGR|g zP~;jKq|>$`J>myY@e`<^K(3(V7tpeEdrw5n!Q(d}L=%;(#T&Y)Bzp+1O(}iy~>>u~48rvCx*zWABJ3K03Qr-1th4 z)00hjIsJ5Z+n0eca*r7{(VK`PNLtdDq2YYP1=yN#a~R^>F<^x4iAbWKRR`xy)yB}+ zcJ(v3<}za)gKFw`a1T7~vsIS{cx|b+-ed^N`h;zOuivwqmjco<6`7Cf610Cq$7Hp) zH_Wj=IP4GcoW9O4nrjdEZLsw#zH;FoZB(XDWWOr-%+rli#wQlaCgUaY zbWo0DCDk%9rwRTt(uL^3s(v9ZLr1?ASD(|p+Sw{b<1DWm+j%ATrBVwscDGW)d|%OY zwR@13<anT diff --git a/models/user.py b/models/user.py index 100f098..427428b 100644 --- a/models/user.py +++ b/models/user.py @@ -1,3 +1,4 @@ +from email.policy import default from enum import unique from typing import Dict, Union @@ -8,12 +9,13 @@ class UserModel(db.Model): - __tablename__ = "users" # will be used to tell sqlalchemy the table name for users + __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) + activated = db.Column(db.Boolean, default=False) # def __init__(self, username: str, password: str): # self.username = username @@ -37,4 +39,4 @@ def save_to_database(self) -> None: def delete_from_database(self) -> None: db.session.delete(self) - db.session.commit() \ No newline at end of file + db.session.commit() diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc index d9c38d2fb01af3d7224075221d116788b51fa2a6..5d2448aa048e65be33ff1891a9f07579c1b5a55a 100644 GIT binary patch delta 1370 zcmZux%Wl&^6!py5iJc~)f>IzY;8I@ZkqU$W@!GHe2?#>Kf-EQ|GffSxU9KlQLaGXt z1q)b!5i1sDg9JM^@C}G9Z2Sv|A3#FlUMCP9v2>5G?>u~c&KW;ipN^INQmJ6zb^r4C z^)JIu%i}D4y8GkkDMPN2BM%KZqQtVOSLE7@hBx}|yTw`hOiZv1$rkZ`?HDeE4Sz!= z`6d?Fo8+qbyt0mlCJEO6JE>^h-M*EI+Ys8N%drAisyU^VtipDzc>;!Ld`I%ustkLS z_TN-VK3h|@ja1x3u$jO`NJS88*hP=K3HnR2tU|R3gu@uIjbJm*hHcJZBDs*ewiIGX z8!Jrn74gb=#TJ=1SF8@Zk;9yKI9%8H4!>bz#a_lI%pIuf0^Wxtll%!^6uOu_i6?B) z?1+vj&6iwSGyI|~054I#lxtwM%b!Y6ntVLw5i#Ed^u{TxQT8o>}{Q8ZWc z951t?D07>)%OvL6s2POSg(%iF7{_Tl|JbE3#aRYnQrKTt)l3zrEBwto8i7YBte)UGX!Q z&P1(HX2;tzdq>$=!tbRPV3gqBLrpb0y7w{SB7(sS!elx8`XX;wrNe9`eTWfzQCq#|v^B0ds_+94aVNFNykww*$s z4>;V9%mLrFkg@hCN@HY8Ciy-eiYSvrVxJB5C@b|n#0JrlDAYJ1${r*5Uqvnyi zD@ejcg7MPHzI01B(ZW1}CLEE~j|+!onZ+I6cS!n;ZmS(E_qt)!fLSa$YOy%csE(Np zv`|MN;h>J971Z5KS^c!0ul>6=dO?swh(%LMk0;~88nF`%X)<5EN!M;nxk{kxfvHc7 z5~~Q0&_dG4;+*LWR+Djx_RIP6RFWu?48 Date: Tue, 1 Mar 2022 12:44:09 +0530 Subject: [PATCH 31/42] Added email confirmation --- database/data.db | Bin 28672 -> 32768 bytes models/__pycache__/user.cpython-39.pyc | Bin 1579 -> 2579 bytes models/user.py | 47 ++++++++++++++---- resources/__pycache__/user.cpython-39.pyc | Bin 3546 -> 3968 bytes resources/user.py | 55 ++++++++++++---------- static/confirmation_page.css | 5 ++ templates/confirmation_page.html | 31 ++++++++++++ 7 files changed, 103 insertions(+), 35 deletions(-) create mode 100644 static/confirmation_page.css create mode 100644 templates/confirmation_page.html diff --git a/database/data.db b/database/data.db index b242f12f2fd696cc828fbe233923cb387e1a0434..d7bd4092aeb73f6aa24454c82f771dd218fb87d4 100644 GIT binary patch delta 469 zcmZp8z}V2hG(nz`W1@nG5IfI(2EMiYXZaWLe&cK4xzBrgV`D0hRAVL^ySTVGW3ypN zVp2|OW=U#pF_>VV9Kw5nJYCFG?+jkgSt) z`SKZAC*S4M;$~|sVuYCNJXwf8jz2XwF*8RY%rVH>!!bz1baEnpmY9wL7iXxSXJDwS zf(BS#lS@;R8EB;vyHROzYEd!EyU$4)^#vt!1%2`~Rl#!T{ znpyy7q~wGO;E)coN-sG-*U-q=#FCMXLEBf91FkMNF)t-CC%3XVH789EO%zot z7Xt$W6aRe%{`>rIflLtCETM3QUze9zo)NB-Nr+h+;T(wR5S_aj`0w!V2D*DazpDbX UD?$qvr!pxs`*IR;)FJ~10L@R65&!@I delta 232 zcmZo@U}|{4I6rGx;Sik1Ts5J2Shuq$FcwCRk;0Nq$jkF@$89EX0@3 z%EBgYFFE-luf*hee0Fc0|2%7KW_j4 diff --git a/models/__pycache__/user.cpython-39.pyc b/models/__pycache__/user.cpython-39.pyc index 4a9919aaf1d13ba33deeef7373a1280162da0573..0528c2e1138aa932249ecb4c0dca1744236255f8 100644 GIT binary patch literal 2579 zcmbVOOH&(15Z;;Hl~ykhSOz~xqErrvoEV8WxLhtf7`sS}pb(@gd)XT8jKt!7$g?AC zRL;rHkzZiD${hU<@&oc4a+qsQ{DintNqSZiP}sS6RMVR7>D_*P-4ZDjvKp)#zfCoN z&uH48iaEbbVD7?>4}egOB3}!TjRI_AqfnghgX`2bJ`>7yprvuBj65jALLDtS9 zjUkp}nB`fng|BLA+XZUcBQ#}?(zIQqmOb`RqZyigsnIOU?vKCF?MpP*&?@;8u&q_F zH12z@kmhdaMIo@(Hj6t^7&B>fqF8_=&DoPKi-k;gx$m?hUO^JmW^XPgA#Fy_W*6p{ zT1~cGTO#f}S!@xqJjZC0F3hypB3qf84v6QsyWw=i+r8NqTX0tv=Gh9JS+0@V;>z4I zac7p;(hOyW+STEm?B$e#=Z$VMKRTnu$}JnZ!KyZBWi*)b)?&w zQ)&8~Gss)9t}olq!?PLS3v7gqLNT+iG0oBZOXCHyiy#{VSpj4tbTpBT5A=#4 z8>8ci>=G-{ORo_v(aSFl`!bzKxJh~ixQPLGm0kmGl1@M|uglDC%=kt`nQxn2IL13r zrCf)50pW+vV|IA51>rwX1rEEuPathF)eSwU?8z+LR1nW6UouTui*$Wec}u-1Bmt;$ zCb~RyG3X4JI_t1kVaMY@o@@KsK6;KQIzn&pu?CLeI=r|Dckgs*f-Qb1GcC^-j62Ok zX|_C`KumZ2xUZ)1Nzi`3Q4JXNNcFVR)iv66i2#h20d?g@)Dlk#XVonp?K4-z)vZIZ z7lqqvjUCeQs)5QxTvh&0cMfH-<%JY1I>UgzP(CNY;NG>}Vd|zYszDBP$D@1<)ITY9 ziQ#FLn+oO=@WrnwqC)JGD{zw8p;?c91^m#j(uaO|6g)DS!uqHC#6MH!&pVX}E;v;f zI+a-a`kY^rANrN(K&6vBAJgT3DokOA8CA^~M0*OdF|b_8h&^-auQuHfa7x2b>k^C;!5-bE?2@6augU zXn?e0p@6;XdqQ;LTh%In<8&T{OjHl%s=W+=EsjZ>#Zrg+9xmU0kM8tJtDE%)>)RXU z?X`#Njh*d#JL{YE-Z!hh=RPirJy!O;5Drm!V6V(c+lvKHP7g)`$`s7^z!UEfpN7keu@qx+z#I`dDydBpv%8=a8$dLaN9Wu8 zqT;7I%4gu+16R44qA~?zhrx842Zk>wa-pqCeFaA$ZoG zWyg_O$AMAT_0>7=I8VC7AKVli2eRO^KHJY`YBln^K`7JfpHv6tRfVp5q2N<05LZ7|whoncdB%`;k^#R;*aWfGsE$Jd`R}Pz$by+Di_u*`3*vl8-t`ggxz{ zy?E%M5o_XdD z=5N5`21l`xpa3Cl;}|C-Ahk_mnwWu^Sb(1g_jpMmd)ws1jh;~euQZ=KL%6f8ij12G3;ZSK~wE??lC zbHuy6cS3{SAFt0n@cMiK^ew8!elFz8jEguhOEB}-Af$C9qv{l|bR`A(K9ViiBnGH{ z;$7raU2=yx`euggSC0utgX#;}plw~D@WcBGM_jt#`B?T~lS>-ppniYB$-!lDpzpd&XlToLD86pW@aOD@UoRu9-Z@~XFNbFJ29 zJ)&4SI8(mkWTf3ZO#{Uh(u{2uj@WHHQdthuVZcLnMekppJ}cK z#xNxvw1hm=X=2nPdbzZ&8TzhrL=r~v#!(hWlbIxNKv9vFN&VB5Rm_gGh{%M5v~-sJ w9m>TZ)^%b7AYl3t?C!l9DzknHb&LP$Q*0~Aco~ar1m|+C)IkpFU 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() @@ -33,6 +36,30 @@ def find_by_username(cls, username: str) -> "UserModel": def find_by_id(cls, _id: int) -> "UserModel": return cls.query.filter_by(id=_id).first() + @classmethod + def find_by_email(cls, email: str) -> "UserModel": + return cls.query.filter_by(email=email).first() + + # This method will interact with Mailgun API and return the response sent + def send_confirmation_email(self) -> Response: + # http://127.0.0.1:5000 - is the 'url_root' + # url_for("userconfirm") - this must mathch the name of user confirmation endpoint + link = request.url_root[:-1] + url_for("userconfirm", user_id=self.id) + + return post( + f"https://api.mailgun.net/v3/{MAILGUN_DOMAIN}/messages", + auth=( + "api", + MAILGUN_API_KEY, + ), + data={ + "from": f"{FROM_TITLE} <{FROM_EMAIL}>", + "to": [self.email], + "subject": "CONFIRM REGISTRATION", + "text": f"Click the link to confirm ragistration: {link}", + }, + ) + def save_to_database(self) -> None: db.session.add(self) db.session.commit() diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc index 5d2448aa048e65be33ff1891a9f07579c1b5a55a..f9663ce42a6e78ec529e911952b7b6e086195bcc 100644 GIT binary patch literal 3968 zcmai1OLN=E5e5bz2vVR#$?|iZ4f5IzH(qL!%B!}rj=jz%<-}3BENvx=Lr@TAC_%!5 z9t zb|YZ_cw0CpR&WLK689i`TD}Uo!7FGY_K=gdizHT8$KQ-Qt+y~D^y6D zgo|7y>bPo7HQ1%~c`4-Y3ocR!Z4$QPJ&{G!_34AV_kQ=_{?>L(N11;t2zOA*Yp8?~ znA`}Mu(-+DQ{%)8Y;M5>cIDB6kHl_LDj{PNgJ%yz!$G-+QvMm0GEPm!POaz0bMs46 z*{6+(x$itT<$JVhVsdN1whZW-(7VvzhTfj($L~(e)7EptH1KZIn>Vk22v$DQIwZ9|9~yWx9;aKcpjw~A`fvAA!Q+N z`2&2E;54P)SD&lbsg~%tVX1GvveP2sl6;-h&O7x^HOnLbol4{a$K#bPIl531HsCbx7NsIAm+zu7*s|p@k4l4?uIV!Szl^c39m`>^ zIbQu7vu2>~)6C7YM&*VfStJarb{J*_AEngx!tn7ZN~b+baT=9nCh##_eiIw%^UPJW zyIeta8KtBva4lwP&1*h*PNpAW*o6-Yo10(3W4QIyh9@?JHPzU>p*0S7fcIu)(=pD> zs!2uwH^k(UvxgjLnI+$WAm69zCRKFjbw%c>EMW4dXp}B0 z!)lwh>9Mg(+x5})_G9!h{O!k>uv0eW24a;nX94iLO$IO;#TT2wT#Jsrxs3P5CXF{%xwhL)CXt zRTex%(vjo_RTqovmIl)I(D4%#Z7J7eX4eInNE@RpJapH0Q2rmG>u~X)*iG^UHg8}m z;ZegyqM?laDVq~ip0RmGCkV-bYEIa`OI&^e3GJ5rlWOC=#4T=PhNV0$HJ+JY+7oMH zbLT5Ye7F;w5IM)LTRFNzU3_fTjwPnioWDs^0wns;vb!&HFYd{Ah7Fi*(i_51l@lqQ@g zR|nuIMcnu+yrI|8xR;mkIht?fI<<&uB@uwULKQ{H${m6@jta@E)-)OjE}PWD;63CC zy@vLisx=~aCc3S?=V!FW&#AhAh(-tT&u>vm@;$>^vpjo^Ic(YVtYzl1@iKu>UvlcaeKW9NiL+s_l$2{OStaUjB)rVct6!S=;SEkKP}iqNW| z=A)qunt$B@#LQDh9EIQy`U5a#j8s|sDyc8?+SXj4_0wW^7dMGL&Ruy4OUh+bl@)Q` zV>KJyChZ#xD4;LthKPvT=2){{{ngXR;JnFuWsBB(YXtZNy<|SUvdb7KPR?{tUp(g~ zC32WIsrEYLOx&h?Oq;aN~aG&D;Gy~zWOG^=9n-wtV8_1I{)2M zK;EGZE-pp4G_>!cBbzNi#OIolbY#;Ou+T!Rp|ywy zH84L#V?Nf%kGVNvA()Rc+!;W@akgr38$9Rm#xus9iFJa^nKvmv#h$Ir?Xia)6|DB* z_VG}RTS`1p8+$5C$5$4Ts39`fKI!F;u=fRh>DYxBqFIGPl>C?~N_HgqlBB!VOoO<* z76>f>Kg^Hdx!TO^5O%SekzJ*S3xKOS@J3!_;IHoi}@L_6N#g z>S*?+`}`Gpk5DcMhBUpK@;Y8sV}PvcVD2z<6IDlR>2H@pQZ4Cbg4H7-55{7&+b;#M zh`&NLyROw^=|2*zOMlO5a@E9I`>F;raRdH#(!Zs$Pduxw8hCZ$JXLjQ_ts8|L^j2L z1kEKi&r@Qj!>{H??a>;Rl1?*c<8vTa?l0<0@;0JHZEe?FH$D7rx>S42vAceEy?dkE H?zaC23_*v8 literal 3546 zcmai1OLODK5yk)@36h{hJ$Ajj-o$Voj_Ej5l1fz)CvmO4wNAORR&A}ct}-qt2s0!h z0R(pdt}L2+(q3JaRORS{bksjden2j`=CpsoCx6|bB+8b`P|a|<2bf3q*I&27PN!wy z`RdvIz5jI$j5{}$x7bRoMe--g_gE09-MegpEV>_YCcdZ@E?yq-Pw)k@XL<{;Q?BH2c0r}Ar0SDmK4%_WgH9NmKH8; zmo6>%R1M?2P&yKL`TQ|4Am=|s%RfL@7$>4IPpnhpRJ;_0eNvl?{raiU@6f8Tkk-K5 zG3Mf=acaPgwyZ%{Cta_xT{cqKBpj;QdmoLxDK_;E<6`6$qp)Cc9Hz<(6If6ldFo4? zC-2Aq#DRSNQsK)xbXG5vQb{kLvXKYVBsuE8{x6Q#bILZY8{}-B(oV8aYPv<~ur>2+ ztlK1Z2k}@2dq)AiPWzV5#i*NyFBF^DC-`w)PBfoI9vbZ*8B|N+$^D;6YHff*{T0G@-E@1ka~o zvY2T{NtoxU!p2g+iKY4MOWi%vt)Tk_?MuFPEK{)N2Y9-mf#2e_OFSuCif`b3X}z+6 zs+zJE8hD>Iby+9>mo{DF4ADgw2F@dL%=trLrN&Tu7XyOYTmWX~+|2JS!;1hoH&2DM z8CbP#y^8n0U8Uu{aaMSPY&w?x*Z&4`UjGMEJ-c#x>)h$F)N9a}ww$JuGfTe#LI0dO z3YG7fr?dc_KSQ58=nShRY~h+Shqmiu>gH?o@%qizn42diM+q*1D5>#f>eXd5xkLXF z-K@jcLg=eP$-aIEx}L@N(S#L$vziJ9I!iAQt9$f4;k4?=&1JoM^J9z`2&=B*h*_I2 zQ_XMp8u};na9M+~I5G4Urfwv;1G(RBbO(ne@k9`f}A zv>ziUroPA7FdkoFrW!saJThFy7~I561RuRuH0I{M1Fmw8Az|Fqe$m2jn}GGT`O+#} zmaMPEOM7n3ZE1gFRz8t+*@(rCfnNuHYO0FR?jhVeC^cKznzHqN3fX)@> zXCE?VXMg)5n|dSA4dXV-#)DXcRy@sYteH*T_a=z~3whwQ=)gMyYk1)PIO)$eKOMiI zl;k17aM}nHFy_C2`(B3;uGeuKCP2MGBjP^2N!=~#$cm*i0Yw~UT9(Z)D&iMNUsBg; z#zdSnFzD4dLdxcp0+(2^ij&{b8o#IR5)6z3Vw%s;a@yXoKxFp1SvOaNYps|dv=xFI zJGEjneIKtaEpmyd-(cb~T18DjlqSs&&~!mfgxO_kI!3ipr8}agMZ)6@itV#$aRpSw zM}$iTm4`uL(D!!@q&jh85*s0@f#?8(bM7gv{UWZMd1q%SA$dtQ9Kw^><05E@)^!J6 zX(5C4%!+p?V3zKFVZageIr)JorzK|0Z`G+6IiP-BoN8jJo4Njvm>r<;%8qG*%rxn4 zHtEkWyKGX97-V{BQx(Bxt%2X`rMpYj&eN*M*+p%FGK$0Eijm2{7m-WuA|sQ5=VB3y zFz}peiE|TwqKQZ~qmAj$)J;;5Z9s)Y-lZE&wMOB@`P^@g)nQd-U1!+m_+Vk!Ix+d$ z^0^m){D3yNMD-;g|AL9h`2s|7j?kpL*?!-L0>G66rV;$M@+Z_X-z!0d8X z(eFwzHv{k-)n!)!Dr>S1OE#FZobBBTs8y!C2=)q}RAUW?;F&5qFB2)Jp+ySil%wec z`Zjgfq!~^TUtuzL(HUmTvPB(Mf}wfe#U&|oZuhQ7@cXnhXPWCg@F8YrXqS{gn(f}3 zWo>|J=b)wofjw&HKDF-r=iYAX_zG0p(YIaYlli7%+z$)&lM1avb*BM_3CS? z$JLjG8l&7~xG^185ylXth{iuORGH^}Vn=3J->vG;g^KJ@HusV+I!I8{G6PiXOobPx uM`ksSLk5$k8(jy#()nvu>3s-}H*%s)S5$xZ9sXUb+ucq4-Rrixt?vLV$_Q-$ diff --git a/resources/user.py b/resources/user.py index b579334..dfe8b92 100644 --- a/resources/user.py +++ b/resources/user.py @@ -1,6 +1,6 @@ -from flask import request +import traceback +from flask import make_response, render_template, 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, @@ -14,21 +14,6 @@ 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() @@ -45,12 +30,24 @@ def post(cls): # if exists, then don't add return {"message": "An user with that username already exists."}, 400 + # Then check if that user email is present or not + if UserModel.find_by_email(user.email): + # if exists, then don't add + return {"message": "An user with that email 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 + try: + user.save_to_database() + user.send_confirmation_email() + return { + "messege": "Account created successfully, an email with activation link has been sent to your email.", + }, 201 + except: + # print(err.messages) + traceback.print_exc() + return {"message": "Internal server error, failed to create user"} class User(Resource): @@ -76,8 +73,8 @@ def delete(cls, user_id: int): class UserLogin(Resource): @classmethod def post(cls): - # get data from parser - user_data = user_schema.load(request.get_json()) + # get data from user to login. Include email to optional field. + user_data = user_schema.load(request.get_json(), partial=("email",)) # find user in database user = UserModel.find_by_username(user_data.username) @@ -128,6 +125,14 @@ def get(cls, user_id: int): if user: user.activated = True user.save_to_database() - return {"message": "User activated."}, 200 - - return {"meggase": "User not found"}, 404 + headers = {"Content-Type": "text/html"} + return make_response( + render_template( + "confirmation_page.html", + email=user.username, + ), + 200, + headers, + ) + + return {"message": "User not found."} diff --git a/static/confirmation_page.css b/static/confirmation_page.css new file mode 100644 index 0000000..5a74cf6 --- /dev/null +++ b/static/confirmation_page.css @@ -0,0 +1,5 @@ +.full-height { + height: 80vh; + margin: 10px; + background-color: aquamarine; +} diff --git a/templates/confirmation_page.html b/templates/confirmation_page.html new file mode 100644 index 0000000..4eb558e --- /dev/null +++ b/templates/confirmation_page.html @@ -0,0 +1,31 @@ + + + + + Registration Confirmation + + + + + +
+
+

Thank You

+

Your registration has been confirmed through < {{ email }} >.

+
+ +
+
+

Activated on:

+ +
+
+ + + \ No newline at end of file From 5d4d5a9dda780e020d3d6f22af3ad910c334ea41 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Wed, 2 Mar 2022 20:01:54 +0530 Subject: [PATCH 32/42] Added environment variables --- .env.example | 2 ++ .gitignore | 4 ++- database/data.db | Bin 32768 -> 32768 bytes libs/__init__.py | 0 libs/mailgun.py | 33 +++++++++++++++++++++++++ models/__pycache__/item.cpython-39.pyc | Bin 1503 -> 1525 bytes models/__pycache__/user.cpython-39.pyc | Bin 2579 -> 2323 bytes models/item.py | 2 +- models/user.py | 31 +++++------------------ requirements.txt | 3 ++- 10 files changed, 47 insertions(+), 28 deletions(-) create mode 100644 .env.example create mode 100644 libs/__init__.py create mode 100644 libs/mailgun.py diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4756ef5 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +MAILGUN_DOMAIN= +MAILGUN_API_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 643b9c8..e4677e2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ test/test.py test/test.db __pycache__ .vscode -.idea \ No newline at end of file +.idea +/.env +database/data.db \ No newline at end of file diff --git a/database/data.db b/database/data.db index d7bd4092aeb73f6aa24454c82f771dd218fb87d4..49549e3aaae37ea9947260c45bc125a324d468ab 100644 GIT binary patch delta 115 zcmZo@U}|V!njp={I#I@%k#%Fj5`H#D{-X^1M>h*9?B*A@WMpH|_7&yKP0UM4%qcES z%1BH}O)Z%Gz+PeUA9)U-+P4h+Z#N4%Jm!}bV%A1i$HlkQu0faa#E8M3mnpO6Ek!4lJj#7jf_n!8QB=LeMLFo>T(nFQWA3}Kd@Jr{70Sx zX#9N!{`>rIHw!9U=GWzAmS=>kWD;W5Mpy*V$Hl Response: + + return post( + f"https://api.mailgun.net/v3/{cls.MAILGUN_DOMAIN}/messages", + auth=( + "api", + cls.MAILGUN_API_KEY, + ), + data={ + "from": f"{cls.FROM_TITLE} <{cls.FROM_EMAIL}>", + "to": email, + "subject": subject, + "text": text, + "html": html, + }, + ) diff --git a/models/__pycache__/item.cpython-39.pyc b/models/__pycache__/item.cpython-39.pyc index a1c4f639674f11a6cafe9821303d4d18e89425f4..f448d86dac81ed43c6df91b76e161fa26143aa3f 100644 GIT binary patch delta 373 zcmcc5{gsTZ zsU@jJ(jXBT5P_`4g=HSl36EL)HAO*EOdvu6M96^%1rUKy4rYl=j%Jkwy0MbgMhjgE asQea(O>TZlX-=vgBam0j0wj2tc$fj-yG?`u delta 329 zcmey$eV>~*k(ZZ?0SL7GWRm(P^42kKnb?yWvVbpzdm&>Kf2u&LUfdOYl&i+2Xw+u7Jp3< mkQ5V$5C;*mAVMBQAe4hy!jlVGWf{39_p{mvU`X9&l>z`gokBbS diff --git a/models/__pycache__/user.cpython-39.pyc b/models/__pycache__/user.cpython-39.pyc index 0528c2e1138aa932249ecb4c0dca1744236255f8..7755611268075aa0680f397f9c5aedeb5cc0321f 100644 GIT binary patch delta 861 zcmZuv&2JJx6yMpM{otz>iUAa%wuW@uNx+@Wl=r1Iq({MZXT&c|vV_z~ugeyKE6Wp2*vm(`2mXaxKc3UqU8sPrTMl(P?En>iZ~%s+?TIx__9sd zoQO=BrNkdQ50E~AjPU$$%kGkT<7wR{X8299bgDTF+cyJR*b>5(xKOJ%KV$Ulr35Fhm5vvEDYp@@*q-L zv&yBl;Ipao4ee|-D<)zg0kyD81rNa%3>Gz2AG_3P`Pi|$kA2sn7}-RsqfUe=pQqtk*edw$3+=qd8d7*Y-VTcMllIh2{8XW9#NQ$SZc& zV;3Nq2LO?>C4eHpI>5h|0uMyPV7(CE3@t_?GdPH<<{ADdbZ4&?b?k;)1ThHB2GsiM fZcmICT>D=)-zar@r0>u&XeOX;7Kxu6MN#Ds!VA7D delta 1112 zcmZuw&2Jk;6yKR0uQ&dxlN!>}loVBJ<)pFKU$Ik#;)tYepovk_BJjnU^}KZ)Y;QC> zu7XvR16)CrxgdpHkvMYW58%Xqz+AcTFHj-Gj3Fh8nAQ93`eAQ|ARmXUUv#o8I>v@fxEJImbm>;sF< zu*4S@OF-ga?sL0+nI-qEeCowl&ErWWq9f*qN99_z9{AAk>a<&>s{y5r3NWA5N&&3G zTBR_g{XuU$Dulc@DF;yNuGOlr#!3y3daG-d2JM#WP%ly6D_6Z0C_{xcpu6IEwR)u< z&`D-bgdCz9k0BBpA8bz+qOre!g))Fc7LNS^bm?KUXR;K!;qb>_?;@0vIs6gI$|1hx zX7!bSu=u<@!3-^aV;9h@EZT41nbZC}=CA7?f5X81E4ZRHl?eJH*6}~>^w~}Mt-Um* zHER62*>;{hbzC5)&&XiTbz6Gm*5a%tc9Dj&g|*^pc~?vHp&XONbwL;ru@j5MPHpB0 zC%_`Et+;SCx~Js1buiOIrYq0UiFJxu@;ky(U)s7WY55CTtX$g{;xM{fEYidNtICf+ z6rWX!lbPZWqKNh&Qnrpy@QwAC=$`zYG>`K{k${Iog+-|1y21z06^aNr5_zl$9gBTM z7!@?1;Eq18a>sno;bACvT$}uw0W)DLv$L`N=)u$7&Spz%yXw-Jt$Wvbf9qqFxc|7d z(|NM};U z!pQxT{2`HZoGs}1dA|JGc?~t?_YQqn(xE)zruMw7K{*TtAb?kmr)Jzu<`3s#j6`vjoZ4FmsWnrh}= zTt8ncCtL2#WG+Ld)^0v0KXdO7`KF0)X=uAoBjE4twRZWE@huxLrQs_YRBR{IXP$EZ eFNJc~hanpe;GUVt$joyV=?|xH4ilRgvHu27H~=O9 diff --git a/models/item.py b/models/item.py index 6b87894..0a137cd 100644 --- a/models/item.py +++ b/models/item.py @@ -13,7 +13,7 @@ class ItemModel(db.Model): # tells SQLAlchemy that it is something that will be 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") + store = db.relationship("StoreModel", back_populates="items") # searches the database for items using name @classmethod diff --git a/models/user.py b/models/user.py index d098c8d..60fddb9 100644 --- a/models/user.py +++ b/models/user.py @@ -3,18 +3,7 @@ from flask import request, url_for from database import db - -UserJSON = Dict[str, Union[int, str]] -# MAILGUN_DOMAIN = "YOUR_DOMAIN" -MAILGUN_DOMAIN = "sandbox2547fbe807ac4a6faa83edbd51fe6e93.mailgun.org" - -# MAILGUN_API_KEY = "API_KEY_HERE" -MAILGUN_API_KEY = "fe5c954e9d180a06938ac17e71ab0240-e2e3d8ec-005737fa" - -FROM_TITLE = "Stores RestAPI" - -# FROM_EMAIL = "Your Mailgun Email" -FROM_EMAIL = "subhadeepdoublecap@gmail.com" +from libs.mailgun import Mailgun class UserModel(db.Model): @@ -46,19 +35,11 @@ def send_confirmation_email(self) -> Response: # url_for("userconfirm") - this must mathch the name of user confirmation endpoint link = request.url_root[:-1] + url_for("userconfirm", user_id=self.id) - return post( - f"https://api.mailgun.net/v3/{MAILGUN_DOMAIN}/messages", - auth=( - "api", - MAILGUN_API_KEY, - ), - data={ - "from": f"{FROM_TITLE} <{FROM_EMAIL}>", - "to": [self.email], - "subject": "CONFIRM REGISTRATION", - "text": f"Click the link to confirm ragistration: {link}", - }, - ) + subject = "CONFIRM REGISTRATION" + text = f"Click the link to confirm ragistration: {link}" + html = f'Click the link to confirm ragistration: {link}' + + return Mailgun.send_email([self.email], subject, text, html) def save_to_database(self) -> None: db.session.add(self) diff --git a/requirements.txt b/requirements.txt index 242289e..40cb649 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ Flask_-JWT-Extended Flask-SQLAlchemy Marshmallow Flask-Marshmallow -Marshmallow-SQLAlchemy \ No newline at end of file +Marshmallow-SQLAlchemy +Python-Dotenv \ No newline at end of file From af8d3cbdc7a314b6e56c64eec51f4276582784d0 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Sat, 5 Mar 2022 14:31:05 +0530 Subject: [PATCH 33/42] Minor changes --- libs/mailgun.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/libs/mailgun.py b/libs/mailgun.py index aea8b3d..cf15396 100644 --- a/libs/mailgun.py +++ b/libs/mailgun.py @@ -3,21 +3,29 @@ from requests import Response, post -class Mailgun: +class MailgunException(Exception): + def __init__(self, message: str): + super().__init__(message) - MAILGUN_DOMAIN = os.environ.get("MAILGUN_DOMAIN") - MAILGUN_API_KEY = os.environ.get("MAILGUN_API_KEY") - FROM_TITLE = "Stores RestAPI" +class Mailgun: + MAILGUN_DOMAIN = os.environ.get("MAILGUN_DOMAIN") # can be None + MAILGUN_API_KEY = os.environ.get("MAILGUN_API_KEY") # can be None + FROM_TITLE = "Stores RestAPI" # FROM_EMAIL = "Your Mailgun Email" FROM_EMAIL = "subhadeepdoublecap@gmail.com" # This method will interact with Mailgun API and return the response sent @classmethod def send_email(cls, email: List[str], subject: str, text: str, html: str) -> Response: + if cls.MAILGUN_API_KEY is None: + raise MailgunException("Falied to load Mailgun API key.") - return post( + if cls.MAILGUN_DOMAIN is None: + raise MailgunException("Failde to load Mailgun domain.") + + response = post( f"https://api.mailgun.net/v3/{cls.MAILGUN_DOMAIN}/messages", auth=( "api", @@ -31,3 +39,8 @@ def send_email(cls, email: List[str], subject: str, text: str, html: str) -> Res "html": html, }, ) + + if response.status_code != 200: + raise MailgunException("Error in sending confirmation email, user registration failed.") + + return response From 12480d9b3615703d34a8ec0b37db4a793f26c5b5 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Sat, 5 Mar 2022 15:21:06 +0530 Subject: [PATCH 34/42] minor unfortunate changes --- libs/mailgun.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/mailgun.py b/libs/mailgun.py index cf15396..7e27e92 100644 --- a/libs/mailgun.py +++ b/libs/mailgun.py @@ -14,7 +14,7 @@ class Mailgun: MAILGUN_API_KEY = os.environ.get("MAILGUN_API_KEY") # can be None FROM_TITLE = "Stores RestAPI" # FROM_EMAIL = "Your Mailgun Email" - FROM_EMAIL = "subhadeepdoublecap@gmail.com" + FROM_EMAIL = "postmaster@sandboxb3ef8f2c0e20406cb3f834bc39735ab4.mailgun.org" # This method will interact with Mailgun API and return the response sent @classmethod From a7ff629b540023e211bc1c280a32981710c2ce7f Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Sat, 5 Mar 2022 20:17:49 +0530 Subject: [PATCH 35/42] minor changes --- resources/__pycache__/user.cpython-39.pyc | Bin 3968 -> 3546 bytes resources/user.py | 55 ++++++++++------------ static/confirmation_page.css | 5 -- templates/confirmation_page.html | 31 ------------ 4 files changed, 25 insertions(+), 66 deletions(-) delete mode 100644 static/confirmation_page.css delete mode 100644 templates/confirmation_page.html diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc index f9663ce42a6e78ec529e911952b7b6e086195bcc..5d2448aa048e65be33ff1891a9f07579c1b5a55a 100644 GIT binary patch literal 3546 zcmai1OLODK5yk)@36h{hJ$Ajj-o$Voj_Ej5l1fz)CvmO4wNAORR&A}ct}-qt2s0!h z0R(pdt}L2+(q3JaRORS{bksjden2j`=CpsoCx6|bB+8b`P|a|<2bf3q*I&27PN!wy z`RdvIz5jI$j5{}$x7bRoMe--g_gE09-MegpEV>_YCcdZ@E?yq-Pw)k@XL<{;Q?BH2c0r}Ar0SDmK4%_WgH9NmKH8; zmo6>%R1M?2P&yKL`TQ|4Am=|s%RfL@7$>4IPpnhpRJ;_0eNvl?{raiU@6f8Tkk-K5 zG3Mf=acaPgwyZ%{Cta_xT{cqKBpj;QdmoLxDK_;E<6`6$qp)Cc9Hz<(6If6ldFo4? zC-2Aq#DRSNQsK)xbXG5vQb{kLvXKYVBsuE8{x6Q#bILZY8{}-B(oV8aYPv<~ur>2+ ztlK1Z2k}@2dq)AiPWzV5#i*NyFBF^DC-`w)PBfoI9vbZ*8B|N+$^D;6YHff*{T0G@-E@1ka~o zvY2T{NtoxU!p2g+iKY4MOWi%vt)Tk_?MuFPEK{)N2Y9-mf#2e_OFSuCif`b3X}z+6 zs+zJE8hD>Iby+9>mo{DF4ADgw2F@dL%=trLrN&Tu7XyOYTmWX~+|2JS!;1hoH&2DM z8CbP#y^8n0U8Uu{aaMSPY&w?x*Z&4`UjGMEJ-c#x>)h$F)N9a}ww$JuGfTe#LI0dO z3YG7fr?dc_KSQ58=nShRY~h+Shqmiu>gH?o@%qizn42diM+q*1D5>#f>eXd5xkLXF z-K@jcLg=eP$-aIEx}L@N(S#L$vziJ9I!iAQt9$f4;k4?=&1JoM^J9z`2&=B*h*_I2 zQ_XMp8u};na9M+~I5G4Urfwv;1G(RBbO(ne@k9`f}A zv>ziUroPA7FdkoFrW!saJThFy7~I561RuRuH0I{M1Fmw8Az|Fqe$m2jn}GGT`O+#} zmaMPEOM7n3ZE1gFRz8t+*@(rCfnNuHYO0FR?jhVeC^cKznzHqN3fX)@> zXCE?VXMg)5n|dSA4dXV-#)DXcRy@sYteH*T_a=z~3whwQ=)gMyYk1)PIO)$eKOMiI zl;k17aM}nHFy_C2`(B3;uGeuKCP2MGBjP^2N!=~#$cm*i0Yw~UT9(Z)D&iMNUsBg; z#zdSnFzD4dLdxcp0+(2^ij&{b8o#IR5)6z3Vw%s;a@yXoKxFp1SvOaNYps|dv=xFI zJGEjneIKtaEpmyd-(cb~T18DjlqSs&&~!mfgxO_kI!3ipr8}agMZ)6@itV#$aRpSw zM}$iTm4`uL(D!!@q&jh85*s0@f#?8(bM7gv{UWZMd1q%SA$dtQ9Kw^><05E@)^!J6 zX(5C4%!+p?V3zKFVZageIr)JorzK|0Z`G+6IiP-BoN8jJo4Njvm>r<;%8qG*%rxn4 zHtEkWyKGX97-V{BQx(Bxt%2X`rMpYj&eN*M*+p%FGK$0Eijm2{7m-WuA|sQ5=VB3y zFz}peiE|TwqKQZ~qmAj$)J;;5Z9s)Y-lZE&wMOB@`P^@g)nQd-U1!+m_+Vk!Ix+d$ z^0^m){D3yNMD-;g|AL9h`2s|7j?kpL*?!-L0>G66rV;$M@+Z_X-z!0d8X z(eFwzHv{k-)n!)!Dr>S1OE#FZobBBTs8y!C2=)q}RAUW?;F&5qFB2)Jp+ySil%wec z`Zjgfq!~^TUtuzL(HUmTvPB(Mf}wfe#U&|oZuhQ7@cXnhXPWCg@F8YrXqS{gn(f}3 zWo>|J=b)wofjw&HKDF-r=iYAX_zG0p(YIaYlli7%+z$)&lM1avb*BM_3CS? z$JLjG8l&7~xG^185ylXth{iuORGH^}Vn=3J->vG;g^KJ@HusV+I!I8{G6PiXOobPx uM`ksSLk5$k8(jy#()nvu>3s-}H*%s)S5$xZ9sXUb+ucq4-Rrixt?vLV$_Q-$ literal 3968 zcmai1OLN=E5e5bz2vVR#$?|iZ4f5IzH(qL!%B!}rj=jz%<-}3BENvx=Lr@TAC_%!5 z9t zb|YZ_cw0CpR&WLK689i`TD}Uo!7FGY_K=gdizHT8$KQ-Qt+y~D^y6D zgo|7y>bPo7HQ1%~c`4-Y3ocR!Z4$QPJ&{G!_34AV_kQ=_{?>L(N11;t2zOA*Yp8?~ znA`}Mu(-+DQ{%)8Y;M5>cIDB6kHl_LDj{PNgJ%yz!$G-+QvMm0GEPm!POaz0bMs46 z*{6+(x$itT<$JVhVsdN1whZW-(7VvzhTfj($L~(e)7EptH1KZIn>Vk22v$DQIwZ9|9~yWx9;aKcpjw~A`fvAA!Q+N z`2&2E;54P)SD&lbsg~%tVX1GvveP2sl6;-h&O7x^HOnLbol4{a$K#bPIl531HsCbx7NsIAm+zu7*s|p@k4l4?uIV!Szl^c39m`>^ zIbQu7vu2>~)6C7YM&*VfStJarb{J*_AEngx!tn7ZN~b+baT=9nCh##_eiIw%^UPJW zyIeta8KtBva4lwP&1*h*PNpAW*o6-Yo10(3W4QIyh9@?JHPzU>p*0S7fcIu)(=pD> zs!2uwH^k(UvxgjLnI+$WAm69zCRKFjbw%c>EMW4dXp}B0 z!)lwh>9Mg(+x5})_G9!h{O!k>uv0eW24a;nX94iLO$IO;#TT2wT#Jsrxs3P5CXF{%xwhL)CXt zRTex%(vjo_RTqovmIl)I(D4%#Z7J7eX4eInNE@RpJapH0Q2rmG>u~X)*iG^UHg8}m z;ZegyqM?laDVq~ip0RmGCkV-bYEIa`OI&^e3GJ5rlWOC=#4T=PhNV0$HJ+JY+7oMH zbLT5Ye7F;w5IM)LTRFNzU3_fTjwPnioWDs^0wns;vb!&HFYd{Ah7Fi*(i_51l@lqQ@g zR|nuIMcnu+yrI|8xR;mkIht?fI<<&uB@uwULKQ{H${m6@jta@E)-)OjE}PWD;63CC zy@vLisx=~aCc3S?=V!FW&#AhAh(-tT&u>vm@;$>^vpjo^Ic(YVtYzl1@iKu>UvlcaeKW9NiL+s_l$2{OStaUjB)rVct6!S=;SEkKP}iqNW| z=A)qunt$B@#LQDh9EIQy`U5a#j8s|sDyc8?+SXj4_0wW^7dMGL&Ruy4OUh+bl@)Q` zV>KJyChZ#xD4;LthKPvT=2){{{ngXR;JnFuWsBB(YXtZNy<|SUvdb7KPR?{tUp(g~ zC32WIsrEYLOx&h?Oq;aN~aG&D;Gy~zWOG^=9n-wtV8_1I{)2M zK;EGZE-pp4G_>!cBbzNi#OIolbY#;Ou+T!Rp|ywy zH84L#V?Nf%kGVNvA()Rc+!;W@akgr38$9Rm#xus9iFJa^nKvmv#h$Ir?Xia)6|DB* z_VG}RTS`1p8+$5C$5$4Ts39`fKI!F;u=fRh>DYxBqFIGPl>C?~N_HgqlBB!VOoO<* z76>f>Kg^Hdx!TO^5O%SekzJ*S3xKOS@J3!_;IHoi}@L_6N#g z>S*?+`}`Gpk5DcMhBUpK@;Y8sV}PvcVD2z<6IDlR>2H@pQZ4Cbg4H7-55{7&+b;#M zh`&NLyROw^=|2*zOMlO5a@E9I`>F;raRdH#(!Zs$Pduxw8hCZ$JXLjQ_ts8|L^j2L z1kEKi&r@Qj!>{H??a>;Rl1?*c<8vTa?l0<0@;0JHZEe?FH$D7rx>S42vAceEy?dkE H?zaC23_*v8 diff --git a/resources/user.py b/resources/user.py index dfe8b92..b579334 100644 --- a/resources/user.py +++ b/resources/user.py @@ -1,6 +1,6 @@ -import traceback -from flask import make_response, render_template, request +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, @@ -14,6 +14,21 @@ 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() @@ -30,24 +45,12 @@ def post(cls): # if exists, then don't add return {"message": "An user with that username already exists."}, 400 - # Then check if that user email is present or not - if UserModel.find_by_email(user.email): - # if exists, then don't add - return {"message": "An user with that email 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 - try: - user.save_to_database() - user.send_confirmation_email() - return { - "messege": "Account created successfully, an email with activation link has been sent to your email.", - }, 201 - except: - # print(err.messages) - traceback.print_exc() - return {"message": "Internal server error, failed to create user"} + user.save_to_database() + + return {"messege": "User added successfully."}, 201 class User(Resource): @@ -73,8 +76,8 @@ def delete(cls, user_id: int): class UserLogin(Resource): @classmethod def post(cls): - # get data from user to login. Include email to optional field. - user_data = user_schema.load(request.get_json(), partial=("email",)) + # get data from parser + user_data = user_schema.load(request.get_json()) # find user in database user = UserModel.find_by_username(user_data.username) @@ -125,14 +128,6 @@ def get(cls, user_id: int): if user: user.activated = True user.save_to_database() - headers = {"Content-Type": "text/html"} - return make_response( - render_template( - "confirmation_page.html", - email=user.username, - ), - 200, - headers, - ) - - return {"message": "User not found."} + return {"message": "User activated."}, 200 + + return {"meggase": "User not found"}, 404 diff --git a/static/confirmation_page.css b/static/confirmation_page.css deleted file mode 100644 index 5a74cf6..0000000 --- a/static/confirmation_page.css +++ /dev/null @@ -1,5 +0,0 @@ -.full-height { - height: 80vh; - margin: 10px; - background-color: aquamarine; -} diff --git a/templates/confirmation_page.html b/templates/confirmation_page.html deleted file mode 100644 index 4eb558e..0000000 --- a/templates/confirmation_page.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - Registration Confirmation - - - - - -
-
-

Thank You

-

Your registration has been confirmed through < {{ email }} >.

-
- -
-
-

Activated on:

- -
-
- - - \ No newline at end of file From b2f02e88e7f15fd4b885b6c6b1bbd27b6ebe2275 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Sat, 5 Mar 2022 20:26:29 +0530 Subject: [PATCH 36/42] Re-added confirmation page --- static/confirmation_page.css | 5 +++++ template/confirmation_page.html | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 static/confirmation_page.css create mode 100644 template/confirmation_page.html diff --git a/static/confirmation_page.css b/static/confirmation_page.css new file mode 100644 index 0000000..5a74cf6 --- /dev/null +++ b/static/confirmation_page.css @@ -0,0 +1,5 @@ +.full-height { + height: 80vh; + margin: 10px; + background-color: aquamarine; +} diff --git a/template/confirmation_page.html b/template/confirmation_page.html new file mode 100644 index 0000000..4eb558e --- /dev/null +++ b/template/confirmation_page.html @@ -0,0 +1,31 @@ + + + + + Registration Confirmation + + + + + +
+
+

Thank You

+

Your registration has been confirmed through < {{ email }} >.

+
+ +
+
+

Activated on:

+ +
+
+ + + \ No newline at end of file From 3daf4391641e0711c9ca9b17fe54ac31fc411dfc Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Sat, 5 Mar 2022 21:02:59 +0530 Subject: [PATCH 37/42] minor changes --- database/data.db | Bin 32768 -> 32768 bytes libs/mailgun.py | 2 +- models/__pycache__/user.cpython-39.pyc | Bin 2323 -> 2323 bytes resources/__pycache__/user.cpython-39.pyc | Bin 3546 -> 4064 bytes resources/user.py | 33 ++++++++++++++---- .../confirmation_page.html | 0 6 files changed, 28 insertions(+), 7 deletions(-) rename {template => templates}/confirmation_page.html (100%) diff --git a/database/data.db b/database/data.db index 49549e3aaae37ea9947260c45bc125a324d468ab..6f30f8c989a1c5af9e1e410a63965a3629a38b5e 100644 GIT binary patch delta 141 zcmZo@U}|V!+Q25ja-MX KaPv2NE(ZV|C@hTt delta 161 zcmZo@U}|V!+Q25ja+HDp=w?BI-TbbWjBE_rzM`DDiFqlBImM+(8Hp*WsRg-}#i==I zdI&yP+95qRF*8RmIX~CX$k>F7fq{XM|1AUm+s%RwkNIVVn6=T3fhf7dz<+16pu#17 RDP?9~PV7c+{$|hR002QnG-Ch& diff --git a/libs/mailgun.py b/libs/mailgun.py index 7e27e92..cf15396 100644 --- a/libs/mailgun.py +++ b/libs/mailgun.py @@ -14,7 +14,7 @@ class Mailgun: MAILGUN_API_KEY = os.environ.get("MAILGUN_API_KEY") # can be None FROM_TITLE = "Stores RestAPI" # FROM_EMAIL = "Your Mailgun Email" - FROM_EMAIL = "postmaster@sandboxb3ef8f2c0e20406cb3f834bc39735ab4.mailgun.org" + FROM_EMAIL = "subhadeepdoublecap@gmail.com" # This method will interact with Mailgun API and return the response sent @classmethod diff --git a/models/__pycache__/user.cpython-39.pyc b/models/__pycache__/user.cpython-39.pyc index 7755611268075aa0680f397f9c5aedeb5cc0321f..223a9c568ee02e98446a8af998a56155e8440c1f 100644 GIT binary patch delta 20 acmbO%G+Br{k(ZZ?0SI1{D{tiH-~<3Ia|9{? delta 20 acmbO%G+Br{k(ZZ?0SK&|38lm9>tK5X(;b4Hr+10m7HuIhJn)mO!!-EJ6o zzWVF?yPI9Z_%|{0j}7J~O8y@ZZg4X;64NtHsclDC9;YM67`TTVJ&=WK2#UP)HHRo$;1Ur4%MSJxZy z#bnJ}GY!#wYIv7^Yw#v-Ju`SqSWhkQGWa%M0>7mBE8v%T2Yg5KSHZ9FRq(5t{|5LA zybHdo`D@@W@-^^lntzX1w~gMVmpHr8GmDxOpN&MWP<0-NJR8YSpuYUaAdYyTqAa~F zWhNnN<-tJsxsraE411O0LMTO`gdc>V$a7z1haxSyQ=t?CDf0cPv~_T#d|D`yf)|}V zp?s1=T%;;e$3<-_+3HzEI|&X2I_JYI%|)>+MaqTrl}Ls$mXoa9cm2~`$lqsN#MoMc znC)<1BmubkC-*jQ{qEk~?VXSgIsaH-ZldIuL4@I%-0+yNxXIZwxJ>cd}1p5q%tuNsxM6W0nHkj+#0ZL1M(W= z4&*l=w`cP4^@({>e_@ygz76{F3a_HIHei1;w!zb@nkOyYW^yMotHwjLd<=8NUh7*$ zErI@nJu&`hGj&JUxO)_-eOK)VO0#K@2semvTm0A+k8!5FAN>P|8h`la0H`pEU+?Yd zB?OlE^JW-kqg1)&J##l7>FXbi;`n&o4N|w9K<^+3RrE;PkQ+zop}QaC?yeB2o8#VH zmAS_-w{k%LQh)g{MJ`F7gphBFOmG-2pZwI1MS+)#vK9izXe{&-Kk0cAN!V z+ehJOgUYj1wlQtd8AK`fcaMGQPUm)U^`RlafmSnX%&gWKCX!7$o@Q)vIpH)z)W@VKyt zS*||9+&L{^uqCU-S|&<`nU3i&`@f8_ud7y-Ip%ofQ;eD!b)QCVomL9R_emqZUo?C_ z$@nOyddv4e8wK&Sr5VOSo+kpE;nKxI`aE;wnHZ`6Zfqc4*uJJJ^z5F zTnAcK9t9TG)Q2hCw|X`CZ5sYQk?#=sE=Xa)L*xdrbpLacYjtTu`Tz~LQM9HUlbIa{ z#zfK(W#OT_zKQbx09}WRd)Z!;E}(e@L&+Ysxkyx$aWF-5GL`3S9?{8!3y&2YOM9mLDfMDSYa!&e7hs?d(Trc!*L07VJ!tTD7UUs3?QRUol`}<_RN=LhuLt!7yfsR9FWpDlhZ$ z_FSNK<7{saH;FaQT$x-^E`t6iOwE2l z$)w;jf2L!Tl1af6bL!7f@Px9mnTqeopP-x6)}}#`$vO$>1OP~a^r97xB+^i&(+97f zilZ{GeVbxG$AGC~tAy_>^ZT9}){wr)SG8f@L1jMH$X{@C!h9efWwpt&FH91q2~uEb-tv9FSNyta@?4UxU}NiKhk^)-Z1%DJ}( zWGemnJRs|EhS;LfhC-M8C6OycnnYSe2!Rl<7b9{}+eZ#8Wd6F|DIEPBC8z!dYgo2f zrThsuK(GC^ZR7jOu9to?RV793t=`-Hrj1!!n0$ z>pJ}jDP-4@OuJ|%fz0=lAda&mNf+m>90_?i7Nfm>E?}nkn<{~KwH!^qSx`FtLzTFz zF}HMMZRZ*`u41Nh<>8l>{;TpnA+px9*D6!&sc4C|Qs0e{^2Yd^(5O>J1WEyQpw>vL zeP0`}r1Q;bT`NUrMb59vobv|aijlh@$Y1q)t>&0rOV?MbcISGh+qv3lbQ=EyeaNIE delta 1688 zcmZ`(&u`;I6!wfA$8j7dY0{rbw<&>wciSKk(!v&1f;}vUB^-8pF^Aw}oYcihma(&_ z*;F}nuPafF-~>|l*uR0lfEyPsJ|S@egm(V~-kU&EAS9a4^UWL2dw%bG^Yd0xvAtZ* zRN(jIu-g8qaBf#ga(?S@+RwXrqA-P}!$QC47UkRsm-;2QL=?Yxrnu#M3d=C_slrTO zJyYFf;8|t?wkn{}V~F3?I?0N>wpC9Gk@whd#lsf!Vz2E*zC#nW6Gn+Pfs)ImwHC zefxtf7!tD^3|Omu(h3+a;)2FT{YRH7Uj@SJ2x|y)7kGN2_Wbxg@tuC`<; zocwg=hkop{2H$}-N9E{><0rV~nW@#~!~WGLa|iAJa-q+5ekhL8_2wpSxPh>RPy^VDNCUOn9kAxkxv6uk&&t}n-4E(Vg$?| z#6f(Lq`KUXjvT_@gkYe`mM4|b62BSCIwp$eMJn^z7M6i8Ay9-8f=pl;XUpPgrg4DP zNiwJqnZ#L*-a`A^2=iRXG@wX6h7sYZifW6OneE*wOs{R6Cv6W5_hHPG14-k1;zw&w z9GksEtoC^A!8J|S!eOrmIvJy3es4jAIaZDT zuflJGwF@KC0SrosM)++g?Zu4-z9+LG^+$OI7jPSBaaA;}&GLc_eIce+y>SP(&aLyb z?1SMEjF~$~i$VB4B<2(C6<8H7QybHFU=k}AWI~=3DXKRZna~!<>q3>624yP9Lz9w8 zF&*Y-`ihK-E4mdT^%)0xH4+jiaJMr-}`F`!%8yk(Cd6222m_EB(EiTopEeuj`HMK qdyW;69v9UsG2RDfqtHJfuq{HhS1mI8R}J|r>RO=;|BZrKF#iO298WF) diff --git a/resources/user.py b/resources/user.py index b579334..c71ae00 100644 --- a/resources/user.py +++ b/resources/user.py @@ -9,6 +9,9 @@ get_jwt_identity, get_jwt, ) +import traceback +from flask import make_response, render_template, request + from models.user import UserModel from schemas.user import UserSchema @@ -44,13 +47,23 @@ def post(cls): if UserModel.find_by_username(user.username): # if exists, then don't add return {"message": "An user with that username already exists."}, 400 + if UserModel.find_by_email(user.email): + # if exists, then don't add + return {"message": "An user with that email 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 + try: + user.save_to_database() + user.send_confirmation_email() + return { + "messege": "Account created successfully, an email with activation link has been sent to your email.", + }, 201 + except: + # print(err.messages) + traceback.print_exc() + return {"message": "Internal server error, failed to create user"} class User(Resource): @@ -76,8 +89,8 @@ def delete(cls, user_id: int): class UserLogin(Resource): @classmethod def post(cls): - # get data from parser - user_data = user_schema.load(request.get_json()) + # get data from user to login. Include email to optional field. + user_data = user_schema.load(request.get_json(), partial=("email",)) # find user in database user = UserModel.find_by_username(user_data.username) @@ -128,6 +141,14 @@ def get(cls, user_id: int): if user: user.activated = True user.save_to_database() - return {"message": "User activated."}, 200 + headers = {"Content-Type": "text/html"} + return make_response( + render_template( + "confirmation_page.html", + email=user.username, + ), + 200, + headers, + ) return {"meggase": "User not found"}, 404 diff --git a/template/confirmation_page.html b/templates/confirmation_page.html similarity index 100% rename from template/confirmation_page.html rename to templates/confirmation_page.html From ee467428552235d378f1f0e3fa61127e60e5604f Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Sun, 6 Mar 2022 22:46:02 +0530 Subject: [PATCH 38/42] minor changes --- libs/mailgun.py | 13 +++++++++---- resources/user.py | 5 +++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/libs/mailgun.py b/libs/mailgun.py index cf15396..5ec6ea6 100644 --- a/libs/mailgun.py +++ b/libs/mailgun.py @@ -3,6 +3,11 @@ from requests import Response, post +FAILED_LOAD_API_KEY = "Falied to load Mailgun API key." +FAILED_LOAD_DOMAIN_NAME = "Failde to load Mailgun domain." +FAILED_SEND_CONFIRMATION_MAIL = "Error in sending confirmation email, user registration failed." + + class MailgunException(Exception): def __init__(self, message: str): super().__init__(message) @@ -14,16 +19,16 @@ class Mailgun: MAILGUN_API_KEY = os.environ.get("MAILGUN_API_KEY") # can be None FROM_TITLE = "Stores RestAPI" # FROM_EMAIL = "Your Mailgun Email" - FROM_EMAIL = "subhadeepdoublecap@gmail.com" + FROM_EMAIL = "postmaster@sandboxb3ef8f2c0e20406cb3f834bc39735ab4.mailgun.org" # This method will interact with Mailgun API and return the response sent @classmethod def send_email(cls, email: List[str], subject: str, text: str, html: str) -> Response: if cls.MAILGUN_API_KEY is None: - raise MailgunException("Falied to load Mailgun API key.") + raise MailgunException(FAILED_LOAD_API_KEY) if cls.MAILGUN_DOMAIN is None: - raise MailgunException("Failde to load Mailgun domain.") + raise MailgunException(FAILED_LOAD_DOMAIN_NAME) response = post( f"https://api.mailgun.net/v3/{cls.MAILGUN_DOMAIN}/messages", @@ -41,6 +46,6 @@ def send_email(cls, email: List[str], subject: str, text: str, html: str) -> Res ) if response.status_code != 200: - raise MailgunException("Error in sending confirmation email, user registration failed.") + raise MailgunException(FAILED_SEND_CONFIRMATION_MAIL) return response diff --git a/resources/user.py b/resources/user.py index c71ae00..9dc76a1 100644 --- a/resources/user.py +++ b/resources/user.py @@ -16,6 +16,7 @@ from models.user import UserModel from schemas.user import UserSchema from blacklist import BLACKLIST +from libs.mailgun import MailgunException # extracted parser variable for global use, and made it private # _user_parser = reqparse.RequestParser() @@ -60,6 +61,10 @@ def post(cls): return { "messege": "Account created successfully, an email with activation link has been sent to your email.", }, 201 + # Delete user from database in case of any Mailgun error + except MailgunException as e: + user.delete_from_database() + return {"message", str(e)}, 500 except: # print(err.messages) traceback.print_exc() From 1f1b360390a16ad248e95c917a958d7346ba9977 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Tue, 8 Mar 2022 12:03:49 +0530 Subject: [PATCH 39/42] Added confirmation model and schema --- models/__pycache__/user.cpython-39.pyc | Bin 2323 -> 2691 bytes models/confirmation.py | 48 ++++++++++++++++++++++ models/user.py | 19 ++++++++- resources/__pycache__/user.cpython-39.pyc | Bin 4064 -> 4204 bytes schemas/confirmation.py | 11 +++++ 5 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 models/confirmation.py create mode 100644 schemas/confirmation.py diff --git a/models/__pycache__/user.cpython-39.pyc b/models/__pycache__/user.cpython-39.pyc index 223a9c568ee02e98446a8af998a56155e8440c1f..4dace0a2f35a79d6396288f2e99f5d111ca4a278 100644 GIT binary patch delta 1378 zcmZ`(&2Jk;6yKShU9TN`V>@=752vx(P`afFK?PKa0+os=q9PSUaPY;fcP7bN>s{;3 zMvy2~4haV&s0e#OqKD*y6!CX(=Dv3%xN}2%ycyS$6^R+mPxIcq-`jZ~vswM5>H3vQ z$-wc;gPZ-Yi)U_|49;G8yv7Y~N0p)5b&2uF;05lS8r%`3!R(3It@7fw;g!ywJ>K+4 zUVE6tJ0T4HG)&@;5-y@ZIh-CA3J-9}-_QQ+AD4NNm-q}Xe`|It!bPd_oN$-Vp0X3t zofTD4!_QZrQ(ohBt*fJM4s~;=Yw)JlHD1`wqprmlw5}=UwXa3KbjrFdzO1D-UqNZ% zg>gdjzSPUKg})7w@fcd&ZLNTE)8gBKho{b7KVvTdmsW4 zr@dgRDs6QYieS*g>z_&ix=b7YXO59N`)Gx!5YWh8HSbYa&4#pf7Z%Y69-BcVVHNkF zT#sP|cX@Ruj5*%EH{o}_H#dm#@%Eh#8h8|LD8@?)u5e2eZphcNAI#gDcJB?irEEJ0 z6S7uZAy+13G4#XeR<_A*WL>tj^a=7(V?eNb$X9gHNGYdS5Oct?ZyeAW2dvpa|C#+*D9R=$53wOz7L_ z2s{gxQJ?*6wX0X|-pVR=Yi=D+Wfj3NT|$Zdk7H$jS$zwdiuSp^QiCPjO=m6p&Tf*o zvY%}K17+kf-8AqT?sI$Gyl_`>t`e0&V{Fjdfn?a@e(LvqDc;SV6xPX7_O$Tfv5F

j4%eVti0aLK4;B^IWDrg%YYn*`UE|@`3?^NDKa40jYWFBXOGE5lGzio$-s!2(G zvY6|-O!Kz9TK3d=Kg!Ed*q0lsOLq6;ajoWt>bE5~rYk-EAz1pQXa`e?*Y(r=?%YS} Tek9&euU?iBsstx36P)IM?=MUg delta 1037 zcmZWnJ8u&~5Wc|KYm&UdhPP87*f ziiCm!Bx^gA0?L$BG)Vjc3L1)SpoG5w1D(e>SA-KqPvu7g5&k90jH<=>+!Efef3-k zm5@RMCsCcq4g7Xo%q7r;@w3zFDqk}@+9}%NC+32EidaL&KNlz;I(xJo1#RL)PR9x9 z68~m8O^4XyAO)-tN{_tyQxdMw$AnCtJxH_v22 z!g}h2;eNn~FmDHeM;%`zoNnaqIT0ly)%9R|K%*xC5n32AHYY`z>ql%sX1pi?OSmkd zDPcvzzoW#f+6_s{E+k-vGPR{*Esx6Z6;WPQQ9+wboK0nM?7L;>_~*pk?N-`zJ7H5k ojoqQoWC)=@2%!Gh?Z0Gl-?}vj$k3x3^7+L5&m;KMG)ilK05kyhz5oCK diff --git a/models/confirmation.py b/models/confirmation.py new file mode 100644 index 0000000..5bf94e5 --- /dev/null +++ b/models/confirmation.py @@ -0,0 +1,48 @@ +from uuid import uuid4 +from time import time + +from database import db + + +CONFIRMATION_EXPIRATION_DELTA = 1800 # 30minutes + + +class ConfirmationModel(db.Model): + __tablename__ = "confirmations" + + id = db.Column(db.String(50), primary_key=True) + expire_at = db.Column(db.Integer, nullable=False) + confirm_status = db.Column(db.Boolean, nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False) + user = db.relationship("UserModel") + + def __init__(self, user_id: int, **kwargs): + super().__init__(**kwargs) + self.user_id = user_id + self.id = uuid4().hex + self.expire_at = int(time()) + CONFIRMATION_EXPIRATION_DELTA # current time + 30 minutes + self.confirm_status = False + + @classmethod + def find_by_id(cls, _id: str) -> "ConfirmationModel": + 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() + + # To check id confirmation has expired + # When a method only returns the status/value of property, it must be given @property decorator + @property + def has_expired(self) -> bool: + return time() > self.expire_at + + # Forcefully expire the confirmation at current time + def force_to_expire(self) -> None: + if not self.has_expired: # has_expired() method is given a propeerty decorator, so can be called as an object + self.expire_at = int(time()) + self.save_to_database() diff --git a/models/user.py b/models/user.py index 60fddb9..cf3d739 100644 --- a/models/user.py +++ b/models/user.py @@ -4,6 +4,7 @@ from database import db from libs.mailgun import Mailgun +from models.confirmation import ConfirmationModel class UserModel(db.Model): @@ -15,7 +16,18 @@ class UserModel(db.Model): username = db.Column(db.String(80), nullable=False, unique=True) email = db.Column(db.String(50), nullable=False, unique=True) password = db.Column(db.String(80), nullable=False) - activated = db.Column(db.Boolean, default=False) + + confirmation = db.relationship( + "ConfirmationModel", + lazy="dynamic", + cascade="all, delete-orphan", + ) + # lazy=dynamic means that when we create a new UserModel, confirmation is not retrieved from the db, + # When we access the confirmation, then it it is retrieved from detabase + + @property + def most_recent_confirmation(self) -> "ConfirmationModel": + return self.confirmation.order_by(db.desc(ConfirmationModel.expire_at)).first() @classmethod def find_by_username(cls, username: str) -> "UserModel": @@ -33,7 +45,10 @@ def find_by_email(cls, email: str) -> "UserModel": def send_confirmation_email(self) -> Response: # http://127.0.0.1:5000 - is the 'url_root' # url_for("userconfirm") - this must mathch the name of user confirmation endpoint - link = request.url_root[:-1] + url_for("userconfirm", user_id=self.id) + link = request.url_root[:-1] + url_for( + "confirmation", + confirmation_id=self.most_recent_confirmation.id, + ) subject = "CONFIRM REGISTRATION" text = f"Click the link to confirm ragistration: {link}" diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc index 151214814236d3c2d4a39f099c2779f7470b1e05..eb492b40294045fa1f73b04486add3219726bdeb 100644 GIT binary patch delta 1488 zcmZ`(&u<$=6rP#gtk?E>vraa(n-Fz<~o2w^fC>a6o_%5?ou}8&`Ew1+U)c{pP)ydGozD z`%CE$&#vck83Mn5f0|jH9ow}h7^AzD&$c+>*|5+m){2w_?l!3%ze>2pb326R0&QEX zjRUv&Fz{i;PXKp#9(Z2y3E(4q6!@s(lfYd*27FBMDd5L=0eC_25;vBKU;GV|@u{4n zKf69noHy&uu+eQ_-dqcA$IVV#K1f}Zud;DlgE-852`u_XhOGX+PU7sHFDdzmQPP{a zOZTj=2qm1xxw~weZZK}Hvm3-9HwlNBkJ<9$|9ObQC${&7`!jgDNilAj%tH+1m63n3 zAGGE_RDP>HPaXNYw&Mv0woY6ggh3pvtcy--h1cWyYCQ@PEsBNg=!H=&(bmE!(YsL~ z5*q9iF)hE)-}7fN?HK^VY|Ua81sh`2q$blJF~%Mmx(#fGN_Xgu3I>Yl!%#+=2Y41N z(g943QIVIQXD8+U&@LU3FB)g&U2r5i@3wC5YoY{1R1iFb!HT$8qBVl}b$Q>o_4WvQ z=Mbs@z1+Kyz1Ke|uZrhj%-2K?{qqPX5Y8YxwW86V^N!p|m9L+|fM*aeW8niNTC*LC zvq;Y&45q6$b;Q%)xCR!P048PBG2xh};h3)cGxa?^F26QU(o6DzSvD{;F(QZ3AC~ak zVhn*H979kU7x1kpchc3fC`XdU(x}SyMOi@qO9+E(R3cb}75QtrQaO3pJIM6{I6eUD zmjiMY7vzuDoV=B3c&KTwxZGV^3!-Sf8-{nhu+wM+-0O5>5LN!2DNLyXEgfF|$>u$I zDqFsKgp2~E5&zpHi{RY^iwpo0F1Sv`BIvg_brI*O1FM=ck;VZGgZh@_b!*Z+LWFI( zWtFR!F?8S^h;RiQpMdqZ0})Iru7C`0(OK!`?r*(-6LG&!EyA&X!k6ajqwsh&hNwDyi6f1!g2UQsAnQ&QdfJ-@`mnAJFYYCWSq=@yg(LU delta 1333 zcmZuwOHUI~6rMXXOlSI>!a#YHQmKF=wLpnP#9+W6ffz9%CQ?jl;f@IM&^v8>M9~Bi zT@Vv;7j7icrE%epaAo4k<-|l?ni!XC+<49{ae%aw^Ywn`+hWM Date: Tue, 8 Mar 2022 19:05:56 +0530 Subject: [PATCH 40/42] Added confirmation resources --- .gitignore | 4 +- app.py | 5 +- database/data.db | Bin 32768 -> 0 bytes models/__pycache__/item.cpython-39.pyc | Bin 1525 -> 1525 bytes models/__pycache__/store.cpython-39.pyc | Bin 1410 -> 1410 bytes models/__pycache__/user.cpython-39.pyc | Bin 2691 -> 2691 bytes models/confirmation.py | 6 +- resources/__pycache__/user.cpython-39.pyc | Bin 4204 -> 3803 bytes resources/confirmation.py | 77 ++++++++++++++++++++++ resources/user.py | 46 ++----------- schemas/confirmation.py | 2 +- schemas/user.py | 8 ++- 12 files changed, 101 insertions(+), 47 deletions(-) delete mode 100644 database/data.db create mode 100644 resources/confirmation.py diff --git a/.gitignore b/.gitignore index e4677e2..178d4d0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ __pycache__ .vscode .idea /.env -database/data.db \ No newline at end of file +database/data.db +resources/__pycache__ +model/__pycache__ \ No newline at end of file diff --git a/app.py b/app.py index dde98a9..b2fa87b 100644 --- a/app.py +++ b/app.py @@ -5,7 +5,6 @@ from ma import ma from resources.user import ( - UserConfirm, UserRegister, User, UserLogin, @@ -14,6 +13,7 @@ ) from resources.item import Item, ItemList from resources.store import Store, StoreList +from resources.confirmation import Confirmation, ConfirmationByUser from blacklist import BLACKLIST from database import db @@ -125,7 +125,8 @@ def revoked_token_callback(self, callback): api.add_resource(ItemList, "/items") api.add_resource(StoreList, "/stores") api.add_resource(UserRegister, "/register") -api.add_resource(UserConfirm, "/activate/") +api.add_resource(Confirmation, "/user_confirm/") +api.add_resource(ConfirmationByUser, "/confirmation/user/") api.add_resource(User, "/user/") api.add_resource(UserLogin, "/login") api.add_resource(UserLogout, "/logout") diff --git a/database/data.db b/database/data.db deleted file mode 100644 index 6f30f8c989a1c5af9e1e410a63965a3629a38b5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI(!B5jr9Ki9Gu?-f+yqJa)@3=;T4m9y%q%03yoSl?m!f7dM)h3ML+Nl>47ylFQ z{w2ovC&2$;Vm$i#mH{JfB;1_erfXkcU)%Tl{Cat9x3^o5l2E+r_BwtdHjG<_X&ReC z7>1EmzXkOhO&L{~A8n|$d8s_9m^IcKAB&~mMt<} zn{r2b!fiB#yI-&06@|rPzY~g=j#qu^c;!d;?XjIFy{H|E?Rvve2V6QRPP)CYHQ|ge zyxMceI}p$0fhb3TonB?%t?lkhQBJR>E4CY+tnIjIqw?r%w(#V(^rTyrdqP*n`UC~r z&gZPhmT5%CLHHqld!&MD`TeA;w_7^IRz9&2X6=gBe}mW-`|X zS+MV9bJmlUNu3^EzEv5nbAz5a%w|=0yB~+WSTE+rI<2dwWHa4AHKSq2kB-js{qSnO z{WyN#?FHuzuHF!SJBi-;Nf?N&Mx!nrH%;y|i&2Nh?gHHjTctHKEv-7%*1BHJ+jG}9 zt>~udSYD6qi>hFjei&+ILI42-5I_I{1Q0*~0R#|00D*r);GUT?R^{!*xPSP@55n-| zybyHz>H%2WKiPbpKHXSrcRQ8!jRyr4ms$F0sF?`?1Q0*~0R#|0009ILKmY**W=CKt zvwHD-U^xCiGfHQ(6CjZwfB*srAbp8)df5N delta 20 acmey${gsKnP`xfpLv*5--;07zN|)c^nh delta 26 gcmZn`Z5HKD "ConfirmationModel": diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc index eb492b40294045fa1f73b04486add3219726bdeb..4ecf12a408437827dff5e3d47a1b77f5ba83d28b 100644 GIT binary patch delta 1242 zcmY*YOK;p%6!y7(jP3C_o<|-{n-@uhhPEL|2}QJ|kgftksx+xkjl{?^*J&K)rE3S8 z%0m{}HjC&Yu>zGP><~WzA+bbVng!~f1*`6$J;yUbVXXW4-tV4!<~#TE`D^iyIX5Vk zYz2R3yRWxCs+_x5*bC+S+Jh=|lrH!H3RifcGtu>Y@BHf zwC+DeXoF$*eM(qPhrSX zHeztcp&hPsBLZI7XP+nyMN^W}@sFSkRZt$UV2qo`Kr)f4r*OcyNpXhq0F@5auhho) zy^fjaNxY|JIx#x=^9^+hpdvrimZ2zn+Sc2?7YAR2&7|MtK@zlrI202oH)9d^r{l24 zo9zp=HN$QYb%ZN_*S2Q0Ol^1KOgq9enR78rmi5-g998`Wg2G(YVGiKX8JJ+>Fq!s} zG4`*axwu*ZgP9<|)7w=K<7eq4U7o2j5T3ke*5qZ!k{iZtsLP(Q2W9z-u~aW&NM`Rx zD6)0jjJT*0q4A^c;0IOA<3d~|SdgW{G`ufY3y1g0L|h}dj_|m2AH^@CVPO*THL-?V zT@{pAyh%V(#0J4@|G_fOGGlaRSN>VJ4V&^GcTT5T@5pU4bPb%5{@nOf{$#GaH%JO8 zJ2cUB^OT7J&aI)Es^TD34{dRf6p?Yb%C$2#)RI!Z($8S1r&_8@(^}H%AZFxsYZcy+ z4QtsTsiG`X>$4L#FI?1!VFZ%|c@a}InR_S6`5u6OT@=;M6a6smIDJ}g0ZCa6p%c2MQCyy-1WIV!f*=wXKDt$H=@vS+^n D^db;f delta 1529 zcmZ8h&u<$=6yBK~uf4W+?YN1ZrcL8EAz;$D5owC3QlNL2jhSoO|Gh_zMtH4~WYGXD(ck5C_D2n-Hq*YQA~<-rJcs?|W~4 z8ULebJC>Ey;Q8~XsoKoQfqjuua&YxQChlrprjqfpm8?f96Hm?tZJoEHBG*p)Wq zA+3jc-StD(^MzDXv08oxqniTIsI8}In!t!rbrdV8Z)486AuzmxE({={QzD1dhv{*3 z$5QiHlhfS1%%hsZ}Bm*rv8dA8uO^Qu0FR5 zhI|#|JL-Ny*fEeW{D=sten?EsH-h+pZYBm~GqFcy17uUK2T7PyT<2_`?&v`}`ikw7 z9X8MhOwE{=*)fsl)d%KF29=G0f_`lc0~SdO3u%RuH4qF4(kjUr$qVWR8! z{-ciMq3P6v))o{Fmj+rWv#KAv<(H!)GcK(vF|we7Cxn}K*7T-yjdx1X<{#N!05PP^K5nxc$)`8os;6=)^yiDna;u5U-x z+wKI#MyJ>2QAwBL&z0|6_|F(Z3L%YvWwKjhFLX>8-w+PmH2w>%BeCH3VfYsTG@8^I zF(99qReae$jKTajGy9q6l39j^yIV5kH2fo9M!*)AC4>d2C{4~IRp;eDHh_p`_=DX#;a>X)%*qeE8GetkoD&Z)`<_t31lPW_?V@`I?xDrvV>^UI#o za+|%j%;Ji8aY(#&q7If=(Q*j^IgTPaNrF1GXY<24Tv+%H87zAxJDeTMCbP+Z0X=X_ AfdBvi diff --git a/resources/confirmation.py b/resources/confirmation.py new file mode 100644 index 0000000..96358c1 --- /dev/null +++ b/resources/confirmation.py @@ -0,0 +1,77 @@ +from time import time +import traceback +from flask_restful import Resource +from flask import make_response, render_template + +from models.confirmation import ConfirmationModel +from models.user import UserModel +from schemas.confirmation import ConfirmationSchema +from libs.mailgun import MailgunException + +confirmation_schema = ConfirmationSchema() + + +class Confirmation(Resource): + # Returns confirmation HTML page + @classmethod + def get(cls, confirmation_id: str): + confirmation = ConfirmationModel.find_by_id(confirmation_id) + if not confirmation: + return {"message": "Confirmation referrence not found"}, 404 + + if confirmation.has_expired: + return {"message": "Confirmation link has expired."}, 400 + + if confirmation.confirmed: + return {"message": "Registration has already been confirmed."}, 400 + + confirmation.confirmed = True + confirmation.save_to_database() + + headers = {"Content-Type", "text/html"} + return make_response( + render_template("confirmation_page.html", email=confirmation.user.email), + 200, + headers, + ) + + +class ConfirmationByUser(Resource): + # Return confirmations for given user. Only for test purpose + @classmethod + def get(cls, user_id: int): + user = UserModel.find_by_id(user_id) + if not user: + return {"message": "User not found."}, 404 + + return { + "current_time": int(time()), + "confirmation": [confirmation_schema.dump(each) for each in user.confirmation.order_by(ConfirmationModel.expire_at)], + }, 200 + + # Resend confirmation email + @classmethod + def post(cls, user_id: int): + user = UserModel.find_by_id(user_id) + if not user: + return {"message": "User not found."}, 404 + + try: + confirmation = user.most_recent_confirmation + if confirmation: + if confirmation.confirmed: + return {"message": "Registration has already been confirmed"}, 400 + + confirmation.force_to_expire() + + new_confirmation = ConfirmationModel(user_id) + new_confirmation.save_to_database() + user.send_confirmation_email() + + return {"message": "Confirmation mail re-send successful"}, 201 + + except MailgunException as err: + return {"message": str(err)}, 500 + except: + traceback.print_exc() + return {"message": "Failed to resend confirmation mail."}, 500 diff --git a/resources/user.py b/resources/user.py index 9dc76a1..545c890 100644 --- a/resources/user.py +++ b/resources/user.py @@ -10,28 +10,14 @@ get_jwt, ) import traceback -from flask import make_response, render_template, request +from flask import request from models.user import UserModel from schemas.user import UserSchema from blacklist import BLACKLIST from libs.mailgun import MailgunException - -# 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" -# ) +from models.confirmation import ConfirmationModel user_schema = UserSchema() @@ -57,6 +43,8 @@ def post(cls): # flask_marshmallow already creates a user model, so we need not do it manually try: user.save_to_database() + confirmation = ConfirmationModel(user.id) + confirmation.save_to_database() user.send_confirmation_email() return { "messege": "Account created successfully, an email with activation link has been sent to your email.", @@ -68,6 +56,7 @@ def post(cls): except: # print(err.messages) traceback.print_exc() + user.delete_from_database() return {"message": "Internal server error, failed to create user"} @@ -103,8 +92,9 @@ def post(cls): # check password # this here is what authenticate() function used to do if user and safe_str_cmp(user.password, user_data.password): + confirmation = user.most_recent_confirmation # Check if user is activated - if user.activated: + if confirmation and confirmation.confirm_status: # 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) @@ -135,25 +125,3 @@ def post(cls): 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 - - -class UserConfirm(Resource): - @classmethod - def get(cls, user_id: int): - user = UserModel.find_by_id(user_id) - - # If user is found, activate their profile - if user: - user.activated = True - user.save_to_database() - headers = {"Content-Type": "text/html"} - return make_response( - render_template( - "confirmation_page.html", - email=user.username, - ), - 200, - headers, - ) - - return {"meggase": "User not found"}, 404 diff --git a/schemas/confirmation.py b/schemas/confirmation.py index 31a33f7..fa5e39a 100644 --- a/schemas/confirmation.py +++ b/schemas/confirmation.py @@ -7,5 +7,5 @@ class Meta: model = ConfirmationModel load_instance = True load_only = ("user",) - dump_only = ("id", "expire_at", "confirmed_status") + dump_only = ("id", "expired_at", "confirm_status") include_fk = True diff --git a/schemas/user.py b/schemas/user.py index c8d0a65..dfe871f 100644 --- a/schemas/user.py +++ b/schemas/user.py @@ -1,4 +1,5 @@ from ma import ma +from marshmallow import pre_dump from models.user import UserModel @@ -13,5 +14,10 @@ class Meta: load_only = ("password",) # makes 'password' field load_only dump_only = ( "id", - "activated", + "confirmation", ) # makes 'id' field dump_only. + + @pre_dump + def _pre_dump(self, user: UserModel, **kwargs): # Here user is the user that will be turned to json + user.confirmation = [user.most_recent_confirmation] + return user From 0238ae189b2b74cd04d891de78dbc8407c6aba77 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Tue, 8 Mar 2022 21:23:31 +0530 Subject: [PATCH 41/42] minor changes --- models/__pycache__/user.cpython-39.pyc | Bin 2691 -> 2634 bytes models/user.py | 13 +++++-------- resources/confirmation.py | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/models/__pycache__/user.cpython-39.pyc b/models/__pycache__/user.cpython-39.pyc index c671a4fba26f9f04201dabe70c7799e1f2a9c3f8..20344f8871127eb585aa2478150596cc91c893c6 100644 GIT binary patch delta 1021 zcmZuv&1)1f6yIbrGuf}QwAJH>q`ED^ks3-UkBO#x7Nor+G{7WDAT?SiL$YkG@caulpCp|^mz1QPGS2@4eM zxD*{>()aoYFf3}HmLqvmAVpF-Kw+5<5iDPWPx%1wqE_O*yaP|y6zU4f14khiJ?|;M z6^T|vsKV(kDzpFNHLW5lE z7}Q;NKY*%)UJp?D!V8uhouP1-vj$Joj3Xm3ZQz^%)7P*dXqz->62*)gy};!Y@|!nT z9Mtvv8n%zXnT2xEZ{ZQcMBJ4}{^b0)5gVZEqpFB#mo~DrL82^bMuINO?|xK2&NCSo z4bLN#QQfjj#`CX`Oz{L*k#BJGz8!*hJ!Rdbt^8Z&T+5H`oiR=)ZfE?w`G*Aq&_Lb5Wdm0X{4LO0 zQ^toiCF3K-L?3K4+)_1nusP;np_KHg}kID~BGy`UW8Jx=x85t*=;DtC)T z;t4l{v*S|uxPjcZpv2;)sGDhRciVP?Y9vW@6Ggq1wzp!Q*mAkA1(bW&MC@Jv`+bF1 Wl60JO8C?aY2s7alsK64?%lQLyLg0e{ delta 1017 zcmZuwy>HYo6hAvo;>+d2721P-Uj?)rNaa)IQw6C=2%$2x>XLy|?re@LPIA~eP#Gvv zSr|H09+=tK*ccHr4BaheL{SI+0T6ahEfpe9`Ni+~=bxY7b3PAl43(pD*(>O0dw!<* zdaFFXzN#HkAb=HO5P}$n5EvgaD@GxT?a$`=;vdH zmE#}`K;ce-xCFl{5KO(*LmO6DA;slF-PExnRTr5A=t7 zvIc7dW-AswUsG-?60L|(8NmlW)&zwJ6)jEs0&8GQ?!tFqP<};M!KB=_j*Sf)DX6R# z6MWL>jqSs@w9xIGd_}%Q_E;^a^5ceZa-Ws+$K_jet*<+E%1qTX)M1~S2M)^4+3!JB zezVu)S*I+SGk)38bcK7IHF%n4+%*yt2Br-ZHH4m^32D$c>M(8;>oy;ipPb3kLFFIL zR8Y~A1uFm$IKQzikGL&7WcY|<`NAEU9?{OhQ)*XLMD&z4va~^>ENVuAUY8%;Xm)R% zS#jR*96}k*D8FbT{|d=PJPdBiHg2xug?AQG){7J6-c2&vraV84a+WjRN!tBln}aCa zjf8lXa-w`rS(NFUA|7?RyxO1SX_xYBjZeslVs-t530j>b<1;3Qx&dV1oPkRQ<_!F; zO20uSR?=X83vpTlhRCXbscgvLCWYJm#jSc_$U#t18dy;{;4$ diff --git a/models/user.py b/models/user.py index cf3d739..e6049ab 100644 --- a/models/user.py +++ b/models/user.py @@ -1,5 +1,4 @@ -from typing import Dict, Union -from requests import Response, post +from requests import Response from flask import request, url_for from database import db @@ -14,13 +13,14 @@ class UserModel(db.Model): # table columns for users table id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), nullable=False, unique=True) - email = db.Column(db.String(50), nullable=False, unique=True) + email = db.Column(db.String(80), nullable=False, unique=True) password = db.Column(db.String(80), nullable=False) confirmation = db.relationship( "ConfirmationModel", lazy="dynamic", cascade="all, delete-orphan", + overlaps="user", ) # lazy=dynamic means that when we create a new UserModel, confirmation is not retrieved from the db, # When we access the confirmation, then it it is retrieved from detabase @@ -45,14 +45,11 @@ def find_by_email(cls, email: str) -> "UserModel": def send_confirmation_email(self) -> Response: # http://127.0.0.1:5000 - is the 'url_root' # url_for("userconfirm") - this must mathch the name of user confirmation endpoint - link = request.url_root[:-1] + url_for( - "confirmation", - confirmation_id=self.most_recent_confirmation.id, - ) + link = request.url_root[:-1] + url_for("confirmation", confirmation_id=self.most_recent_confirmation.id) subject = "CONFIRM REGISTRATION" text = f"Click the link to confirm ragistration: {link}" - html = f'Click the link to confirm ragistration: {link}' + html = f'Click the link to confirm registration: {link}' return Mailgun.send_email([self.email], subject, text, html) diff --git a/resources/confirmation.py b/resources/confirmation.py index 96358c1..02cb226 100644 --- a/resources/confirmation.py +++ b/resources/confirmation.py @@ -28,7 +28,7 @@ def get(cls, confirmation_id: str): confirmation.confirmed = True confirmation.save_to_database() - headers = {"Content-Type", "text/html"} + headers = {"Content-Type": "text/html"} return make_response( render_template("confirmation_page.html", email=confirmation.user.email), 200, From ee9865b0de7a7ce28ce602c00eea7f5b12288ae4 Mon Sep 17 00:00:00 2001 From: Subhadeep Date: Wed, 9 Mar 2022 11:39:49 +0530 Subject: [PATCH 42/42] minor changes --- models/__pycache__/user.cpython-39.pyc | Bin 2634 -> 2634 bytes resources/__pycache__/item.cpython-39.pyc | Bin 2805 -> 2805 bytes resources/__pycache__/store.cpython-39.pyc | Bin 1715 -> 1815 bytes resources/__pycache__/user.cpython-39.pyc | Bin 3803 -> 4161 bytes resources/confirmation.py | 17 +++++++---- resources/item.py | 1 + resources/store.py | 16 ++++++---- resources/user.py | 33 ++++++++++++++------- schemas/confirmation.py | 2 +- 9 files changed, 46 insertions(+), 23 deletions(-) diff --git a/models/__pycache__/user.cpython-39.pyc b/models/__pycache__/user.cpython-39.pyc index 20344f8871127eb585aa2478150596cc91c893c6..e795248064950a6e84c465695af72f2eb5fd5f2d 100644 GIT binary patch delta 19 ZcmX>la!Q0Nk(ZZ?0SGLMH*z^}0RSz#1N{I1 delta 19 ZcmX>la!Q0Nk(ZZ?0SF@EHgY*|0RS!x1P1^B diff --git a/resources/__pycache__/item.cpython-39.pyc b/resources/__pycache__/item.cpython-39.pyc index 624e8a71099d1e029765ed86ebf942253e6088f2..d2d434f99cf24ba168d59f716914c4e36d9b2f9d 100644 GIT binary patch delta 82 zcmew=`c;%Sk(ZZ?0SJ!Qt0&Fc$Q#1UD786>IiHbHW%DH#eMUyh%}i`;jEr8BIoYQ$ mif^9B?#alQGx-I_GRB0-{hUdR@smGs{$XcgW8`8M@BjdbCl$c} delta 82 zcmew=`c;%Sk(ZZ?0SNY8kxZJgkvD{yQF3zmAi-~j-b02TBA diff --git a/resources/__pycache__/store.cpython-39.pyc b/resources/__pycache__/store.cpython-39.pyc index d63844ac65feb400631eec3afe69441a45b869ee..d409f59cc063c1fbec6f29e9bbb33765379d126a 100644 GIT binary patch delta 801 zcmZ{h&1(}u7{+JzD_^^t)YRIBR%3(M_z@KKAc#=NrUvQ;b|bOtVc9wp38ab4PDMjR zp?WMBw&1lr_AKbtqaO6`{s+B#^Wr<3q6l@D{mnD;&OY<(yC01ArWqJU7Qr=neQ~pt znwX22PcGkR5Y_D!rV;e zd6IWgwJ_PcJvW+l`H<0a&}RX8QKrv(ArEVjE?-$56{e-Zke9o|?E$Hc%;^fDeab1R zO}>3x=1LC}xhsL8Vjq2htZ{0KhGFo8_VzGBRWlK&4SrX11jp2-4{8Ad}V`*PLKp1t?JW5y43Y zr7LM@1G(rL;_ALOmJTpclU$n3a&SuT9mHX2p?-oo`U?#Q?3H&`f zQ6d0K0cN5f%J+qtf43x6VQ?$@tS&Xqit0ImvcP$P#Qr4H3RG{x6Doi-mM{}5cC`>y zw0qYwc7xkHD0aEcZ079ZxOyM43Ugm0=5o42V-M;o^Pu+1y2fff6#3JSH}x9+ ze4Wu0GiyNL&h8$51Now|&81A9@NuK3a>2taSK%NZWvr9F!w5x|ZjsB22ehF5p%hi~ z#FnP>0Eq>ik;mBAKgiuzjDR#X(h5yr$S!_K#{Putk$r^GLzJDyuw%q9v!T-&5C=U% zJ#-Z*x@S$uK4z8DnA;UKm@;n8{3Jt>uooeJo=PP<>09t5T*F^n%R(+hF2cOuA8CTy z57OVj9)1TlvV{$s@Bxa3OrG%#m2a>_C7X%lqHegrzzG8jb2tU3!g`uZ)zrUee9Qb* z&v=I5AT;a9TTmttGCt&rv(EVBQJrTO)XZu`b=L~p|J8S9@6PKFR=nCYvlk7t<}Qx= z5fvb6dfEQjY8-DdZiNxJqFtwb?~EzW8dx&WHc-m{)hkcsvva_9U`Y$W#RLm;(~W$+ q<*cul0WHHtIlQF)NolT9xVN`+HX8Cf5ImA5Q6KxnEC+kEvGNBzsg;TV diff --git a/resources/__pycache__/user.cpython-39.pyc b/resources/__pycache__/user.cpython-39.pyc index 4ecf12a408437827dff5e3d47a1b77f5ba83d28b..8ce997af053afbe7a5788b1da1df8456ff45a554 100644 GIT binary patch delta 1996 zcmZuyO>7%Q6yBL#|Hgkw+%(Pa=BH^)QH}Aa}|2Fxf z9w__$UIl(X%r4%Z4L=G@&@u98>G2-(6~~yrIL-pa2^K6)vQTk~4Hl=_P*G>Y#TgbZ z&ZZQ0f{i>-*odL-tHn8xPqI;vM{Ri?-@)_pL zE0O7^=#D%~{#4tMn9Qq-YI`9jAE>9N+rf>d!S#AW=+#E6&Z2Fm+Wts>ubv}y`MbJwQZF;cVaX+Zr)HF!hF-a2RPIKl zpMJV0Tqa?(O7n`)dhQO{@*EJp%82kD>f6g5(i=EXh$4Jo_wSP~?a;mkECIAIRRn>r zRq6XmUeOd0+Wn3wU6m;9dEgA%93qA~>OQ$cnFD!F<%Ys;0_hVquQ0n`hNbWm!@V$0 zd2Z?nli-BBvJ6~^y)$dMw&6_WO~d?xC`!?mj7{U|&yW-IZ%{=>WKvs;S;HIoM6MLe zK9Yc#*HwwB1}OSwb} zR)7_+gA_|8gD%vb)}aT4shFB4T9yw2%ls0K_4FqgMI3wpI?H=_43k*_`<&VJLMgev zk&W|VBxtO)z4Junvmo$S5#|t%{6H5hb<+@UEApx<#g2S{B&*L7r-5I#Cwv*`&o<|; z;Yt?}CUFs;y?;EBNfZ)s{yOT9ZR6~he_sCMn#;a|gtG`s2ej$Mh4NClfU%DY;Rovg)0pES?P?-mOU{R+>*#152XlQ*nHv+qJXq&;AA*p0Q5PA= zMhHIz-b9Gy+9`8kmTT=`D}WCGT)Re#SB%TLyyZDbuE=|yxzKsw_Vf7&%XCow>&fg{ zjvbDt9|vR6Q)$$zCf_y~cOuyZU=3qx*VePibnZG-S|XQQ&soFi?0d0HI*wHrPh<<} zSSBBF+Ud4D_M^heqTEmVJT7$=;TpnGbRn=6g=2xL zZrTpuiTqbagH0@UYLI?B^!Fx-UJ)BrF~{4(TdQjQPA_E5EkHlzz>Ut#2w z(j|iQXonmScu%N(0bqPsco8jYsawLdWf7tM@=qZhwj6xsv0YDi>ihDqfw|O63X)@y*@1cK8Ixe$h1PTexG5zX;Ym1U zZ)yeS*`c;wrQCxGq($H^%AkKT42~T49pS$qR|Au3f3>fyI^|ynNx(P>JWKn+Q z|Cmh6VBqfFD9#>3uy5uYIEo|SrQsNLK7%lUfKLy{8^tkQJc)oSzU;J?du6-9S~X)8 lidJb}1n`mmH{o(af!O02R8c3@AQmrtTIh5r9GVDu{{x?@v+w`_ delta 1669 zcmZ`(&2Jk;6yKRy+q?eQI3EpdnxrNlBo3)b=@+7DpcE+ts&H`mG6HLxStpx1>u7dd z63Zq?<{F7;;Ksp`xRe9`0YY$s3sSGkfg=js5b6PO;JvYe0}8h0H~X9SX7=s-{dT|2 z{WRvdnM~4v-~P(Xz1Ic~oKgDNIGF$9&1Hk9dFEq-XFS$sr4ir`?*rba`FY@3-VeNA z^HJafd=U7c=3~Hz_!;15G#}^jqLCj~uaaNXkL;s8PJO~zg%%BIb`njpX&n-=#0fW# z*Bc8>(yV#p-KQ*F|sP1;?7NE#zN#^aEmsL;2JqG1{`v->46T8tQtDadhj4v~FL z)+QU2TbLI&-ZA(xBz?$=2G{cf9EE?G?eyc8-d1k=1UD3o1;~XbNUlbSEiVk+b+0jZ zD^OSHtnGh745KjvtW2j|L!a%;kKrI)KiJU^i3Vz-A&nTj9q^1a=%w*_fYOi${o*cJ6x?3#Kx z{v?@DmH4}4Onnz`j8DPM#Kga05@{A-M3oYqll`Y1)})8fc~m1GTNV9~^=IE3aQj?P zletQ*9`M4^r%?RS_YlgPA_Y^FJg&*-Vh|ThUfRd(6s)8<T`6R-sM)qf`0&F=NB@R2;&{e6gaqC?uJw*=v}>8W((S6A!-VG%ZXooUsQZTwy$d580x;MBOPd2EPLtFj zY35LiHXPLYPHkQNma^xkA?d^sJsqw>pa>&00Z<2V6*PGNCIfmK*K+{UXd9fIGg?HF z4sDV{0$&cw8T9ek^AX#Wez!4ai|P@5;`*_>&@Lha5HXkYJngz0wLekNKhLzSoXq6% zDW(!=6!E_=<`P5$7$G*-qLi5MHH0S_Q=etpuV2T#x|iDWRM@zHsbGJ6+oJSZT?o*; zj4G7u!26&qYxlk2v>JESx-)tC79P=4rPtDzAg}|YTMD$)A_CX)7u46z4jEO~`tC2{ rheY5@AW#8;O@ts1Xay(KV}5l*ONcGNbr)7hcy`E2rU%yVMs diff --git a/resources/confirmation.py b/resources/confirmation.py index 02cb226..be44960 100644 --- a/resources/confirmation.py +++ b/resources/confirmation.py @@ -9,6 +9,11 @@ from libs.mailgun import MailgunException confirmation_schema = ConfirmationSchema() +NOT_FOUND = "Confirmation referrence not found" +EXPIRED = "Confirmation link has expired." +ALREADY_CONFIRMED = "Registration has already been confirmed." +RESEND_FAIL = "Failed to resend confirmation mail." +RESEND_SUCCESSFUL = "Confirmation mail re-send successful" class Confirmation(Resource): @@ -17,13 +22,13 @@ class Confirmation(Resource): def get(cls, confirmation_id: str): confirmation = ConfirmationModel.find_by_id(confirmation_id) if not confirmation: - return {"message": "Confirmation referrence not found"}, 404 + return {"message": NOT_FOUND}, 404 if confirmation.has_expired: - return {"message": "Confirmation link has expired."}, 400 + return {"message": EXPIRED}, 400 if confirmation.confirmed: - return {"message": "Registration has already been confirmed."}, 400 + return {"message": ALREADY_CONFIRMED}, 400 confirmation.confirmed = True confirmation.save_to_database() @@ -60,7 +65,7 @@ def post(cls, user_id: int): confirmation = user.most_recent_confirmation if confirmation: if confirmation.confirmed: - return {"message": "Registration has already been confirmed"}, 400 + return {"message": ALREADY_CONFIRMED}, 400 confirmation.force_to_expire() @@ -68,10 +73,10 @@ def post(cls, user_id: int): new_confirmation.save_to_database() user.send_confirmation_email() - return {"message": "Confirmation mail re-send successful"}, 201 + return {"message": RESEND_SUCCESSFUL}, 201 except MailgunException as err: return {"message": str(err)}, 500 except: traceback.print_exc() - return {"message": "Failed to resend confirmation mail."}, 500 + return {"message": RESEND_FAIL}, 500 diff --git a/resources/item.py b/resources/item.py index 82306b3..14f1c48 100644 --- a/resources/item.py +++ b/resources/item.py @@ -15,6 +15,7 @@ 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) diff --git a/resources/store.py b/resources/store.py index 15b7a15..5474e42 100644 --- a/resources/store.py +++ b/resources/store.py @@ -4,6 +4,12 @@ from schemas.store import StoreSchema from models.store import StoreModel +NAME_ALREADY_EXISTS = "Store alrady exists." +ERROR_INSERTING = "An error occured while creating the store." +STORE_NOT_FOUND = "Store not found." +STORE_DELETED = "Store deleted." + + store_schema = StoreSchema() store_list_schema = StoreSchema(many=True) @@ -15,12 +21,12 @@ def get(cls, name: str): if store: return store_schema.dump(store), 200 - return {"message": "Store not found."}, 404 + 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 + return {"message": NAME_ALREADY_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 @@ -29,7 +35,7 @@ def post(cls, name: str): try: store.save_to_database() except: - return {"message": "An error occured while creating the store."}, 500 + return {"message": ERROR_INSERTING}, 500 return store_schema.dump(store), 201 @@ -38,9 +44,9 @@ 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_DELETED} - return {"message": "store don't exist"} + return {"message": STORE_NOT_FOUND} class StoreList(Resource): diff --git a/resources/user.py b/resources/user.py index 545c890..025ae9f 100644 --- a/resources/user.py +++ b/resources/user.py @@ -21,6 +21,16 @@ user_schema = UserSchema() +USER_ALREADY_EXISTS = "A user with that username already exists." +EMAIL_ALREADY_EXISTS = "A user with that email already exists." +USER_NOT_FOUND = "User not found." +USER_DELETED = "User deleted." +INVALID_CREDENTIALS = "Invalid credentials!" +USER_LOGGED_OUT = "User successfully logged out." +NOT_CONFIRMED_ERROR = "You have not confirmed registration, please check your email <{}>." +FAILED_TO_CREATE = "Internal server error. Failed to create user." +SUCCESS_REGISTER_MESSAGE = "Account created successfully, an email with an activation link has been sent to your email address, please check." + # New user registration class class UserRegister(Resource): @@ -33,10 +43,10 @@ def post(cls): # 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 + return {"message": USER_ALREADY_EXISTS}, 400 if UserModel.find_by_email(user.email): # if exists, then don't add - return {"message": "An user with that email already exists."}, 400 + return {"message": EMAIL_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. @@ -47,7 +57,7 @@ def post(cls): confirmation.save_to_database() user.send_confirmation_email() return { - "messege": "Account created successfully, an email with activation link has been sent to your email.", + "messege": SUCCESS_REGISTER_MESSAGE, }, 201 # Delete user from database in case of any Mailgun error except MailgunException as e: @@ -57,7 +67,7 @@ def post(cls): # print(err.messages) traceback.print_exc() user.delete_from_database() - return {"message": "Internal server error, failed to create user"} + return {"message": FAILED_TO_CREATE} class User(Resource): @@ -66,7 +76,7 @@ def get(cls, user_id: int): user = UserModel.find_by_id(user_id) if not user: - return {"message": "User not found."}, 404 + return {"message": USER_NOT_FOUND}, 404 return user_schema.dump(user), 200 @@ -74,10 +84,10 @@ def get(cls, user_id: int): def delete(cls, user_id: int): user = UserModel.find_by_id(user_id) if not user: - return {"message": "User not found."}, 404 + return {"message": USER_NOT_FOUND}, 404 user.delete_from_database() - return {"message": "User deleted."}, 200 + return {"message": USER_DELETED}, 200 class UserLogin(Resource): @@ -93,8 +103,9 @@ def post(cls): # this here is what authenticate() function used to do if user and safe_str_cmp(user.password, user_data.password): confirmation = user.most_recent_confirmation + print("user resource: ", confirmation.id) # Check if user is activated - if confirmation and confirmation.confirm_status: + if confirmation and confirmation.confirmed: # 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) @@ -102,9 +113,9 @@ def post(cls): return {"access_token": access_token, "refresh_token": refresh_token}, 200 # If user is not activated - return {"message": "You have not confirmed registration, please check your email."} + return {"message": NOT_CONFIRMED_ERROR} - return {"message": "Invalid credentials."}, 401 # Unauthorized + return {"message": INVALID_CREDENTIALS}, 401 # Unauthorized class UserLogout(Resource): @@ -114,7 +125,7 @@ class UserLogout(Resource): 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 + return {"message": USER_LOGGED_OUT.format(jti)}, 200 class TokenRefresh(Resource): diff --git a/schemas/confirmation.py b/schemas/confirmation.py index fa5e39a..a417a67 100644 --- a/schemas/confirmation.py +++ b/schemas/confirmation.py @@ -7,5 +7,5 @@ class Meta: model = ConfirmationModel load_instance = True load_only = ("user",) - dump_only = ("id", "expired_at", "confirm_status") + dump_only = ("id", "expired_at", "confirmed") include_fk = True