diff --git a/blog.db b/blog.db index fcaf23f..e69de29 100644 Binary files a/blog.db and b/blog.db differ diff --git a/database.py b/database.py index 2f79ed4..6171399 100644 --- a/database.py +++ b/database.py @@ -9,4 +9,11 @@ SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False,) -Base = declarative_base() \ No newline at end of file +Base = declarative_base() + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() \ No newline at end of file diff --git a/files/repository/blog.py b/files/repository/blog.py new file mode 100644 index 0000000..aa13ea9 --- /dev/null +++ b/files/repository/blog.py @@ -0,0 +1,43 @@ +from sqlalchemy.orm import Session +import models, schemas +from fastapi import HTTPException,status + +def get_all(db: Session): + blogs = db.query(models.Blog).all() + return blogs + +def create(request: schemas.Blog,db: Session): + new_blog = models.Blog(title=request.title, body=request.body,user_id=1) + db.add(new_blog) + db.commit() + db.refresh(new_blog) + return new_blog + +def destroy(id:int,db: Session): + blog = db.query(models.Blog).filter(models.Blog.id == id) + + if not blog.first(): + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, + detail=f"Blog with id {id} not found") + + blog.delete(synchronize_session=False) + db.commit() + return 'done' + +def update(id:int,request:schemas.Blog, db:Session): + blog = db.query(models.Blog).filter(models.Blog.id == id) + + if not blog.first(): + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, + detail=f"Blog with id {id} not found") + + blog.update(request) + db.commit() + return 'updated' + +def show(id:int,db:Session): + blog = db.query(models.Blog).filter(models.Blog.id == id).first() + if not blog: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, + detail=f"Blog with the id {id} is not available") + return blog \ No newline at end of file diff --git a/files/repository/user.py b/files/repository/user.py new file mode 100644 index 0000000..a7db534 --- /dev/null +++ b/files/repository/user.py @@ -0,0 +1,19 @@ + +from sqlalchemy.orm import Session +import models, schemas +from fastapi import HTTPException,status +from hashing import Hash + +def create(request: schemas.User,db:Session): + new_user = models.User(name=request.name,email=request.email,password=Hash.bcrypt(request.password)) + db.add(new_user) + db.commit() + db.refresh(new_user) + return new_user + +def show(id:int,db:Session): + user = db.query(models.User).filter(models.User.id == id).first() + if not user: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, + detail=f"User with the id {id} is not available") + return user \ No newline at end of file diff --git a/files/routers/__init__.py b/files/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/files/routers/authentication.py b/files/routers/authentication.py new file mode 100644 index 0000000..3dd96ea --- /dev/null +++ b/files/routers/authentication.py @@ -0,0 +1,19 @@ +from fastapi import APIRouter, Depends, status, HTTPException +from fastapi.security import OAuth2PasswordRequestForm +import schemas, database, models, token +from hashing import Hash +from sqlalchemy.orm import Session +router = APIRouter(tags=['Authentication']) + +@router.post('/login') +def login(request:OAuth2PasswordRequestForm = Depends(), db: Session = Depends(database.get_db)): + user = db.query(models.User).filter(models.User.email == request.username).first() + if not user: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, + detail=f"Invalid Credentials") + if not Hash.verify(user.password, request.password): + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, + detail=f"Incorrect password") + + access_token = token.create_access_token(data={"sub": user.email}) + return {"access_token": access_token, "token_type": "bearer"} \ No newline at end of file diff --git a/files/routers/blog.py b/files/routers/blog.py new file mode 100644 index 0000000..d9a67b8 --- /dev/null +++ b/files/routers/blog.py @@ -0,0 +1,35 @@ +from typing import List +from fastapi import APIRouter,Depends,status,HTTPException +import schemas, database, models, oauth2 +from sqlalchemy.orm import Session +from ..repository import blog + +router = APIRouter( + prefix="/blog", + tags=['Blogs'] +) + +get_db = database.get_db + +@router.get('/', response_model=List[schemas.ShowBlog]) +def all(db: Session = Depends(get_db),current_user: schemas.User = Depends(oauth2.get_current_user)): + return blog.get_all(db) + + +@router.post('/', status_code=status.HTTP_201_CREATED,) +def create(request: schemas.Blog, db: Session = Depends(get_db),current_user: schemas.User = Depends(oauth2.get_current_user)): + return blog.create(request, db) + +@router.delete('/{id}', status_code=status.HTTP_204_NO_CONTENT) +def destroy(id:int, db: Session = Depends(get_db),current_user: schemas.User = Depends(oauth2.get_current_user)): + return blog.destroy(id,db) + + +@router.put('/{id}', status_code=status.HTTP_202_ACCEPTED) +def update(id:int, request: schemas.Blog, db: Session = Depends(get_db),current_user: schemas.User = Depends(oauth2.get_current_user)): + return blog.update(id,request, db) + + +@router.get('/{id}', status_code=200, response_model=schemas.ShowBlog) +def show(id:int, db: Session = Depends(get_db),current_user: schemas.User = Depends(oauth2.get_current_user)): + return blog.show(id,db) \ No newline at end of file diff --git a/files/routers/user.py b/files/routers/user.py new file mode 100644 index 0000000..a2eb71a --- /dev/null +++ b/files/routers/user.py @@ -0,0 +1,21 @@ +from fastapi import APIRouter +import database, schemas, models +from sqlalchemy.orm import Session +from fastapi import APIRouter,Depends,status +from ..repository import user + +router = APIRouter( + prefix="/user", + tags=['Users'] +) + +get_db = database.get_db + + +@router.post('/', response_model=schemas.ShowUser) +def create_user(request: schemas.User,db: Session = Depends(get_db)): + return user.create(request,db) + +@router.get('/{id}',response_model=schemas.ShowUser) +def get_user(id:int,db: Session = Depends(get_db)): + return user.show(id,db) \ No newline at end of file diff --git a/hashing.py b/hashing.py index 42641e9..7ee1542 100644 --- a/hashing.py +++ b/hashing.py @@ -1,8 +1,10 @@ from passlib.context import CryptContext -pwd_cxt=CryptContext(schemes=['bcrypt'], deprecated='auto') +pwd_cxt = CryptContext(schemes=["bcrypt"], deprecated="auto") class Hash(): - def bcrypt(password : str): + def bcrypt(password: str): return pwd_cxt.hash(password) - \ No newline at end of file + + def verify(hashed_password,plain_password): + return pwd_cxt.verify(plain_password,hashed_password) \ No newline at end of file diff --git a/main.py b/main.py index d671d1f..584a0cb 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,8 @@ -from fastapi import FastAPI, Depends, status, Response, HTTPException -from typing import List -import models, schemas, hashing -from database import engine, SessionLocal -from sqlalchemy.orm import Session +from fastapi import FastAPI, status +import models +from database import engine +from files.routers import blog, user, authentication + app = FastAPI( @@ -12,82 +12,12 @@ description="This API is Created by the developer Manan Ahmed Broti. Not for commercial purpose. The Base URL for this API is set to: https://blog-rhhb.onrender.com/", ) models.Base.metadata.create_all(bind=engine) +app.include_router(authentication.router) +app.include_router(blog.router) +app.include_router(user.router) -def get_db(): - db = SessionLocal() - try: - yield db - finally: - db.close() @app.get("/", status_code=status.HTTP_200_OK) async def credits(): return {"message": "Welcome to the Blog API. Developed By MAnan Ahmed Broti. Website: AhmedManan.com"} -@app.get('/blog', response_model=List[schemas.ShowBlog], tags=['Blogs']) -def all_posts(db : Session = Depends(get_db)): - blogs = db.query(models.Blog).all() - return blogs - -@app.post('/blog', status_code = 201, tags=['Blogs']) -def create_post(request : schemas.Blog, db : Session = Depends(get_db)): - new_blog=models.Blog(title=request.title,body=request.body) - db.add(new_blog) - db.commit() - db.refresh(new_blog) - return new_blog - -@app.get('/blog/{blog_id}', status_code=status.HTTP_200_OK, response_model=schemas.ShowBlog, tags=['Blogs']) -def get_post(blog_id, response : Response, db : Session = Depends(get_db)): - blog = db.query(models.Blog).filter(models.Blog.id== blog_id).first() - if not blog: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,detail=f'Post not available') - # response.status_code = status.HTTP_404_NOT_FOUND - # return {'Details':'Blog post not found!'} - return blog - -@app.put('/blog/{blog_id}', status_code=status.HTTP_202_ACCEPTED, tags=['Blogs']) -def update_post(blog_id, request : schemas.Blog, db : Session = Depends(get_db)): - blog = db.query(models.Blog).filter(models.Blog.id== blog_id) - - if not blog.first(): - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,detail=f'Post with ID={blog_id} not found') - - blog.update(request.title,request.body) - db.commit() - return 'updated successfully' - - -@app.delete('/blog/{blog_id}', status_code=status.HTTP_204_NO_CONTENT, tags=['Blogs']) -def delete_post(blog_id, db : Session = Depends(get_db)): - blog = db.query(models.Blog).filter(models.Blog.id== blog_id) - - if not blog.first(): - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,detail=f'Post with ID={blog_id} not found') - - blog.delete(synchronize_session=False) - db.commit() - return {f'Blog post deleted!'} - -@app.post('/user', status_code = 201, tags=['User']) -def create_user(request: schemas.User, db: Session = Depends(get_db)): - # hashed_password = pwd_context.hash(request.password) # Hash the password - - user_data = request.dict() - user_data["password"] = hashing.Hash.bcrypt(request.password) - - new_user = models.User(**user_data) - db.add(new_user) - db.commit() - db.refresh(new_user) - - return new_user - -@app.get('/user/{user_id}', status_code=status.HTTP_200_OK, response_model=schemas.ShowUser, tags=['User']) -def get_post(user_id:int, response : Response, db : Session = Depends(get_db)): - user = db.query(models.User).filter(models.User.id== user_id).first() - if not user: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,detail=f'User not available') - # response.status_code = status.HTTP_404_NOT_FOUND - # return {'Details':'Blog post not found!'} - return user \ No newline at end of file diff --git a/models.py b/models.py index 6249a96..97d297b 100644 --- a/models.py +++ b/models.py @@ -1,24 +1,25 @@ -from sqlalchemy import Column, Integer, String, DateTime, func +from sqlalchemy import Column, Integer, String, ForeignKey from database import Base +from sqlalchemy.orm import relationship + class Blog(Base): __tablename__ = 'blogs' + id = Column(Integer, primary_key=True, index=True) title = Column(String) body = Column(String) - author = Column(String) - tags = Column(String) - created_at = Column(DateTime, default=func.now()) - updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) + user_id = Column(Integer, ForeignKey('users.id')) + + creator = relationship("User", back_populates="blogs") class User(Base): __tablename__ = 'users' + id = Column(Integer, primary_key=True, index=True) name = Column(String) - email = Column(String) # Fixed typo here + email = Column(String) password = Column(String) - user_type = Column(String) # Renamed "type" to "user_type" - status = Column(String) - created_at = Column(DateTime, default=func.now()) - updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) \ No newline at end of file + + blogs = relationship('Blog', back_populates="creator") \ No newline at end of file diff --git a/my_token.py b/my_token.py new file mode 100644 index 0000000..3e8df91 --- /dev/null +++ b/my_token.py @@ -0,0 +1,24 @@ +from datetime import datetime, timedelta +from jose import JWTError, jwt +import schemas + +SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 30 + +def create_access_token(data: dict): + to_encode = data.copy() + expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + return encoded_jwt + +def verify_token(token:str,credentials_exception): + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + email: str = payload.get("sub") + if email is None: + raise credentials_exception + token_data = schemas.TokenData(email=email) + except JWTError: + raise credentials_exception \ No newline at end of file diff --git a/oauth2.py b/oauth2.py new file mode 100644 index 0000000..57b6318 --- /dev/null +++ b/oauth2.py @@ -0,0 +1,14 @@ +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +import token + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login") + +def get_current_user(data: str = Depends(oauth2_scheme)): + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + + return token.verify_token(data, credentials_exception) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b7d8865..871e7dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ mysql-connector-python passlib bcrypt python-jose -python-multipart \ No newline at end of file +python-multipart +cryptography \ No newline at end of file diff --git a/schemas.py b/schemas.py index 4985d8f..756c314 100644 --- a/schemas.py +++ b/schemas.py @@ -1,31 +1,45 @@ +from typing import List, Optional from pydantic import BaseModel -from typing import Optional -class Blog(BaseModel): - title:str - body:str - author:str - tags:str - # published: Optional [bool] = False -class ShowBlog(BaseModel): - title:str - body:str - author:str - tags:str - class config(): +class BlogBase(BaseModel): + title: str + body: str + +class Blog(BlogBase): + class Config(): orm_mode = True class User(BaseModel): name:str email:str password:str - user_type:str - status:str class ShowUser(BaseModel): name:str email:str - status: str - class config(): - orm_mode = True \ No newline at end of file + blogs : List[Blog] =[] + class Config(): + orm_mode = True + +class ShowBlog(BaseModel): + title: str + body:str + creator: ShowUser + + class Config(): + orm_mode = True + + +class Login(BaseModel): + username: str + password:str + + +class Token(BaseModel): + access_token: str + token_type: str + + +class TokenData(BaseModel): + email: Optional[str] = None \ No newline at end of file