diff --git a/Controllers/Login.py b/Controllers/Login.py new file mode 100644 index 0000000..2e775e6 --- /dev/null +++ b/Controllers/Login.py @@ -0,0 +1,41 @@ +import datetime +from flask import request, jsonify +from flask_restful import Resource, abort +from Models.User import User +from app import app, jwt +from flask_jwt_extended import create_access_token, get_jwt_identity, jwt_required, JWTManager, current_user, create_refresh_token + +# Register a callback function that takes whatever object is passed in as the +# identity when creating JWTs and converts it to a JSON serializable format. +@jwt.user_identity_loader +def user_identity_lookup(user): + return user.id + + +# Register a callback function that loads a user from your database whenever +# a protected route is accessed. This should return any python object on a +# successful lookup, or None if the lookup failed for any reason (for example +# if the user has been deleted from the database). +@jwt.user_lookup_loader +def user_lookup_callback(_jwt_header, jwt_data): + identity = jwt_data["sub"] + return User.query.filter_by(id=identity).one_or_none() + +class Login(Resource): + def get(self, ): + user = User.query.filter_by(email=request.json['email']).first_or_404() + + if not user or not user.check_password(request.json['password']): + abort(401, message='Unauthorized') + access_token = create_access_token(identity=user) + refresh_token = create_refresh_token(identity=user) + + return jsonify(access_token=access_token, refresh_token=refresh_token) + + +class Refresh(Resource): + @jwt_required(refresh=True) + def get(self, ): + identity = get_jwt_identity() + access_token = create_access_token(identity=identity) + return jsonify(access_token=access_token) \ No newline at end of file diff --git a/Controllers/Post.py b/Controllers/Post.py new file mode 100644 index 0000000..4592a30 --- /dev/null +++ b/Controllers/Post.py @@ -0,0 +1,66 @@ +from flask import request, jsonify +from Models.Post import Post +from Models.Tag import Tag +from Models.Schema import post_schema, posts_schema +from flask_restful import Resource, abort +from app import db +from flask_jwt_extended import create_access_token, get_jwt_identity, jwt_required, JWTManager, current_user + +class PostListResource(Resource): + @jwt_required() + def get(self): + posts = Post.query.all() + return posts_schema.dump(posts) + + @jwt_required() + def post(self): + tags_array= [] + for tag_id in request.json['tags']: + tags_array.append(Tag.query.filter_by(id=tag_id).first()) + + new_post = Post( + title=request.json['title'], + content=request.json['content'], + author_id=current_user.id, + author=current_user, + tags=tags_array + ) + db.session.add(new_post) + db.session.commit() + return post_schema.dump(new_post) + + +class PostResource(Resource): + @jwt_required() + def get(self, post_id): + post = Post.query.get_or_404(post_id) + return post_schema.dump(post) + + @jwt_required() + def put(self, post_id): + post = Post.query.get_or_404(post_id) + + post.title = request.json['title'] + post.content = request.json['content'] + + db.session.commit() + return post_schema.dump(post) + + @jwt_required() + def patch(self, post_id): + post = Post.query.get_or_404(post_id) + + if 'title' in request.json: + post.title = request.json['title'] + if 'content' in request.json: + post.content = request.json['content'] + + db.session.commit() + return post_schema.dump(post) + + @jwt_required() + def delete(self, post_id): + post = Post.query.get_or_404(post_id) + db.session.delete(post) + db.session.commit() + return '', 204 \ No newline at end of file diff --git a/Controllers/Tag.py b/Controllers/Tag.py new file mode 100644 index 0000000..b74523d --- /dev/null +++ b/Controllers/Tag.py @@ -0,0 +1,22 @@ +from flask import request +from Models.Schema import tag_schema, tags_schema +from Models.Tag import Tag +from flask_restful import Resource, abort +from app import db +from werkzeug.security import generate_password_hash +from flask_jwt_extended import create_access_token, get_jwt_identity, jwt_required, JWTManager, current_user + +class TagListResource(Resource): + @jwt_required() + def get(self): + tags = Tag.query.all() + return tags_schema.dump(tags) + + @jwt_required() + def post(self): + new_tag = Tag( + name=request.json['name'] + ) + db.session.add(new_tag) + db.session.commit() + return tag_schema.dump(new_tag) \ No newline at end of file diff --git a/Controllers/User.py b/Controllers/User.py new file mode 100644 index 0000000..2648d9e --- /dev/null +++ b/Controllers/User.py @@ -0,0 +1,63 @@ +from flask import request +from Models.User import User +from Models.Schema import user_schema, users_schema +from flask_restful import Resource, abort +from app import db +from werkzeug.security import generate_password_hash +from flask_jwt_extended import create_access_token, get_jwt_identity, jwt_required, JWTManager, current_user + +class UserListResource(Resource): + @jwt_required() + def get(self): + users = User.query.all() + return users_schema.dump(users) + + @jwt_required() + def post(self): + new_user = User( + name=request.json['name'], + email=request.json['email'], + password=generate_password_hash(request.json['password']) + ) + db.session.add(new_user) + db.session.commit() + return user_schema.dump(new_user) + + +class UserResource(Resource): + @jwt_required() + def get(self, user_id): + user = User.query.get_or_404(user_id) + return user_schema.dump(post) + + @jwt_required() + def put(self, user_id): + user = User.query.get_or_404(user_id) + + user.name = request.json['name'] + user.email = request.json['email'] + user.password = generate_password_hash(request.json['password']) + + db.session.commit() + return user_schema.dump(post) + + @jwt_required() + def patch(self, user_id): + user = User.query.get_or_404(user_id) + + if 'name' in request.json: + user.name = request.json['name'] + if 'email' in request.json: + user.email = request.json['email'] + if 'password' in request.json: + user.password = generate_password_hash(request.json['password']) + + db.session.commit() + return user_schema.dump(post) + + @jwt_required() + def delete(self, user_id): + user = User.query.get_or_404(user_id) + db.session.delete(user) + db.session.commit() + return '', 204 \ No newline at end of file diff --git a/Models/Post.py b/Models/Post.py new file mode 100644 index 0000000..ba1bb40 --- /dev/null +++ b/Models/Post.py @@ -0,0 +1,23 @@ +from app import db, ma +from Models.User import User +from Models.Tag import Tag + +tags_posts = db.Table('tags_posts_mapping', + db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'), primary_key=True), + db.Column('post_id', db.Integer, db.ForeignKey('post.id'), primary_key=True) +) + + +class Post(db.Model): + __tablename__ = "post" + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(50)) + content = db.Column(db.String(255)) + tags = db.relationship('Tag', secondary=tags_posts, lazy='subquery', + backref=db.backref('posts', lazy=True)) + author_id = db.Column(db.Integer, db.ForeignKey("user.id")) + author = db.relationship("User", backref="posts") + + def __repr__(self): + return '' % self.title + diff --git a/Models/Schema.py b/Models/Schema.py new file mode 100644 index 0000000..eb9d4b8 --- /dev/null +++ b/Models/Schema.py @@ -0,0 +1,33 @@ +from marshmallow_sqlalchemy import SQLAlchemyAutoSchema +from Models.User import User +from Models.Post import Post +from Models.Tag import Tag + +class UserSchema(SQLAlchemyAutoSchema): + class Meta: + model = User + exclude = ("password",) + include_fk = True + include_relationships = True + load_instance = True + +class PostSchema(SQLAlchemyAutoSchema): + class Meta: + model= Post + include_fk = True + include_relationships = True + load_instance = True + +class TagSchema(SQLAlchemyAutoSchema): + class Meta: + model= Tag + include_fk = True + include_relationships = True + load_instance = True + +user_schema = UserSchema() +users_schema = UserSchema(many=True) +post_schema = PostSchema() +posts_schema = PostSchema(many=True) +tag_schema = TagSchema() +tags_schema = TagSchema(many=True) \ No newline at end of file diff --git a/Models/Tag.py b/Models/Tag.py new file mode 100644 index 0000000..959b45f --- /dev/null +++ b/Models/Tag.py @@ -0,0 +1,12 @@ +from app import db, ma + +class Tag(db.Model): + __tablename__ = "tags" + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50)) + + + def __repr__(self): + return '' % self.name + + diff --git a/Models/User.py b/Models/User.py new file mode 100644 index 0000000..6a7cca7 --- /dev/null +++ b/Models/User.py @@ -0,0 +1,16 @@ +from app import db, ma +from werkzeug.security import check_password_hash + +class User(db.Model): + __tablename__ = "user" + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50)) + email = db.Column(db.String(255)) + password = db.Column(db.String(255)) + + def __repr__(self): + return '' % self.name + + def check_password(self, password): + return check_password_hash(self.password, password) + diff --git a/app.py b/app.py new file mode 100644 index 0000000..092e629 --- /dev/null +++ b/app.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +import datetime +import jwt +from flask import Flask, request +from flask_sqlalchemy import SQLAlchemy +from flask_marshmallow import Marshmallow +from flask_restful import Api, abort +from werkzeug.security import generate_password_hash, check_password_hash +from flask_jwt_extended import create_access_token, get_jwt_identity, jwt_required, JWTManager +import config + +app = Flask(__name__) +app.config.from_object('config') +db = SQLAlchemy(app) +ma = Marshmallow(app) +api = Api(app) +jwt = JWTManager(app) + +from Controllers.Post import PostListResource, PostResource +from Controllers.User import UserListResource, UserResource +from Controllers.Tag import TagListResource +from Controllers.Login import Login, Refresh + +api.add_resource(UserListResource, '/v1/user') +api.add_resource(UserResource, '/v1/user/') +api.add_resource(Login, '/v1/login') +api.add_resource(Refresh, '/v1/refresh') +api.add_resource(PostListResource, '/v1/posts') +api.add_resource(PostResource, '/v1/posts/') +api.add_resource(TagListResource, '/v1/tags') +db.create_all() + + +if __name__ == '__main__': + app.run() \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..b57f369 --- /dev/null +++ b/config.py @@ -0,0 +1,7 @@ +from datetime import timedelta + +DEBUG = True +JWT_SECRET_KEY = "SECRET" +SQLALCHEMY_DATABASE_URI='sqlite:///test.db' +JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=24) +JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=30) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1cd799d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +Flask +Flask-SQLAlchemy +Flask-RESTful +flask-marshmallow +flask-jwt-extended +pyjwt +marshmallow-sqlalchemy