Compare commits

..

No commits in common. "master" and "lab3" have entirely different histories.
master ... lab3

4 changed files with 120 additions and 131 deletions

View File

@ -1,27 +1,20 @@
from flask import Flask, request, jsonify from flask import Flask, request, jsonify
from flask_jwt_extended import create_access_token, get_jwt_identity, jwt_required, JWTManager
import time import time
import json import json
import uuid import uuid
import datetime
import os
from hashlib import sha256
from marshmallow import Schema, fields from marshmallow import Schema, fields
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__) app = Flask(__name__)
app.config.from_pyfile('config.py', silent=True) app.config.from_pyfile('config.py', silent=True)
app.config["JWT_SECRET_KEY"] = os.getenv("JWT_SECRET_KEY")
db = SQLAlchemy(app) db = SQLAlchemy(app)
jwt = JWTManager(app)
class UserModel(db.Model): class UserModel(db.Model):
__tablename__ = "user" __tablename__ = "user"
uuid = db.Column(db.String(32), unique=True, primary_key=True, nullable=False) uuid = db.Column(db.String(32), unique=True, primary_key=True, nullable=False)
name = db.Column(db.String(64), nullable=False) name = db.Column(db.String(64), nullable=False)
password = db.Column(db.String(64), nullable=False)
bal_uuid = db.Column(db.String(32), db.ForeignKey('balance.uuid')) bal_uuid = db.Column(db.String(32), db.ForeignKey('balance.uuid'))
class CategoryModel(db.Model): class CategoryModel(db.Model):
@ -46,7 +39,6 @@ class BalanceModel(db.Model):
class UserSchema(Schema): class UserSchema(Schema):
uuid = fields.Str() uuid = fields.Str()
name = fields.Str() name = fields.Str()
password = fields.Str()
bal_uuid = fields.Str() bal_uuid = fields.Str()
class CategorySchema(Schema): class CategorySchema(Schema):
@ -57,7 +49,7 @@ class RecordSchema(Schema):
uuid = fields.Str() uuid = fields.Str()
user_uuid = fields.Str() user_uuid = fields.Str()
cat_uuid = fields.Str() cat_uuid = fields.Str()
date = fields.Date('iso') date = fields.Date()
amount = fields.Integer() amount = fields.Integer()
class BalanceSchema(Schema): class BalanceSchema(Schema):
@ -96,33 +88,39 @@ def ep_reset():
return {}, 200 return {}, 200
@app.route("/users", methods = ["GET"]) @app.route("/users", methods = ["GET"])
@jwt_required()
def ep_users_get(): def ep_users_get():
result = db.session.query(UserModel).all() result = db.session.query(UserModel).all()
return users_schema.dumps(result) return users_schema.dumps(result)
@app.route("/user/<user_id>", methods = ["GET"])
def ep_user_get(user_id):
result = db.session.query(UserModel).filter(UserModel.uuid == user_id).all()
if len(result) == 1:
return user_schema.dumps(result[0]), 200
else:
return {}, 404
@app.route("/user", methods = ["POST"]) @app.route("/user", methods = ["POST"])
def ep_user_post(): def ep_user_post():
name = request.json.get('name', None) body = request.json
password = request.json.get('password', None)
pass_hashed = sha256(password.encode("UTF-8")).digest().hex() if not body:
b = BalanceModel(uuid=uuid.uuid4().hex, value=0)
u_uuid = uuid.uuid4().hex
bal_uuid = b.uuid
try:
_ = user_schema.load({'uuid': u_uuid, 'name': name,
'password': pass_hashed, 'bal_uuid': bal_uuid})
except Exception as e:
return {}, 403 return {}, 403
u = UserModel(uuid=u_uuid, name=name, if 'uuid' in body:
password=pass_hashed, bal_uuid=bal_uuid) return {}, 403
at = create_access_token(identity = json.dumps({'uuid': u.uuid, 'bal_uuid': u.bal_uuid})) b = BalanceModel(uuid=uuid.uuid4().hex, value=0)
body.update({'uuid': uuid.uuid4().hex})
body.update({'bal_uuid': b.uuid})
try:
_ = user_schema.load(body)
except ValidationError as e:
return {}, 403
u = UserModel(**body)
try: try:
db.session.add(b) db.session.add(b)
@ -132,43 +130,29 @@ def ep_user_post():
db.session.rollback() db.session.rollback()
return {}, 403 return {}, 403
return jsonify(access_token = at), 200 return jsonify(user_schema.load(body)), 200
@app.route("/user", methods = ["GET"]) @app.route("/user/<user_id>", methods = ["DELETE"])
def ep_user_get(): def ep_user_delete(user_id):
name = request.json.get('user', None) try:
password = request.json.get('password', None) result = db.session.query(UserModel).filter(UserModel.uuid == user_id).all()
pass_hashed = sha256(password.encode("UTF-8")).digest().hex() except Exception as e:
return {}, 403
u = db.session.query(UserModel).filter(UserModel.name == name).one_or_none() if len(result) == 0:
if not u:
return {}, 404 return {}, 404
if u.password != pass_hashed:
return {"message": "Wrong password."}, 401
at = create_access_token(identity = json.dumps({'uuid': u.uuid, 'bal_uuid': u.bal_uuid}))
return jsonify(access_token = at), 200
@app.route("/user", methods = ["DELETE"])
@jwt_required()
def ep_user_delete():
current_user = json.loads(get_jwt_identity())
try: try:
db.session.query(UserModel).filter(UserModel.uuid == current_user['uuid']).delete() db.session.query(UserModel).filter(UserModel.uuid == user_id).delete()
db.session.query(BalanceModel).filter(BalanceModel.uuid == current_user['bal_uuid']).delete() db.session.query(BalanceModel).filter(BalanceModel.uuid == result[0].bal_uuid).delete()
db.session.commit() db.session.commit()
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
return {}, 403 return {}, 403
return {"message": "Success."}, 200 return user_schema.dumps(result[0]), 200
@app.route("/category", methods = ["GET"]) @app.route("/category", methods = ["GET"])
@jwt_required()
def ep_category_get(): def ep_category_get():
body = request.json body = request.json
@ -183,7 +167,6 @@ def ep_category_get():
return {}, 403 return {}, 403
@app.route("/category", methods = ["POST"]) @app.route("/category", methods = ["POST"])
@jwt_required()
def ep_category_post(): def ep_category_post():
body = request.json body = request.json
@ -212,7 +195,6 @@ def ep_category_post():
return jsonify(category_schema.load(body)), 200 return jsonify(category_schema.load(body)), 200
@app.route("/category", methods = ["DELETE"]) @app.route("/category", methods = ["DELETE"])
@jwt_required()
def ep_category_delete(): def ep_category_delete():
body = request.json body = request.json
@ -238,18 +220,15 @@ def ep_category_delete():
return category_schema.dumps(result[0]), 200 return category_schema.dumps(result[0]), 200
@app.route("/record/<record_id>", methods = ["GET"]) @app.route("/record/<record_id>", methods = ["GET"])
@jwt_required()
def ep_record_get(record_id): def ep_record_get(record_id):
current_user = json.loads(get_jwt_identity()) result = db.session.query(RecordModel).filter(RecordModel.uuid == record_id).all()
result = db.session.query(RecordModel).filter(RecordModel.uuid == record_id).one_or_none()
if result and result.user_uuid == current_user['uuid']: if len(result) == 1:
return user_schema.dumps(result), 200 return user_schema.dumps(result[0]), 200
else: else:
return {}, 403 return {}, 404
@app.route("/record", methods = ["GET"]) @app.route("/record", methods = ["GET"])
@jwt_required()
def ep_record_get_filtered(): def ep_record_get_filtered():
r = db.session.query(RecordModel) r = db.session.query(RecordModel)
@ -269,63 +248,68 @@ def ep_record_get_filtered():
r = r.filter(RecordModel.cat_uuid == request.json['cat_uuid']) r = r.filter(RecordModel.cat_uuid == request.json['cat_uuid'])
filtered = True filtered = True
if filtered: if filtered:
return records_schema.dumps(r.all()) return records_schema.dumps(r.all())
else: else:
return [], 403 return [], 403
@app.route("/record/<record_id>", methods = ["DELETE"]) @app.route("/record/<record_id>", methods = ["DELETE"])
@jwt_required()
def ep_record_del(record_id): def ep_record_del(record_id):
current_user = json.loads(get_jwt_identity())
try: try:
result = db.session.query(RecordModel).filter(RecordModel.uuid == record_id).one_or_none() result = db.session.query(RecordModel).filter(RecordModel.uuid == record_id).all()
except Exception as e: except Exception as e:
return {}, 403 return {}, 403
if result and result.user_uuid == current_user['uuid']: if len(result) == 0:
return {}, 404
db.session.query(RecordModel).filter(RecordModel.uuid == record_id).delete() db.session.query(RecordModel).filter(RecordModel.uuid == record_id).delete()
db.session.commit() db.session.commit()
else:
return {}, 401
return record_schema.dumps(result), 200 return record_schema.dumps(result[0]), 200
@app.route("/record", methods = ["POST"]) @app.route("/record", methods = ["POST"])
@jwt_required()
def ep_record_post(): def ep_record_post():
current_user = json.loads(get_jwt_identity()) body = request.json
amount = request.json.get('amount', None) if not body:
category = request.json.get('category', None)
if not all([amount, category]):
return {}, 403 return {}, 403
u_uuid = uuid.uuid4().hex if 'uuid' in body:
return {}, 403
r_time = time.strftime("%Y-%m-%d") body.update({'uuid': uuid.uuid4().hex})
# backward compatibility with lab2 DB model
if 'cat_id' in body:
body.update({'cat_uuid': body['cat_id']})
del body['cat_id']
if 'user_id' in body:
body.update({'user_uuid': body['user_id']})
del body['user_id']
try: try:
_ = record_schema.load({'amount': amount, "cat_uuid": category, "uuid": u_uuid, _ = record_schema.load(body)
'user_uuid': current_user['uuid'], 'date': r_time})
except Exception as e: except Exception as e:
return {}, 400 return {}, 403
r = RecordModel(amount=amount, cat_uuid=category, uuid=u_uuid, user_uuid=current_user['uuid'], date=r_time) r = RecordModel(**body)
b_id = db.session \
.query(UserModel) \
.filter(UserModel.uuid == body['user_uuid']) \
.all()[0] \
.bal_uuid
v = db.session \ v = db.session \
.query(BalanceModel) \ .query(BalanceModel) \
.filter(BalanceModel.uuid == current_user['bal_uuid']) \ .filter(BalanceModel.uuid == b_id) \
.one_or_none() \ .all()[0] \
.value .value
BalanceModel \ BalanceModel.metadata.tables.get("balance").update().where(BalanceModel.metadata.tables.get("balance").c.uuid == b_id).values(value = v-body['amount'])
.metadata.tables.get("balance") \
.update() \
.where(BalanceModel.metadata.tables.get("balance").c.uuid == current_user['bal_uuid']) \
.values(value = v - amount)
try: try:
db.session.add(r) db.session.add(r)
@ -334,60 +318,68 @@ def ep_record_post():
db.session.rollback() db.session.rollback()
return {}, 403 return {}, 403
return {'uuid': r.uuid}, 200
return jsonify(record_schema.load(body)), 200
@app.route("/balance_up", methods = ["POST"]) @app.route("/balance_up", methods = ["POST"])
@jwt_required()
def ep_balance_up(): def ep_balance_up():
current_user = json.loads(get_jwt_identity()) body = request.json
amount = request.json.get('amount', None) if 'user_id' in body:
body.update({'user_uuid': body['user_id']})
del body['user_id']
elif 'uuid' in body:
body.update({'user_uuid': body['uuid']})
del body['uuid']
if 'user_uuid' not in body:
return {}, 403
try: try:
b_id = db.session \
.query(UserModel) \
.filter(UserModel.uuid == body['user_uuid']) \
.all()[0] \
.bal_uuid
v = db.session \ v = db.session \
.query(BalanceModel) \ .query(BalanceModel) \
.filter(BalanceModel.uuid == current_user['bal_uuid']) \ .filter(BalanceModel.uuid == b_id) \
.one_or_none() \ .all()[0] \
.value .value
BalanceModel.metadata.tables.get("balance").update().where(BalanceModel.metadata.tables.get("balance").c.uuid == current_user['bal_uuid']).values(value = v + amount) BalanceModel.metadata.tables.get("balance").update().where(BalanceModel.metadata.tables.get("balance").c.uuid == b_id).values(value = v + body['amount'])
except Exception as e: except Exception as e:
return {}, 407 return {}, 403
return {}, 200 return {}, 200
@app.route("/balance", methods = ["GET"]) @app.route("/balance", methods = ["GET"])
@jwt_required()
def ep_balance_get(): def ep_balance_get():
current_user = json.loads(get_jwt_identity()) body = request.json
result = db.session.query(BalanceModel).filter(BalanceModel.uuid == current_user['bal_uuid']).one_or_none()
return balance_schema.dumps(result), 200 if 'user_id' in body:
body.update({'user_uuid': body['user_id']})
del body['user_id']
elif 'uuid' in body:
body.update({'user_uuid': body['uuid']})
del body['uuid']
@jwt.expired_token_loader if 'user_uuid' not in body:
def expired_token_callback(jwt_header, jwt_payload): return {}, 403
return (
jsonify({"message": "The token has expired.", "error": "token_expired"}),
401,
)
@jwt.invalid_token_loader try:
def invalid_token_callback(error): b_id = db.session \
return ( .query(UserModel) \
jsonify( .filter(UserModel.uuid == body['user_uuid']) \
{"message": "Signature verification failed.", "error": "invalid_token"} .all()[0] \
), .bal_uuid
401,
)
@jwt.unauthorized_loader result = db.session.query(BalanceModel).filter(BalanceModel.uuid == b_id).all()
def missing_token_callback(error): except Exception as e:
return ( return {}, 403
jsonify(
{ if len(result) == 1:
"description": "Request does not contain an access token.", return user_schema.dumps(result[0]), 200
"error": "authorization_required", else:
} return {}, 404
),
401,
)

