├── .gitignore ├── Pipfile ├── models.py ├── database.py ├── app_utils.py ├── schemas.py ├── crud.py └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | fastapi = "*" 10 | uvicorn = "*" 11 | sqlalchemy = "*" 12 | mysql-connector-python = "*" 13 | bcrypt = "*" 14 | pytest = "*" 15 | pyjwt = "*" 16 | graphene = "*" 17 | graphene-sqlalchemy = "*" 18 | 19 | [requires] 20 | python_version = "3.7" 21 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Integer, String 2 | from database import Base 3 | 4 | 5 | class UserInfo(Base): 6 | __tablename__ = "user_info" 7 | 8 | id = Column(Integer, primary_key=True, index=True) 9 | username = Column(String, unique=True) 10 | password = Column(String) 11 | fullname = Column(String, unique=True) 12 | 13 | 14 | class Blog(Base): 15 | __tablename__ = "blog" 16 | 17 | id = Column(Integer, primary_key=True, index=True) 18 | title = Column(String) 19 | content = Column(String) 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /database.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | from sqlalchemy.ext.declarative import declarative_base 3 | from sqlalchemy.orm import sessionmaker, scoped_session 4 | 5 | SQLALCHEMY_DATABASE_URL = "mysql+mysqlconnector://root:cuong1990@localhost:3306/restapi" 6 | 7 | engine = create_engine( 8 | SQLALCHEMY_DATABASE_URL, 9 | ) 10 | db_session = scoped_session(sessionmaker(autocommit=False, 11 | autoflush=False, 12 | bind=engine)) 13 | 14 | Base = declarative_base() 15 | Base.query = db_session.query_property() 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app_utils.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta, datetime 2 | import jwt 3 | secret_key = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" 4 | algorithm = "HS256" 5 | 6 | 7 | def create_access_token(*, data: dict, expires_delta: timedelta = None): 8 | to_encode = data.copy() 9 | if expires_delta: 10 | expire = datetime.utcnow() + expires_delta 11 | else: 12 | expire = datetime.utcnow() + timedelta(minutes=15) 13 | to_encode.update({"exp": expire}) 14 | encoded_jwt = jwt.encode(to_encode, secret_key, algorithm=algorithm) 15 | return encoded_jwt 16 | 17 | 18 | def decode_access_token(*, data: str): 19 | to_decode = data 20 | return jwt.decode(to_decode, secret_key, algorithm=algorithm) 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /schemas.py: -------------------------------------------------------------------------------- 1 | from graphene_sqlalchemy import SQLAlchemyObjectType 2 | from models import UserInfo, Blog 3 | from pydantic import BaseModel 4 | 5 | 6 | class UserInfoBase(BaseModel): 7 | username: str 8 | 9 | 10 | class UserCreate(UserInfoBase): 11 | fullname: str 12 | password: str 13 | 14 | 15 | class UserAuthenticate(UserInfoBase): 16 | password: str 17 | 18 | 19 | class UserInformation(UserInfoBase): 20 | id: int 21 | 22 | class Config: 23 | orm_mode = True 24 | 25 | 26 | class Token(BaseModel): 27 | access_token: str 28 | token_type: str 29 | 30 | 31 | class TokenData(BaseModel): 32 | username: str = None 33 | 34 | 35 | class BlogBase(BaseModel): 36 | title: str 37 | content: str 38 | 39 | 40 | class BlogInformation(BlogBase): 41 | id: int 42 | 43 | class Config: 44 | orm_mode = True 45 | 46 | 47 | class UserInfoSchema(SQLAlchemyObjectType): 48 | class Meta: 49 | model = UserInfo 50 | 51 | 52 | class BlogSchema(SQLAlchemyObjectType): 53 | class Meta: 54 | model = Blog 55 | 56 | 57 | -------------------------------------------------------------------------------- /crud.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import Session 2 | import models, schemas 3 | import bcrypt 4 | 5 | 6 | def get_user_by_username(db: Session, username: str): 7 | return db.query(models.UserInfo).filter(models.UserInfo.username == username).first() 8 | 9 | 10 | def create_user(db: Session, user: schemas.UserCreate): 11 | hashed_password = bcrypt.hashpw(user.password.encode('utf-8'), bcrypt.gensalt()) 12 | db_user = models.UserInfo(username=user.username, password=hashed_password, fullname=user.fullname) 13 | db.add(db_user) 14 | db.commit() 15 | db.refresh(db_user) 16 | return db_user 17 | 18 | 19 | def check_username_password(db: Session, user: schemas.UserAuthenticate): 20 | db_user_info: models.UserInfo = get_user_by_username(db, username=user.username) 21 | return bcrypt.checkpw(user.password.encode('utf-8'), db_user_info.password.encode('utf-8')) 22 | 23 | 24 | def create_new_blog(db: Session, blog: schemas.BlogBase): 25 | db_blog = models.Blog(title=blog.title, content=blog.content) 26 | db.add(db_blog) 27 | db.commit() 28 | db.refresh(db_blog) 29 | return db_blog 30 | 31 | 32 | def get_all_blogs(db: Session): 33 | return db.query(models.Blog).all() 34 | 35 | 36 | def get_blog_by_id(db: Session, blog_id: int): 37 | return db.query(models.Blog).filter(models.Blog.id == blog_id).first() 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from logging import log, CRITICAL 2 | 3 | import bcrypt 4 | import graphene 5 | import uvicorn 6 | from fastapi import FastAPI, HTTPException 7 | from graphene import String 8 | from graphql import GraphQLError 9 | from jwt import PyJWTError 10 | from starlette.graphql import GraphQLApp 11 | 12 | import crud 13 | import models 14 | from app_utils import decode_access_token 15 | from database import db_session 16 | from schemas import BlogSchema, UserInfoSchema, UserCreate, UserAuthenticate, TokenData, BlogBase 17 | 18 | ACCESS_TOKEN_EXPIRE_MINUTES = 30 19 | 20 | 21 | db = db_session.session_factory() 22 | 23 | 24 | class Query(graphene.ObjectType): 25 | 26 | all_blogs = graphene.List(BlogSchema) 27 | 28 | def resolve_all_blogs(self, info): 29 | query = BlogSchema.get_query(info) # SQLAlchemy query 30 | return query.all() 31 | 32 | 33 | class CreateUser(graphene.Mutation): 34 | class Arguments: 35 | username = graphene.String(required=True) 36 | password = graphene.String(required=True) 37 | fullname = graphene.String() 38 | 39 | ok = graphene.Boolean() 40 | user = graphene.Field(lambda: UserInfoSchema) 41 | 42 | @staticmethod 43 | def mutate(root, info, username, password, fullname, ): 44 | hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()) 45 | user = UserInfoSchema(username=username, password=hashed_password, fullname=fullname) 46 | ok = True 47 | db_user = crud.get_user_by_username(db, username=username) 48 | if db_user: 49 | raise GraphQLError("Username already registered") 50 | user_info = UserCreate(username=username, password=password, fullname=fullname) 51 | crud.create_user(db, user_info) 52 | return CreateUser(user=user, ok=ok) 53 | 54 | 55 | class AuthenUser(graphene.Mutation): 56 | class Arguments: 57 | username = graphene.String(required=True) 58 | password = graphene.String(required=True) 59 | 60 | token = graphene.String() 61 | 62 | @staticmethod 63 | def mutate(root, info, username, password): 64 | db_user = crud.get_user_by_username(db, username=username) 65 | user_authenticate = UserAuthenticate(username=username, password=password) 66 | if db_user is None: 67 | raise GraphQLError("Username not existed") 68 | else: 69 | is_password_correct = crud.check_username_password(db, user_authenticate) 70 | if is_password_correct is False: 71 | raise GraphQLError("Password is not correct") 72 | else: 73 | from datetime import timedelta 74 | access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) 75 | from app_utils import create_access_token 76 | access_token = create_access_token( 77 | data={"sub": username}, expires_delta=access_token_expires) 78 | return AuthenUser(token=access_token) 79 | 80 | 81 | class CreateNewBlog(graphene.Mutation): 82 | class Arguments: 83 | title = graphene.String(required=True) 84 | content = graphene.String(required=True) 85 | token = graphene.String(required=True) 86 | 87 | ok = graphene.Boolean() 88 | 89 | @staticmethod 90 | def mutate(root, info, title, content, token): 91 | try: 92 | payload = decode_access_token(data=token) 93 | username: str = payload.get("sub") 94 | if username is None: 95 | raise GraphQLError("Invalid credentials") 96 | token_data = TokenData(username=username) 97 | except PyJWTError: 98 | raise GraphQLError("Invalid credentials") 99 | user = crud.get_user_by_username(db, username=token_data.username) 100 | if user is None: 101 | raise GraphQLError("Invalid credentials") 102 | blog = BlogBase(title=title, content=content) 103 | crud.create_new_blog(db=db, blog=blog) 104 | ok = True 105 | return CreateNewBlog(ok=ok) 106 | 107 | 108 | class MyMutations(graphene.ObjectType): 109 | user = CreateUser.Field() 110 | authen_user = AuthenUser.Field() 111 | create_new_blog = CreateNewBlog.Field() 112 | 113 | 114 | app = FastAPI() 115 | app.add_route("/graphql", GraphQLApp(schema=graphene.Schema(query=Query, mutation=MyMutations))) 116 | 117 | 118 | if __name__ == "__main__": 119 | uvicorn.run(app, host="127.0.0.1", port=8000) --------------------------------------------------------------------------------