├── .vscode └── settings.json ├── sqlite.db ├── Pipfile ├── .gitignore ├── README.md ├── Pipfile.lock └── app.py /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/home/daniel/.local/share/virtualenvs/rest-api-auth-TX_bd5gL/bin/python" 3 | } -------------------------------------------------------------------------------- /sqlite.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielArturoAlejoAlvarez/Rest-Api-Python-3-7-7-Flask-SQLAlchemy-JWT-Authentication/HEAD/sqlite.db -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | flask = "*" 10 | flask-sqlalchemy = "*" 11 | flask-marshmallow = "*" 12 | marshmallow-sqlalchemy = "*" 13 | pyjwt = "*" 14 | 15 | [requires] 16 | python_version = "3.7" 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/flask,python 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=flask,python 4 | 5 | ### Flask ### 6 | instance/* 7 | !instance/.gitignore 8 | .webassets-cache 9 | 10 | ### Flask.Python Stack ### 11 | # Byte-compiled / optimized / DLL files 12 | __pycache__/ 13 | *.py[cod] 14 | *$py.class 15 | 16 | # C extensions 17 | *.so 18 | 19 | # Distribution / packaging 20 | .Python 21 | build/ 22 | develop-eggs/ 23 | dist/ 24 | downloads/ 25 | eggs/ 26 | .eggs/ 27 | lib/ 28 | lib64/ 29 | parts/ 30 | sdist/ 31 | var/ 32 | wheels/ 33 | pip-wheel-metadata/ 34 | share/python-wheels/ 35 | *.egg-info/ 36 | .installed.cfg 37 | *.egg 38 | MANIFEST 39 | 40 | # PyInstaller 41 | # Usually these files are written by a python script from a template 42 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 43 | *.manifest 44 | *.spec 45 | 46 | # Installer logs 47 | pip-log.txt 48 | pip-delete-this-directory.txt 49 | 50 | # Unit test / coverage reports 51 | htmlcov/ 52 | .tox/ 53 | .nox/ 54 | .coverage 55 | .coverage.* 56 | .cache 57 | nosetests.xml 58 | coverage.xml 59 | *.cover 60 | *.py,cover 61 | .hypothesis/ 62 | .pytest_cache/ 63 | pytestdebug.log 64 | 65 | # Translations 66 | *.mo 67 | *.pot 68 | 69 | # Django stuff: 70 | *.log 71 | local_settings.py 72 | db.sqlite3 73 | db.sqlite3-journal 74 | 75 | # Flask stuff: 76 | instance/ 77 | 78 | # Scrapy stuff: 79 | .scrapy 80 | 81 | # Sphinx documentation 82 | docs/_build/ 83 | doc/_build/ 84 | 85 | # PyBuilder 86 | target/ 87 | 88 | # Jupyter Notebook 89 | .ipynb_checkpoints 90 | 91 | # IPython 92 | profile_default/ 93 | ipython_config.py 94 | 95 | # pyenv 96 | .python-version 97 | 98 | # pipenv 99 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 100 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 101 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 102 | # install all needed dependencies. 103 | #Pipfile.lock 104 | 105 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 106 | __pypackages__/ 107 | 108 | # Celery stuff 109 | celerybeat-schedule 110 | celerybeat.pid 111 | 112 | # SageMath parsed files 113 | *.sage.py 114 | 115 | # Environments 116 | .env 117 | .venv 118 | env/ 119 | venv/ 120 | ENV/ 121 | env.bak/ 122 | venv.bak/ 123 | 124 | # Spyder project settings 125 | .spyderproject 126 | .spyproject 127 | 128 | # Rope project settings 129 | .ropeproject 130 | 131 | # mkdocs documentation 132 | /site 133 | 134 | # mypy 135 | .mypy_cache/ 136 | .dmypy.json 137 | dmypy.json 138 | 139 | # Pyre type checker 140 | .pyre/ 141 | 142 | # pytype static type analyzer 143 | .pytype/ 144 | 145 | ### Python ### 146 | # Byte-compiled / optimized / DLL files 147 | 148 | # C extensions 149 | 150 | # Distribution / packaging 151 | 152 | # PyInstaller 153 | # Usually these files are written by a python script from a template 154 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 155 | 156 | # Installer logs 157 | 158 | # Unit test / coverage reports 159 | 160 | # Translations 161 | 162 | # Django stuff: 163 | 164 | # Flask stuff: 165 | 166 | # Scrapy stuff: 167 | 168 | # Sphinx documentation 169 | 170 | # PyBuilder 171 | 172 | # Jupyter Notebook 173 | 174 | # IPython 175 | 176 | # pyenv 177 | 178 | # pipenv 179 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 180 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 181 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 182 | # install all needed dependencies. 183 | 184 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 185 | 186 | # Celery stuff 187 | 188 | # SageMath parsed files 189 | 190 | # Environments 191 | 192 | # Spyder project settings 193 | 194 | # Rope project settings 195 | 196 | # mkdocs documentation 197 | 198 | # mypy 199 | 200 | # Pyre type checker 201 | 202 | # pytype static type analyzer 203 | 204 | # End of https://www.toptal.com/developers/gitignore/api/flask,python -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rest-Api-Python-3-7-7-Flask-SQLAlchemy-JWT-Authentication 2 | 3 | ## Description 4 | 5 | This repository is a Software of Application with Python, Flask, SQLAlchemy (ORM), Marshmallow, JWT Authentication and SQLite. 6 | 7 | ## Installation 8 | 9 | Using Flask, SQLAlchemy, Marshmallow, SQLite3,etc preferably. 10 | 11 | ## DataBase 12 | 13 | Using SQLite3 preferably. 14 | 15 | ## Apps 16 | 17 | Using Postman, Insomnia, etc. 18 | 19 | ## Usage 20 | 21 | ```html 22 | $ git clone https://github.com/DanielArturoAlejoAlvarez/Rest-Api-Python-3-7-7-Flask-SQLAlchemy-JWT-Authentication.git 23 | [NAME APP] 24 | 25 | $ pipenv shell 26 | 27 | CREATE DATABASE AND MIGRATIONS 28 | 29 | $ python 30 | 31 | $ from app import db 32 | 33 | $ db.create_all() 34 | 35 | RUN SERVER 36 | 37 | $ python app.py 38 | ``` 39 | 40 | Follow the following steps and you're good to go! Important: 41 | 42 | ![alt text](https://camo.githubusercontent.com/02cd7c8f8e5b590231c2fa3d744e4d95e37ccb37/68747470733a2f2f63646e2e6f6d6973652e636f2f6173736574732f73637265656e73686f74732f6f6d6973652d666c61736b2d6578616d706c652f706970656e762d72756e2d666c61736b2d72756e2e676966) 43 | 44 | ## Coding 45 | 46 | ### Config 47 | 48 | ```python 49 | ... 50 | app.config['SECRET_KEY'] = 'dhdQq.S17Ex_YlWwwz9ncgglIQBfiAekHgZW6Rxb7H_4R7OmUxzV14fkvGo1ss0Z' 51 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \ 52 | os.path.join(basedir, 'sqlite.db') 53 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 54 | 55 | db = SQLAlchemy(app) 56 | ma = Marshmallow(app) 57 | ... 58 | ``` 59 | 60 | ### Models 61 | 62 | ```python 63 | ... 64 | class User(db.Model): 65 | id = db.Column(db.Integer, primary_key=True) 66 | public_id = db.Column(db.String(50), unique=True) 67 | name = db.Column(db.String(100), nullable=False) 68 | email = db.Column(db.String(128), unique=True, nullable=False) 69 | username = db.Column(db.String(100), unique=True, nullable=False) 70 | password = db.Column(db.String(128), nullable=False) 71 | avatar = db.Column(db.String(512)) 72 | admin = db.Column(db.Boolean) 73 | 74 | def __init__(self, public_id, name, email, username, password, avatar, admin): 75 | self.public_id = public_id 76 | self.name = name 77 | self.email = email 78 | self.username = username 79 | self.password = password 80 | self.avatar = avatar 81 | self.admin = admin 82 | 83 | def __repr__(self): 84 | return ' %r' % self.name 85 | 86 | 87 | class UserSchema(ma.Schema): 88 | class Meta: 89 | fields = ('id', 'public_id', 'name', 'email', 90 | 'username', 'password', 'avatar', 'admin') 91 | 92 | user_schema = UserSchema() 93 | users_schema = UserSchema(many=True) 94 | ... 95 | ``` 96 | 97 | ### Comtrollers 98 | 99 | ```python 100 | ... 101 | @app.route('/user', methods=['POST']) 102 | @token_required 103 | def add_user(current_user): 104 | if not current_user.admin: 105 | return jsonify({'msg': 'Cannot perform that function!'}) 106 | data = request.get_json() 107 | 108 | hashed_password = generate_password_hash(data['password'], method='sha256') 109 | public_id = str(uuid.uuid4()) 110 | name = data['name'] 111 | email = data['email'] 112 | username = data['username'] 113 | password = hashed_password 114 | avatar = data['avatar'] 115 | admin = False 116 | 117 | new_user = User(public_id, name, email, username, password, avatar, admin) 118 | 119 | db.session.add(new_user) 120 | db.session.commit() 121 | 122 | return user_schema.jsonify(new_user) 123 | ... 124 | ``` 125 | 126 | ### Authentication 127 | 128 | ```python 129 | ... 130 | @app.route('/login', methods=['POST']) 131 | def login(): 132 | auth = request.authorization 133 | if not auth or not auth.username or not auth.password: 134 | return make_response('Coult not verify', 401, {'WWW-Authenticate': 'Basic realm="Login required!"'}) 135 | 136 | user = User.query.filter_by(username=auth.username).first() 137 | if not user: 138 | return make_response('Coult not verify', 401, {'WWW-Authenticate': 'Basic realm="Login required!"'}) 139 | 140 | if check_password_hash(user.password, auth.password): 141 | token = jwt.encode({ 142 | 'public_id': user.public_id, 143 | 'exp': datetime.utcnow() + timedelta(minutes=30) 144 | }, app.config['SECRET_KEY']) 145 | return jsonify({ 146 | 'token': token.decode('UTF-8') 147 | }) 148 | return make_response('Coult not verify', 401, {'WWW-Authenticate': 'Basic realm="Login required!"'}) 149 | ... 150 | ``` 151 | 152 | ### Middlewares 153 | 154 | ```python 155 | ... 156 | def token_required(f): 157 | @wraps(f) 158 | def decorated(*args, **kwargs): 159 | token = None 160 | if 'x-access-token' in request.headers: 161 | token = request.headers['x-access-token'] 162 | 163 | if not token: 164 | return jsonify({'msg': 'Token is missing!'}), 401 165 | 166 | try: 167 | data = jwt.decode(token, app.config['SECRET_KEY']) 168 | current_user = User.query.filter_by( 169 | public_id=data['public_id']).first() 170 | 171 | except: 172 | return jsonify({'msg': 'Token is invalid!'}), 401 173 | 174 | return f(current_user, *args, **kwargs) 175 | 176 | return decorated 177 | ... 178 | 179 | ``` 180 | 181 | ## Contributing 182 | 183 | Bug reports and pull requests are welcome on GitHub at https://github.com/DanielArturoAlejoAlvarez/Rest-Api-Python-3-7-7-Flask-SQLAlchemy-JWT-Authentication. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 184 | 185 | ## License 186 | 187 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 188 | ```` 189 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "3186c0fa4a0332deafb673bc688e84939821dd2acad6dbe6efcbcd2afb6e2300" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "click": { 20 | "hashes": [ 21 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 22 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 23 | ], 24 | "version": "==7.1.2" 25 | }, 26 | "flask": { 27 | "hashes": [ 28 | "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", 29 | "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" 30 | ], 31 | "index": "pypi", 32 | "version": "==1.1.2" 33 | }, 34 | "flask-marshmallow": { 35 | "hashes": [ 36 | "sha256:1da1e6454a56a3e15107b987121729f152325bdef23f3df2f9b52bbd074af38e", 37 | "sha256:aefc1f1d96256c430a409f08241bab75ffe97e5d14ac5d1f000764e39bf4873a" 38 | ], 39 | "index": "pypi", 40 | "version": "==0.13.0" 41 | }, 42 | "flask-sqlalchemy": { 43 | "hashes": [ 44 | "sha256:05b31d2034dd3f2a685cbbae4cfc4ed906b2a733cff7964ada450fd5e462b84e", 45 | "sha256:bfc7150eaf809b1c283879302f04c42791136060c6eeb12c0c6674fb1291fae5" 46 | ], 47 | "index": "pypi", 48 | "version": "==2.4.4" 49 | }, 50 | "itsdangerous": { 51 | "hashes": [ 52 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", 53 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" 54 | ], 55 | "version": "==1.1.0" 56 | }, 57 | "jinja2": { 58 | "hashes": [ 59 | "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", 60 | "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" 61 | ], 62 | "version": "==2.11.2" 63 | }, 64 | "markupsafe": { 65 | "hashes": [ 66 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 67 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 68 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 69 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 70 | "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", 71 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 72 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 73 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 74 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 75 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 76 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 77 | "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", 78 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 79 | "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", 80 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 81 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 82 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 83 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 84 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 85 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 86 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 87 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 88 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 89 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 90 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 91 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 92 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 93 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 94 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 95 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 96 | "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", 97 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", 98 | "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" 99 | ], 100 | "version": "==1.1.1" 101 | }, 102 | "marshmallow": { 103 | "hashes": [ 104 | "sha256:67bf4cae9d3275b3fc74bd7ff88a7c98ee8c57c94b251a67b031dc293ecc4b76", 105 | "sha256:a2a5eefb4b75a3b43f05be1cca0b6686adf56af7465c3ca629e5ad8d1e1fe13d" 106 | ], 107 | "version": "==3.7.1" 108 | }, 109 | "marshmallow-sqlalchemy": { 110 | "hashes": [ 111 | "sha256:03a555b610bb307689b821b64e2416593ec21a85925c8c436c2cd08ebc6bb85e", 112 | "sha256:0ef59c8da8da2e18e808e3880158049e9d72f3031c84cc804b6c533a0eb668a9" 113 | ], 114 | "index": "pypi", 115 | "version": "==0.23.1" 116 | }, 117 | "pyjwt": { 118 | "hashes": [ 119 | "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e", 120 | "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96" 121 | ], 122 | "index": "pypi", 123 | "version": "==1.7.1" 124 | }, 125 | "six": { 126 | "hashes": [ 127 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 128 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 129 | ], 130 | "version": "==1.15.0" 131 | }, 132 | "sqlalchemy": { 133 | "hashes": [ 134 | "sha256:0942a3a0df3f6131580eddd26d99071b48cfe5aaf3eab2783076fbc5a1c1882e", 135 | "sha256:0ec575db1b54909750332c2e335c2bb11257883914a03bc5a3306a4488ecc772", 136 | "sha256:109581ccc8915001e8037b73c29590e78ce74be49ca0a3630a23831f9e3ed6c7", 137 | "sha256:16593fd748944726540cd20f7e83afec816c2ac96b082e26ae226e8f7e9688cf", 138 | "sha256:427273b08efc16a85aa2b39892817e78e3ed074fcb89b2a51c4979bae7e7ba98", 139 | "sha256:50c4ee32f0e1581828843267d8de35c3298e86ceecd5e9017dc45788be70a864", 140 | "sha256:512a85c3c8c3995cc91af3e90f38f460da5d3cade8dc3a229c8e0879037547c9", 141 | "sha256:57aa843b783179ab72e863512e14bdcba186641daf69e4e3a5761d705dcc35b1", 142 | "sha256:621f58cd921cd71ba6215c42954ffaa8a918eecd8c535d97befa1a8acad986dd", 143 | "sha256:6ac2558631a81b85e7fb7a44e5035347938b0a73f5fdc27a8566777d0792a6a4", 144 | "sha256:716754d0b5490bdcf68e1e4925edc02ac07209883314ad01a137642ddb2056f1", 145 | "sha256:736d41cfebedecc6f159fc4ac0769dc89528a989471dc1d378ba07d29a60ba1c", 146 | "sha256:8619b86cb68b185a778635be5b3e6018623c0761dde4df2f112896424aa27bd8", 147 | "sha256:87fad64529cde4f1914a5b9c383628e1a8f9e3930304c09cf22c2ae118a1280e", 148 | "sha256:89494df7f93b1836cae210c42864b292f9b31eeabca4810193761990dc689cce", 149 | "sha256:8cac7bb373a5f1423e28de3fd5fc8063b9c8ffe8957dc1b1a59cb90453db6da1", 150 | "sha256:8fd452dc3d49b3cc54483e033de6c006c304432e6f84b74d7b2c68afa2569ae5", 151 | "sha256:adad60eea2c4c2a1875eb6305a0b6e61a83163f8e233586a4d6a55221ef984fe", 152 | "sha256:c26f95e7609b821b5f08a72dab929baa0d685406b953efd7c89423a511d5c413", 153 | "sha256:cbe1324ef52ff26ccde2cb84b8593c8bf930069dfc06c1e616f1bfd4e47f48a3", 154 | "sha256:d05c4adae06bd0c7f696ae3ec8d993ed8ffcc4e11a76b1b35a5af8a099bd2284", 155 | "sha256:d98bc827a1293ae767c8f2f18be3bb5151fd37ddcd7da2a5f9581baeeb7a3fa1", 156 | "sha256:da2fb75f64792c1fc64c82313a00c728a7c301efe6a60b7a9fe35b16b4368ce7", 157 | "sha256:e4624d7edb2576cd72bb83636cd71c8ce544d8e272f308bd80885056972ca299", 158 | "sha256:e89e0d9e106f8a9180a4ca92a6adde60c58b1b0299e1b43bd5e0312f535fbf33", 159 | "sha256:f11c2437fb5f812d020932119ba02d9e2bc29a6eca01a055233a8b449e3e1e7d", 160 | "sha256:f57be5673e12763dd400fea568608700a63ce1c6bd5bdbc3cc3a2c5fdb045274", 161 | "sha256:fc728ece3d5c772c196fd338a99798e7efac7a04f9cb6416299a3638ee9a94cd" 162 | ], 163 | "version": "==1.3.18" 164 | }, 165 | "werkzeug": { 166 | "hashes": [ 167 | "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", 168 | "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" 169 | ], 170 | "version": "==1.0.1" 171 | } 172 | }, 173 | "develop": {} 174 | } 175 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, jsonify, make_response 2 | from flask_sqlalchemy import SQLAlchemy 3 | from flask_marshmallow import Marshmallow 4 | import os 5 | 6 | import uuid 7 | from werkzeug.security import check_password_hash, generate_password_hash 8 | from datetime import datetime, timedelta 9 | 10 | import jwt 11 | 12 | from functools import wraps 13 | 14 | # Init app 15 | app = Flask(__name__) 16 | basedir = os.path.abspath(os.path.dirname(__file__)) 17 | 18 | # Config Database 19 | app.config['SECRET_KEY'] = 'dhdQq.S17Ex_YlWwwz9ncgglIQBfiAekHgZW6Rxb7H_4R7OmUxzV14fkvGo1ss0Z' 20 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + \ 21 | os.path.join(basedir, 'sqlite.db') 22 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 23 | 24 | db = SQLAlchemy(app) 25 | ma = Marshmallow(app) 26 | 27 | 28 | # Class User 29 | class User(db.Model): 30 | id = db.Column(db.Integer, primary_key=True) 31 | public_id = db.Column(db.String(50), unique=True) 32 | name = db.Column(db.String(100), nullable=False) 33 | email = db.Column(db.String(128), unique=True, nullable=False) 34 | username = db.Column(db.String(100), unique=True, nullable=False) 35 | password = db.Column(db.String(128), nullable=False) 36 | avatar = db.Column(db.String(512)) 37 | admin = db.Column(db.Boolean) 38 | 39 | def __init__(self, public_id, name, email, username, password, avatar, admin): 40 | self.public_id = public_id 41 | self.name = name 42 | self.email = email 43 | self.username = username 44 | self.password = password 45 | self.avatar = avatar 46 | self.admin = admin 47 | 48 | def __repr__(self): 49 | return ' %r' % self.name 50 | 51 | # Class Product 52 | 53 | 54 | class Product(db.Model): 55 | id = db.Column(db.Integer, primary_key=True) 56 | category_id = db.Column(db.Integer, db.ForeignKey( 57 | 'category.id'), nullable=False) 58 | name = db.Column(db.String(100), unique=True, nullable=False) 59 | description = db.Column(db.String(200)) 60 | price = db.Column(db.Float, nullable=False) 61 | stock = db.Column(db.Integer, nullable=False) 62 | image_url = db.Column(db.String(512), nullable=False) 63 | date_reg = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) 64 | 65 | category = db.relationship( 66 | 'Category', backref=db.backref('products', lazy=True)) 67 | 68 | def __init__(self, category_id, name, description, price, stock, image_url): 69 | self.category_id = category_id 70 | self.name = name 71 | self.description = description 72 | self.price = price 73 | self.stock = stock 74 | self.image_url = image_url 75 | 76 | def __repr__(self): 77 | return '' % self.name 78 | 79 | 80 | # Class Category 81 | class Category(db.Model): 82 | id = db.Column(db.Integer, primary_key=True) 83 | name = db.Column(db.String(50), nullable=False) 84 | date_reg = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) 85 | 86 | def __init__(self, name): 87 | self.name = name 88 | 89 | def __repr__(self): 90 | return '' % self.name 91 | 92 | # Create Schemas 93 | 94 | 95 | class UserSchema(ma.Schema): 96 | class Meta: 97 | fields = ('id', 'public_id', 'name', 'email', 98 | 'username', 'password', 'avatar', 'admin') 99 | 100 | 101 | class ProductSchema(ma.Schema): 102 | class Meta: 103 | fields = ('id', 'category_id', 'name', 'description', 104 | 'price', 'stock', 'image_url') 105 | 106 | 107 | class CategorySchema(ma.Schema): 108 | class Meta: 109 | fields = ('id', 'name') 110 | 111 | 112 | # Init Schemas 113 | user_schema = UserSchema() 114 | users_schema = UserSchema(many=True) 115 | product_schema = ProductSchema() 116 | products_schema = ProductSchema(many=True) 117 | category_schema = CategorySchema() 118 | categories_schema = CategorySchema(many=True) 119 | 120 | # Routes 121 | 122 | # Authentication 123 | # Login User 124 | 125 | 126 | @app.route('/login', methods=['POST']) 127 | def login(): 128 | auth = request.authorization 129 | if not auth or not auth.username or not auth.password: 130 | return make_response('Coult not verify', 401, {'WWW-Authenticate': 'Basic realm="Login required!"'}) 131 | 132 | user = User.query.filter_by(username=auth.username).first() 133 | if not user: 134 | return make_response('Coult not verify', 401, {'WWW-Authenticate': 'Basic realm="Login required!"'}) 135 | 136 | if check_password_hash(user.password, auth.password): 137 | token = jwt.encode({ 138 | 'public_id': user.public_id, 139 | 'exp': datetime.utcnow() + timedelta(minutes=30) 140 | }, app.config['SECRET_KEY']) 141 | return jsonify({ 142 | 'token': token.decode('UTF-8') 143 | }) 144 | return make_response('Coult not verify', 401, {'WWW-Authenticate': 'Basic realm="Login required!"'}) 145 | 146 | 147 | # Token Authenticated 148 | def token_required(f): 149 | @wraps(f) 150 | def decorated(*args, **kwargs): 151 | token = None 152 | if 'x-access-token' in request.headers: 153 | token = request.headers['x-access-token'] 154 | 155 | if not token: 156 | return jsonify({'msg': 'Token is missing!'}), 401 157 | 158 | try: 159 | data = jwt.decode(token, app.config['SECRET_KEY']) 160 | current_user = User.query.filter_by( 161 | public_id=data['public_id']).first() 162 | 163 | except: 164 | return jsonify({'msg': 'Token is invalid!'}), 401 165 | 166 | return f(current_user, *args, **kwargs) 167 | 168 | return decorated 169 | 170 | 171 | # REST API 172 | # Create User 173 | @app.route('/user', methods=['POST']) 174 | @token_required 175 | def add_user(current_user): 176 | if not current_user.admin: 177 | return jsonify({'msg': 'Cannot perform that function!'}) 178 | data = request.get_json() 179 | 180 | hashed_password = generate_password_hash(data['password'], method='sha256') 181 | public_id = str(uuid.uuid4()) 182 | name = data['name'] 183 | email = data['email'] 184 | username = data['username'] 185 | password = hashed_password 186 | avatar = data['avatar'] 187 | admin = False 188 | 189 | new_user = User(public_id, name, email, username, password, avatar, admin) 190 | 191 | db.session.add(new_user) 192 | db.session.commit() 193 | 194 | return user_schema.jsonify(new_user) 195 | 196 | # List User 197 | 198 | 199 | @app.route('/users', methods=['GET']) 200 | @token_required 201 | def get_users(current_user): 202 | if not current_user.admin: 203 | return jsonify({'msg': 'Cannot perform that function!'}) 204 | all_users = User.query.all() 205 | result = users_schema.dump(all_users) 206 | return jsonify(result) 207 | 208 | # Single User 209 | 210 | 211 | @app.route('/user/', methods=['GET']) 212 | @token_required 213 | def get_user(current_user, public_id): 214 | if not current_user.admin: 215 | return jsonify({'msg': 'Cannot perform that function!'}) 216 | user = User.query.filter_by(public_id=public_id).first() 217 | if not user: 218 | return jsonify({"msg": "User no found!"}) 219 | return user_schema.jsonify(user) 220 | 221 | # Update User 222 | 223 | 224 | @app.route('/user/', methods=['PUT']) 225 | @token_required 226 | def update_user(current_user, public_id): 227 | if not current_user.admin: 228 | return jsonify({'msg': 'Cannot perform that function!'}) 229 | user = User.query.filter_by(public_id=public_id).first() 230 | if not user: 231 | return jsonify({"msg": "User no found!"}) 232 | 233 | data = request.get_json() 234 | 235 | hashed_password = generate_password_hash(data['password'], method='sha256') 236 | 237 | user.public_id = str(uuid.uuid4()) 238 | user.name = data['name'] 239 | user.email = data['email'] 240 | user.username = data['username'] 241 | user.password = hashed_password 242 | user.avatar = data['avatar'] 243 | user.admin = False 244 | 245 | db.session.commit() 246 | 247 | return user_schema.jsonify(user) 248 | 249 | 250 | @app.route('/user/', methods=['DELETE']) 251 | @token_required 252 | def delete_user(current_user, id): 253 | if not current_user.admin: 254 | return jsonify({'msg': 'Cannot perform that function!'}) 255 | user = User.query.filter_by(public_id=public_id).first() 256 | if not user: 257 | return jsonify({"msg": "User no found!"}) 258 | db.session.delete(user) 259 | db.session.commit() 260 | return user_schema.jsonify(user) 261 | 262 | 263 | # Create Category 264 | @app.route('/category', methods=['POST']) 265 | @token_required 266 | def add_category(current_user): 267 | if not current_user.admin: 268 | return jsonify({'msg': 'Cannot perform that function!'}) 269 | name = request.json['name'] 270 | 271 | new_category = Category(name) 272 | 273 | db.session.add(new_category) 274 | db.session.commit() 275 | 276 | return category_schema.jsonify(new_category) 277 | 278 | # List Category 279 | 280 | 281 | @app.route('/categories', methods=['GET']) 282 | @token_required 283 | def get_categories(current_user): 284 | if not current_user.admin: 285 | return jsonify({'msg': 'Cannot perform that function!'}) 286 | all_categories = Category.query.all() 287 | result = categories_schema.dump(all_categories) 288 | return jsonify(result) 289 | 290 | # Single Category 291 | 292 | 293 | @app.route('/category/', methods=['GET']) 294 | @token_required 295 | def get_category(current_user, id): 296 | if not current_user.admin: 297 | return jsonify({'msg': 'Cannot perform that function!'}) 298 | category = Category.query.get(id) 299 | return category_schema.jsonify(category) 300 | 301 | # Update Category 302 | 303 | 304 | @app.route('/category/', methods=['PUT']) 305 | @token_required 306 | def update_category(current_user, id): 307 | if not current_user.admin: 308 | return jsonify({'msg': 'Cannot perform that function!'}) 309 | category = Category.query.get(id) 310 | 311 | name = request.json['name'] 312 | 313 | category.name = name 314 | 315 | db.session.commit() 316 | 317 | return category_schema.jsonify(category) 318 | 319 | # Delete Category 320 | 321 | 322 | @app.route('/category/', methods=['DELETE']) 323 | @token_required 324 | def delete_category(current_user, id): 325 | if not current_user.admin: 326 | return jsonify({'msg': 'Cannot perform that function!'}) 327 | category = Category.query.get(id) 328 | db.session.delete(category) 329 | db.session.commit() 330 | return category_schema.jsonify(category) 331 | 332 | # Create Product 333 | 334 | 335 | @app.route('/product', methods=['POST']) 336 | @token_required 337 | def add_product(current_user): 338 | if not current_user.admin: 339 | return jsonify({'msg': 'Cannot perform that function!'}) 340 | category_id = request.json['category_id'] 341 | name = request.json['name'] 342 | description = request.json['description'] 343 | price = request.json['price'] 344 | stock = request.json['stock'] 345 | image_url = request.json['image_url'] 346 | 347 | new_product = Product(category_id, name, description, 348 | price, stock, image_url) 349 | 350 | db.session.add(new_product) 351 | db.session.commit() 352 | 353 | return product_schema.jsonify(new_product) 354 | 355 | # List Products 356 | 357 | 358 | @app.route('/products', methods=['GET']) 359 | @token_required 360 | def get_products(current_user): 361 | if not current_user.admin: 362 | return jsonify({'msg': 'Cannot perform that function!'}) 363 | all_products = Product.query.all() 364 | result = products_schema.dump(all_products) 365 | return jsonify(result) 366 | 367 | # Single Product 368 | 369 | 370 | @app.route('/product/', methods=['GET']) 371 | @token_required 372 | def get_product(current_user, id): 373 | if not current_user.admin: 374 | return jsonify({'msg': 'Cannot perform that function!'}) 375 | product = Product.query.get(id) 376 | return product_schema.jsonify(product) 377 | 378 | # Update Product 379 | 380 | 381 | @app.route('/product/', methods=['PUT']) 382 | @token_required 383 | def update_product(current_user, id): 384 | if not current_user.admin: 385 | return jsonify({'msg': 'Cannot perform that function!'}) 386 | product = Product.query.get(id) 387 | 388 | category_id = request.json['category_id'] 389 | name = request.json['name'] 390 | description = request.json['description'] 391 | price = request.json['price'] 392 | stock = request.json['stock'] 393 | image_url = request.json['image_url'] 394 | 395 | product.category_id = category_id 396 | product.name = name 397 | product.description = description 398 | product.price = price 399 | product.stock = stock 400 | product.image_url = image_url 401 | 402 | db.session.commit() 403 | 404 | return product_schema.jsonify(product) 405 | 406 | # Delete Product 407 | 408 | 409 | @app.route('/product/', methods=['DELETE']) 410 | @token_required 411 | def delete_product(current_user, id): 412 | if not current_user.admin: 413 | return jsonify({'msg': 'Cannot perform that function!'}) 414 | product = Product.query.get(id) 415 | db.session.delete(product) 416 | db.session.commit() 417 | return product_schema.jsonify(product) 418 | 419 | 420 | @app.route('/', methods=["GET"]) 421 | def get(): 422 | return jsonify({ 423 | "ok": True, 424 | "msg": "REST API with FLASK" 425 | }) 426 | 427 | # Run server 428 | if __name__ == "__main__": 429 | app.run(debug=True) 430 | 431 | --------------------------------------------------------------------------------