View File

@ -14,7 +14,6 @@ services:
- ./app:/app/app - ./app:/app/app
env_file: env_file:
- db.env - db.env
- app.env
depends_on: depends_on:
- db - db

View File

@ -3,7 +3,6 @@ apispec==6.8.0
blinker==1.8.2 blinker==1.8.2
click==8.1.7 click==8.1.7
Flask==3.0.3 Flask==3.0.3
Flask-JWT-Extended==4.7.1
Flask-Migrate==4.0.7 Flask-Migrate==4.0.7
flask-smorest==0.45.0 flask-smorest==0.45.0
Flask-SQLAlchemy==3.1.1 Flask-SQLAlchemy==3.1.1
@ -15,7 +14,6 @@ MarkupSafe==2.1.5
marshmallow==3.23.2 marshmallow==3.23.2
packaging==24.2 packaging==24.2
psycopg2-binary==2.9.10 psycopg2-binary==2.9.10
PyJWT==2.10.1
SQLAlchemy==2.0.36 SQLAlchemy==2.0.36
typing_extensions==4.12.2 typing_extensions==4.12.2
webargs==8.6.0 webargs==8.6.0

View File

@ -19,7 +19,7 @@ fi
echo ' Done.' echo ' Done.'
echo -n 'Creating users...' echo -n 'Creating users...'
for i in $(seq 1); do for i in $(seq 9); do
curl -X POST -f -s \ curl -X POST -f -s \
--data "{\"name\": \"hi$i\"}" \ --data "{\"name\": \"hi$i\"}" \
--header "Content-Type: application/json" \ --header "Content-Type: application/json" \
@ -49,7 +49,7 @@ echo " $DELETION_UUID."
echo -n "Deleting user $DELETION_UUID..." echo -n "Deleting user $DELETION_UUID..."
curl -X DELETE -f -s \ curl -X DELETE -f -s \
http://$HOST:12402/user/$DELETION_UUID > /dev/null http://127.0.0.1:12402/user/$DELETION_UUID > /dev/null
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo 'Exiting due to previous error' echo 'Exiting due to previous error'