Skip to content

Commit d208da1

Browse files
committed
Update application
1 parent b0f3d94 commit d208da1

File tree

14 files changed

+392
-0
lines changed

14 files changed

+392
-0
lines changed

ch07/planner/auth/__init__.py

Whitespace-only changes.

ch07/planner/auth/authenticate.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from fastapi import Depends, HTTPException, status
2+
from fastapi.security import OAuth2PasswordBearer
3+
4+
from auth.jwt_handler import verify_access_token
5+
6+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/user/signin")
7+
8+
9+
async def authenticate(token: str = Depends(oauth2_scheme)):
10+
if not token:
11+
raise HTTPException(
12+
status_code=status.HTTP_403_FORBIDDEN,
13+
detail="Sign in for access"
14+
)
15+
16+
decoded_token = verify_access_token(token)
17+
return decoded_token["user"]

ch07/planner/auth/hash_password.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from passlib.context import CryptContext
2+
3+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
4+
5+
class HashPassword:
6+
def create_hash(self, password: str):
7+
return pwd_context.hash(password)
8+
9+
10+
def verify_hash(self, plain_password: str, hashed_password: str):
11+
return pwd_context.verify(plain_password, hashed_password)

ch07/planner/auth/jwt_handler.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from curses.ascii import HT
2+
import time
3+
from datetime import datetime
4+
5+
from fastapi import HTTPException, status
6+
from jose import jwt, JWTError
7+
8+
from database.database import Settings
9+
10+
settings = Settings()
11+
12+
13+
def create_access_token(user: str):
14+
payload = {
15+
"user": user,
16+
"expires": time.time() + 3600
17+
}
18+
19+
token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
20+
return token
21+
22+
23+
def verify_access_token(token: str):
24+
try:
25+
data = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
26+
27+
expire = data.get("expires")
28+
29+
if expire is None:
30+
raise HTTPException(
31+
status_code=status.HTTP_400_BAD_REQUEST,
32+
detail="No access token supplied"
33+
)
34+
if datetime.utcnow() > datetime.utcfromtimestamp(expire):
35+
raise HTTPException(
36+
status_code=status.HTTP_403_FORBIDDEN,
37+
detail="Token expired!"
38+
)
39+
return data
40+
41+
except JWTError:
42+
raise HTTPException(
43+
status_code=status.HTTP_400_BAD_REQUEST,
44+
detail="Invalid token"
45+
)

ch07/planner/database/__init__.py

Whitespace-only changes.

ch07/planner/database/database.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from beanie import init_beanie, PydanticObjectId
2+
from motor.motor_asyncio import AsyncIOMotorClient
3+
from typing import Optional
4+
from pydantic import BaseSettings, BaseModel
5+
6+
from models.users import User
7+
from models.events import Event
8+
9+
class Settings(BaseSettings):
10+
DATABASE_URL: Optional[str] = None
11+
SECRET_KEY: Optional[str] = None
12+
13+
async def initialize_database(self):
14+
client = AsyncIOMotorClient(self.DATABASE_URL)
15+
await init_beanie(database=client.get_default_database(),
16+
document_models=[Event, User])
17+
18+
class Config:
19+
env_file = ".env"
20+
21+
22+
class Database:
23+
def __init__(self, model):
24+
self.model = model
25+
26+
async def save(self, document):
27+
await document.create()
28+
return
29+
30+
async def get(self, id: PydanticObjectId):
31+
doc = await self.model.get(id)
32+
if doc:
33+
return doc
34+
return False
35+
36+
async def get_all(self):
37+
docs = await self.model.find_all().to_list()
38+
return docs
39+
40+
async def update(self, id: PydanticObjectId, body: BaseModel):
41+
doc_id = id
42+
des_body = body.dict()
43+
44+
des_body = {k:v for k,v in des_body.items() if v is not None}
45+
update_query = {"$set": {
46+
field: value for field, value in des_body.items()
47+
}}
48+
49+
doc = await self.get(doc_id)
50+
if not doc:
51+
return False
52+
await doc.update(update_query)
53+
return doc
54+
55+
async def delete(self, id: PydanticObjectId):
56+
doc = await self.get(id)
57+
if not doc:
58+
return False
59+
await doc.delete()
60+
return True

ch07/planner/main.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from fastapi import FastAPI
2+
from fastapi.responses import RedirectResponse
3+
from database.database import Settings
4+
5+
from routes.users import user_router
6+
from routes.events import event_router
7+
8+
import uvicorn
9+
10+
app = FastAPI()
11+
12+
settings = Settings()
13+
14+
# Register routes
15+
16+
app.include_router(user_router, prefix="/user")
17+
app.include_router(event_router, prefix="/event")
18+
19+
@app.on_event("startup")
20+
async def init_db():
21+
await settings.initialize_database()
22+
23+
@app.get("/")
24+
async def home():
25+
return RedirectResponse(url="/event/")
26+
27+
if __name__ == '__main__':
28+
uvicorn.run("main:app", host="0.0.0.0", port=8080, reload=True)

ch07/planner/models/__init__.py

Whitespace-only changes.

ch07/planner/models/events.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from beanie import Document
2+
from typing import Optional, List
3+
from pydantic import BaseModel
4+
5+
6+
class Event(Document):
7+
creator: Optional[str]
8+
title: str
9+
image: str
10+
description: str
11+
tags: List[str]
12+
location: str
13+
14+
class Config:
15+
schema_extra = {
16+
"example": {
17+
"title": "FastAPI BookLaunch",
18+
"image": "https://linktomyimage.com/image.png",
19+
"description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
20+
"tags": ["python", "fastapi", "book", "launch"],
21+
"location": "Google Meet"
22+
}
23+
}
24+
25+
class Collection:
26+
name = "events"
27+
28+
29+
class EventUpdate(BaseModel):
30+
title: Optional[str]
31+
image: Optional[str]
32+
description: Optional[str]
33+
tags: Optional[List[str]]
34+
location: Optional[str]
35+
36+
class Config:
37+
schema_extra = {
38+
"example": {
39+
"title": "FastAPI BookLaunch",
40+
"image": "https://linktomyimage.com/image.png",
41+
"description": "We will be discussing the contents of the FastAPI book in this event.Ensure to come with your own copy to win gifts!",
42+
"tags": ["python", "fastapi", "book", "launch"],
43+
"location": "Google Meet"
44+
}
45+
}

ch07/planner/models/users.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from beanie import Document
2+
3+
from pydantic import BaseModel, EmailStr
4+
5+
class User(Document):
6+
email: EmailStr
7+
password: str
8+
9+
class Collection:
10+
name = "users"
11+
12+
class Config:
13+
schema_extra = {
14+
"example": {
15+
"email": "fastapi@packt.com",
16+
"password": "strong!!!"
17+
}
18+
}
19+
20+
class TokenResponse(BaseModel):
21+
access_token: str
22+
token_type: str

0 commit comments

Comments
 (0)