from flask import Flask, request, jsonify from flask_jwt_extended import create_access_token, get_jwt_identity, jwt_required, JWTManager import time import json import uuid import datetime import os from hashlib import sha256 from marshmallow import Schema, fields from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config.from_pyfile('config.py', silent=True) app.config["JWT_SECRET_KEY"] = os.getenv("JWT_SECRET_KEY") db = SQLAlchemy(app) jwt = JWTManager(app) class UserModel(db.Model): __tablename__ = "user" uuid = db.Column(db.String(32), unique=True, primary_key=True, 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')) class CategoryModel(db.Model): __tablename__ = "category" uuid = db.Column(db.String(32), unique=True, primary_key=True, nullable=False) name = db.Column(db.String(64), nullable=False) class RecordModel(db.Model): __tablename__ = "record" uuid = db.Column(db.String(32), primary_key=True, nullable=False) user_uuid = db.Column(db.String(32), db.ForeignKey('user.uuid')) cat_uuid = db.Column(db.String(32), db.ForeignKey('category.uuid')) date = db.Column(db.Date) amount = db.Column(db.Integer) class BalanceModel(db.Model): __tablename__ = "balance" uuid = db.Column(db.String(32), primary_key=True, nullable=False) value = db.Column(db.Integer, nullable=False) class UserSchema(Schema): uuid = fields.Str() name = fields.Str() password = fields.Str() bal_uuid = fields.Str() class CategorySchema(Schema): uuid = fields.Str() name = fields.Str() class RecordSchema(Schema): uuid = fields.Str() user_uuid = fields.Str() cat_uuid = fields.Str() date = fields.Date('iso') amount = fields.Integer() class BalanceSchema(Schema): uuid = fields.Str() value = fields.Integer() user_schema = UserSchema() users_schema = UserSchema(many = True) category_schema = CategorySchema() categories_schema = CategorySchema(many = True) record_schema = RecordSchema() records_schema = RecordSchema(many = True) balance_schema = BalanceSchema() # "migration" with app.app_context(): db.create_all() @app.route("/healthcheck") def ep_healthcheck(): return { "date": time.strftime('%Y.%m.%d %H:%M:%S'), "status": "OK" } @app.route("/reset_users_because_postman_is_dumb_like_that") def ep_reset(): db.session.query(RecordModel).delete() db.session.query(CategoryModel).delete() db.session.query(UserModel).delete() db.session.query(BalanceModel).delete() db.session.commit() return {}, 200 @app.route("/users", methods = ["GET"]) @jwt_required() def ep_users_get(): result = db.session.query(UserModel).all() return users_schema.dumps(result) @app.route("/user", methods = ["POST"]) def ep_user_post(): name = request.json.get('name', None) password = request.json.get('password', None) pass_hashed = sha256(password.encode("UTF-8")).digest().hex() 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 u = UserModel(uuid=u_uuid, name=name, password=pass_hashed, bal_uuid=bal_uuid) at = create_access_token(identity = json.dumps({'uuid': u.uuid, 'bal_uuid': u.bal_uuid})) try: db.session.add(b) db.session.add(u) db.session.commit() except Exception as e: db.session.rollback() return {}, 403 return jsonify(access_token = at), 200 @app.route("/user", methods = ["GET"]) def ep_user_get(): name = request.json.get('user', None) password = request.json.get('password', None) pass_hashed = sha256(password.encode("UTF-8")).digest().hex() u = db.session.query(UserModel).filter(UserModel.name == name).one_or_none() if not u: 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: db.session.query(UserModel).filter(UserModel.uuid == current_user['uuid']).delete() db.session.query(BalanceModel).filter(BalanceModel.uuid == current_user['bal_uuid']).delete() db.session.commit() except Exception as e: db.session.rollback() return {}, 403 return {"message": "Success."}, 200 @app.route("/category", methods = ["GET"]) @jwt_required() def ep_category_get(): body = request.json if 'uuid' in body: result = db.session.query(CategoryModel).filter(CategoryModel.uuid == body['uuid']).all() if len(result) == 1: return user_schema.dumps(result[0]), 200 else: return {}, 404 else: return {}, 403 @app.route("/category", methods = ["POST"]) @jwt_required() def ep_category_post(): body = request.json if not body: return {}, 403 if 'uuid' in body: return {}, 403 body.update({'uuid': uuid.uuid4().hex}) try: _ = category_schema.load(body) except ValidationError as e: return {}, 403 c = CategoryModel(**body) try: db.session.add(c) db.session.commit() except Exception as e: db.session.rollback() return {}, 403 return jsonify(category_schema.load(body)), 200 @app.route("/category", methods = ["DELETE"]) @jwt_required() def ep_category_delete(): body = request.json if 'uuid' not in body: return {}, 403 cat_id = body['uuid'] try: result = db.session.query(CategoryModel).filter(CategoryModel.uuid == cat_id).all() except Exception as e: return {}, 403 if len(result) == 0: return {}, 404 try: db.session.query(CategoryModel).filter(CategoryModel.uuid == cat_id).delete() db.session.commit() except Exception as e: return {}, 403 return category_schema.dumps(result[0]), 200 @app.route("/record/", methods = ["GET"]) @jwt_required() def ep_record_get(record_id): current_user = json.loads(get_jwt_identity()) result = db.session.query(RecordModel).filter(RecordModel.uuid == record_id).one_or_none() if result and result.user_uuid == current_user['uuid']: return user_schema.dumps(result), 200 else: return {}, 403 @app.route("/record", methods = ["GET"]) @jwt_required() def ep_record_get_filtered(): r = db.session.query(RecordModel) filtered = False if 'user_id' in request.json: r = r.filter(RecordModel.user_uuid == request.json['user_id']) filtered = True elif 'user_uuid' in request.json: r = r.filter(RecordModel.user_uuid == request.json['user_uuid']) filtered = True if 'cat_id' in request.json: r = r.filter(RecordModel.cat_uuid == request.json['cat_id']) filtered = True if 'cat_uuid' in request.json: r = r.filter(RecordModel.cat_uuid == request.json['cat_uuid']) filtered = True if filtered: return records_schema.dumps(r.all()) else: return [], 403 @app.route("/record/", methods = ["DELETE"]) @jwt_required() def ep_record_del(record_id): current_user = json.loads(get_jwt_identity()) try: result = db.session.query(RecordModel).filter(RecordModel.uuid == record_id).one_or_none() except Exception as e: return {}, 403 if result and result.user_uuid == current_user['uuid']: db.session.query(RecordModel).filter(RecordModel.uuid == record_id).delete() db.session.commit() else: return {}, 401 return record_schema.dumps(result), 200 @app.route("/record", methods = ["POST"]) @jwt_required() def ep_record_post(): current_user = json.loads(get_jwt_identity()) amount = request.json.get('amount', None) category = request.json.get('category', None) if not all([amount, category]): return {}, 403 u_uuid = uuid.uuid4().hex r_time = time.strftime("%Y-%m-%d") try: _ = record_schema.load({'amount': amount, "cat_uuid": category, "uuid": u_uuid, 'user_uuid': current_user['uuid'], 'date': r_time}) except Exception as e: return {}, 400 r = RecordModel(amount=amount, cat_uuid=category, uuid=u_uuid, user_uuid=current_user['uuid'], date=r_time) v = db.session \ .query(BalanceModel) \ .filter(BalanceModel.uuid == current_user['bal_uuid']) \ .one_or_none() \ .value BalanceModel \ .metadata.tables.get("balance") \ .update() \ .where(BalanceModel.metadata.tables.get("balance").c.uuid == current_user['bal_uuid']) \ .values(value = v - amount) try: db.session.add(r) db.session.commit() except Exception as e: db.session.rollback() return {}, 403 return {'uuid': r.uuid}, 200 @app.route("/balance_up", methods = ["POST"]) @jwt_required() def ep_balance_up(): current_user = json.loads(get_jwt_identity()) amount = request.json.get('amount', None) try: v = db.session \ .query(BalanceModel) \ .filter(BalanceModel.uuid == current_user['bal_uuid']) \ .one_or_none() \ .value BalanceModel.metadata.tables.get("balance").update().where(BalanceModel.metadata.tables.get("balance").c.uuid == current_user['bal_uuid']).values(value = v + amount) except Exception as e: return {}, 407 return {}, 200 @app.route("/balance", methods = ["GET"]) @jwt_required() def ep_balance_get(): current_user = json.loads(get_jwt_identity()) result = db.session.query(BalanceModel).filter(BalanceModel.uuid == current_user['bal_uuid']).one_or_none() return balance_schema.dumps(result), 200 @jwt.expired_token_loader def expired_token_callback(jwt_header, jwt_payload): return ( jsonify({"message": "The token has expired.", "error": "token_expired"}), 401, ) @jwt.invalid_token_loader def invalid_token_callback(error): return ( jsonify( {"message": "Signature verification failed.", "error": "invalid_token"} ), 401, ) @jwt.unauthorized_loader def missing_token_callback(error): return ( jsonify( { "description": "Request does not contain an access token.", "error": "authorization_required", } ), 401, )