├── .gitignore
├── README.md
├── blog.db
├── blog
├── OAuth2.py
├── __init__.py
├── blog.db
├── database.py
├── main.py
├── models
│ ├── __init__.py
│ ├── blog.py
│ └── user.py
├── repository
│ ├── auth.py
│ ├── blog.py
│ └── user.py
├── routers
│ ├── __init__.py
│ ├── authentication.py
│ ├── blog.py
│ └── user.py
├── schemas.py
└── utils
│ ├── __init__.py
│ └── hashing.py
├── images
└── screenshot1.png
└── requirements.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | env
3 | blog/.DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # blogs-app-fastapi
2 | Simple Blogs app made using [FastAPI](https://github.com/tiangolo/fastapi)
3 |
4 | ## ScreenShot
5 |
6 |
7 |

8 |
9 |
10 | ## Web Routes
11 |
12 | All routes are available on ``/docs`` or ``/redoc`` paths with Swagger or ReDoc.
13 |
14 | ## Features
15 |
16 | - API Docs
17 | - ORM using SQLAlchemy
18 | - JWT Authentication
19 | - Pydantic Schemas
20 |
21 | ## Quick Start
22 |
23 | First, create a virtual env:
24 |
25 | ```bash
26 | python3 -m venv env
27 | ```
28 |
29 | Second activate the virtual env:
30 |
31 | ```bash
32 | source env/bin/activate
33 | ```
34 |
35 | Next, install requirements from requirements.txt:
36 |
37 | ```bash
38 | pip3 install -r requirements.txt
39 | ```
40 |
41 | For running the app in debug mode:
42 |
43 | ```bash
44 | uvicorn blog.main:app --reload
45 | ```
46 |
--------------------------------------------------------------------------------
/blog.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkey531/blogs-app-fastapi/3b99de96f626ebe3316782f0a14dce9a798e5eef/blog.db
--------------------------------------------------------------------------------
/blog/OAuth2.py:
--------------------------------------------------------------------------------
1 | from fastapi import Depends, HTTPException, status
2 | from fastapi.security import OAuth2PasswordBearer
3 | from .repository import auth as authRepository
4 | from .repository import user as userRepository
5 | from sqlalchemy.orm import Session
6 | from .database import get_db
7 |
8 | oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")
9 |
10 | async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
11 | credentials_exception = HTTPException(
12 | status_code=status.HTTP_401_UNAUTHORIZED,
13 | detail="Could not validate credentials",
14 | headers={"WWW-Authenticate": "Bearer"},
15 | )
16 | token_data = authRepository.verify(token, credentials_exception)
17 | user = userRepository.get(token_data.username, db)
18 | if user is None:
19 | raise credentials_exception
20 | return user
--------------------------------------------------------------------------------
/blog/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkey531/blogs-app-fastapi/3b99de96f626ebe3316782f0a14dce9a798e5eef/blog/__init__.py
--------------------------------------------------------------------------------
/blog/blog.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkey531/blogs-app-fastapi/3b99de96f626ebe3316782f0a14dce9a798e5eef/blog/blog.db
--------------------------------------------------------------------------------
/blog/database.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import create_engine
2 | from sqlalchemy.ext.declarative import declarative_base
3 | from sqlalchemy.orm import sessionmaker
4 |
5 | DATABASE_URL = 'sqlite:///./blog.db'
6 |
7 | engine = create_engine(DATABASE_URL, connect_args = {"check_same_thread": False})
8 |
9 | SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
10 |
11 | Base = declarative_base()
12 |
13 | def get_db():
14 | db = SessionLocal()
15 | try:
16 | yield db
17 | finally:
18 | db.close()
--------------------------------------------------------------------------------
/blog/main.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI
2 | from blog.models import user
3 | from .database import engine
4 | from .models import blog
5 | from blog import database
6 | from .routers import blog as blogRouter
7 | from .routers import user as userRouter
8 | from .routers import authentication as authRouter
9 |
10 |
11 | app = FastAPI()
12 |
13 | blog.Base.metadata.create_all(bind = engine)
14 | user.Base.metadata.create_all(bind = engine)
15 |
16 | get_db = database.get_db
17 |
18 | app.include_router(blogRouter.router)
19 | app.include_router(userRouter.router)
20 | app.include_router(authRouter.router)
21 |
--------------------------------------------------------------------------------
/blog/models/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkey531/blogs-app-fastapi/3b99de96f626ebe3316782f0a14dce9a798e5eef/blog/models/__init__.py
--------------------------------------------------------------------------------
/blog/models/blog.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.orm import relationship
2 | from sqlalchemy.sql.schema import ForeignKey
3 | from ..database import Base
4 | from sqlalchemy import Column, Integer, String, Boolean
5 |
6 | class Blog(Base):
7 | __tablename__ = 'blogs'
8 |
9 | id = Column(Integer, primary_key= True, index= True)
10 | title = Column(String)
11 | body = Column(String)
12 | published = Column(Boolean, default= False)
13 |
14 | username = Column(String, ForeignKey('users.username'))
15 |
16 | creator = relationship('User', back_populates="blogs")
--------------------------------------------------------------------------------
/blog/models/user.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.orm import relationship
2 | from ..database import Base
3 | from sqlalchemy import Column, String
4 |
5 | class User(Base):
6 | __tablename__ = 'users'
7 |
8 | username = Column(String, primary_key= True, index=True)
9 | name = Column(String)
10 | password = Column(String)
11 |
12 | blogs = relationship('Blog', back_populates="creator")
--------------------------------------------------------------------------------
/blog/repository/auth.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime, timedelta
2 | from fastapi import HTTPException, status
3 | from sqlalchemy.orm.session import Session
4 | from ..models.user import User
5 | from .. import schemas
6 | from ..utils.hashing import Hashing
7 | from typing import Optional
8 | from jose import jwt, JWTError
9 |
10 | ACCESS_TOKEN_EXPIRE_MINUTES = 30
11 | SECRET_KEY = '09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7'
12 | ALGORITHM = 'HS256'
13 |
14 | def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
15 | to_encode = data.copy()
16 | if expires_delta:
17 | expire = datetime.utcnow() + expires_delta
18 | else:
19 | expire = datetime.utcnow() + timedelta(minutes=15)
20 | to_encode.update({"exp": expire})
21 | encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
22 | return encoded_jwt
23 |
24 | def login(login: schemas.Login, db: Session):
25 | user = db.query(User).filter(User.username == login.username).first()
26 | if not user:
27 | raise HTTPException(status_code= status.HTTP_404_NOT_FOUND, detail="Invalid Credentials")
28 | if not Hashing.verify(login.password, user.password):
29 | raise HTTPException(status_code= status.HTTP_404_NOT_FOUND, detail="Invalid Credentials")
30 | access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
31 | access_token = create_access_token(
32 | data={"sub": user.username}, expires_delta=access_token_expires
33 | )
34 | return {"access_token": access_token, "token_type": "bearer"}
35 |
36 | def verify(token: str, credentials_exception):
37 | try:
38 | payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
39 | username: str = payload.get("sub")
40 | if username is None:
41 | raise credentials_exception
42 | return schemas.TokenData(username=username)
43 | except JWTError:
44 | raise credentials_exception
45 |
--------------------------------------------------------------------------------
/blog/repository/blog.py:
--------------------------------------------------------------------------------
1 | from fastapi import HTTPException, status
2 | from sqlalchemy.orm.session import Session
3 | from ..models.blog import Blog
4 | from .. import schemas
5 |
6 |
7 | def get_all(db: Session, user: schemas.User):
8 | blogs = db.query(Blog).filter(Blog.username == user.username).all()
9 | return blogs
10 |
11 | def create(blog: schemas.Blog, db: Session):
12 | new_blog = Blog(title = blog.title, body = blog.body, username= "user1")
13 | db.add(new_blog)
14 | db.commit()
15 | db.refresh(new_blog)
16 | return new_blog
17 |
18 | def get(blog_id: int, db: Session):
19 | blog = db.query(Blog).filter(Blog.id == blog_id).first()
20 | if not blog:
21 | raise HTTPException(status_code= status.HTTP_404_NOT_FOUND, detail="no blog with given id found")
22 | return blog
23 |
24 | def delete(blog_id: int, db: Session):
25 | db.query(Blog).filter(Blog.id == blog_id).delete(synchronize_session= False)
26 | db.commit()
27 | return 'deleted'
28 |
29 | def update(blog_id: int, blog: schemas.Blog, db: Session):
30 | blog = db.query(Blog).filter(Blog.id == blog_id).update(blog.dict())
31 | db.commit()
32 | return 'updated'
--------------------------------------------------------------------------------
/blog/repository/user.py:
--------------------------------------------------------------------------------
1 | from fastapi import HTTPException, status
2 | from sqlalchemy.orm.session import Session
3 | from ..models.user import User
4 | from .. import schemas
5 | from ..utils.hashing import Hashing
6 |
7 |
8 | def create(user: schemas.User, db: Session):
9 | hashed_pswd = Hashing.get_hashed_password(user.password)
10 | new_user = User(name= user.name, username = user.username, password= hashed_pswd)
11 | try:
12 | db.add(new_user)
13 | db.commit()
14 | db.refresh(new_user)
15 | return new_user
16 | except Exception as e:
17 | if e.__class__.__name__ == "IntegrityError":
18 | raise HTTPException(status_code= status.HTTP_422_UNPROCESSABLE_ENTITY, detail="username already taken")
19 | raise HTTPException(status_code= status.HTTP_500_INTERNAL_SERVER_ERROR, detail='something went wrong')
20 |
21 | def get(username: str, db: Session):
22 | user = db.query(User).filter(User.username == username).first()
23 | if not user:
24 | raise HTTPException(status_code= status.HTTP_404_NOT_FOUND, detail="user with given username not found")
25 | return user
--------------------------------------------------------------------------------
/blog/routers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkey531/blogs-app-fastapi/3b99de96f626ebe3316782f0a14dce9a798e5eef/blog/routers/__init__.py
--------------------------------------------------------------------------------
/blog/routers/authentication.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime, timedelta
2 | from fastapi import APIRouter, Depends
3 | from sqlalchemy.orm import Session
4 | from blog import schemas
5 | from ..database import get_db
6 | from ..repository import auth as authRepository
7 | from fastapi.security import OAuth2PasswordRequestForm
8 |
9 |
10 | router = APIRouter(tags=['Authentication'])
11 |
12 |
13 | @router.post('/login')
14 | def login(login: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
15 | return authRepository.login(login, db)
16 |
--------------------------------------------------------------------------------
/blog/routers/blog.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 | from fastapi import APIRouter, Depends, status, HTTPException
3 | from sqlalchemy.orm import Session
4 | from blog import schemas
5 | from ..database import get_db
6 | from ..repository import blog as blogRepository
7 | from ..OAuth2 import get_current_user
8 |
9 |
10 | router = APIRouter(prefix='/blogs',
11 | tags=['Blogs'])
12 |
13 | @router.get("/", response_model=List[schemas.ShowBlog])
14 | def get_all(db: Session = Depends(get_db), current_user: schemas.User = Depends(get_current_user)):
15 | return blogRepository.get_all(db, current_user)
16 |
17 | @router.post('/', status_code= status.HTTP_201_CREATED, response_model=schemas.ShowBlog)
18 | def create_blog(blog: schemas.Blog, db: Session = Depends(get_db), current_user: schemas.User = Depends(get_current_user)):
19 | return blogRepository.create(blog, db)
20 |
21 |
22 | @router.get("/{blog_id}", response_model=schemas.ShowBlog)
23 | def get_blog(blog_id: int, db: Session = Depends(get_db), current_user: schemas.User = Depends(get_current_user)):
24 | return blogRepository.get(blog_id, db)
25 |
26 | @router.put("/{blog_id}", status_code= status.HTTP_202_ACCEPTED)
27 | def update_blog(blog_id: int, blog: schemas.Blog, db: Session = Depends(get_db), current_user: schemas.User = Depends(get_current_user)):
28 | return blogRepository.update(blog_id, blog, db)
29 |
30 | @router.delete("/{blog_id}", status_code= status.HTTP_204_NO_CONTENT)
31 | def delete_blog(blog_id: int, db: Session = Depends(get_db), current_user: schemas.User = Depends(get_current_user)):
32 | return blogRepository.delete(blog_id, db)
33 |
--------------------------------------------------------------------------------
/blog/routers/user.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 | from fastapi import APIRouter, Depends, status, HTTPException
3 | from sqlalchemy.orm import Session
4 | from blog import schemas
5 | from ..database import get_db
6 | from ..models.user import User
7 | from ..utils.hashing import Hashing
8 | from ..repository import user as userRepository
9 |
10 |
11 | router = APIRouter(tags=['Users'])
12 |
13 | @router.post('/users', status_code= status.HTTP_201_CREATED)
14 | def create_user(user: schemas.User, db: Session = Depends(get_db)):
15 | return userRepository.create(user, db)
16 |
17 | @router.get('/users/{username}', response_model=schemas.ShowUser)
18 | def get_user_by_username(username: str, db: Session = Depends(get_db)):
19 | return userRepository.get(username, db)
--------------------------------------------------------------------------------
/blog/schemas.py:
--------------------------------------------------------------------------------
1 | from typing import List, Optional
2 | from pydantic import BaseModel
3 |
4 | class User(BaseModel):
5 | name: str
6 | username: str
7 | password: str
8 |
9 | class Config():
10 | orm_mode = True
11 |
12 | class Blog(BaseModel):
13 | title: str
14 | body: str
15 | published: Optional[bool] = False
16 |
17 | class Config():
18 | orm_mode = True
19 |
20 | class ShowUser(BaseModel):
21 | name: str
22 | username: str
23 | blogs: List[Blog] = []
24 |
25 | class Config():
26 | orm_mode = True
27 |
28 |
29 | class ShowBlog(BaseModel):
30 | title: str
31 | body: str
32 | creator: ShowUser
33 |
34 | class Config():
35 | orm_mode = True
36 |
37 | class Login(BaseModel):
38 | username: str
39 | password: str
40 |
41 | class Config():
42 | orm_mode = True
43 |
44 | class TokenData(BaseModel):
45 | username: Optional[str] = None
46 |
47 |
48 |
--------------------------------------------------------------------------------
/blog/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkey531/blogs-app-fastapi/3b99de96f626ebe3316782f0a14dce9a798e5eef/blog/utils/__init__.py
--------------------------------------------------------------------------------
/blog/utils/hashing.py:
--------------------------------------------------------------------------------
1 | from passlib.context import CryptContext
2 |
3 | pswd_cxt = CryptContext(schemes=["bcrypt"], deprecated="auto")
4 |
5 | class Hashing():
6 | def get_hashed_password(password: str):
7 | return pswd_cxt.hash(password)
8 |
9 | def verify(plain_password: str, hashed_password: str):
10 | return pswd_cxt.verify(plain_password, hashed_password)
--------------------------------------------------------------------------------
/images/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/monkey531/blogs-app-fastapi/3b99de96f626ebe3316782f0a14dce9a798e5eef/images/screenshot1.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | fastapi
2 | uvicorn
3 | sqlalchemy
4 | passlib
5 | bcrypt
6 | python-jose
7 | python-multipart
--------------------------------------------------------------------------------