Skip to content

Commit 1fb7499

Browse files
committed
First commit
0 parents  commit 1fb7499

14 files changed

+1295
-0
lines changed

.editorconfig

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
root = true
2+
3+
# Unix-style newlines with a newline ending every file
4+
[*]
5+
insert_final_newline = true
6+
trim_trailing_whitespace = true
7+
end_of_line = lf
8+
charset = utf-8
9+
10+
# https://www.python.org/dev/peps/pep-0008/
11+
[*.py]
12+
indent_style = space
13+
indent_size = 4
14+
max_line_length = 119
15+
16+
[*.html]
17+
indent_style = tab
18+
indent_size = 2
19+
20+
[*.md]
21+
max_line_length = 119
22+
indent_size = 4
23+
indent_style = space

.env

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SQLALCHEMY_DATABASE_URL = 'postgresql://postgres:root@localhost/database_backup'

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DATABASE_URI = 'postgresql://postgres:<password>@localhost/<name_of_the_datbase>'

.gitignore

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.idea
2+
.ipynb_checkpoints
3+
.mypy_cache
4+
.vscode
5+
__pycache__
6+
.pytest_cache
7+
htmlcov
8+
dist
9+
site
10+
.coverage
11+
coverage.xml
12+
.netlify
13+
test.db
14+
log.txt
15+
Pipfile.lock
16+
env3.*
17+
env
18+
docs_build
19+
venv
20+
docs.zip
21+
archive.zip
22+
23+
# vim temporary files
24+
*~
25+
.*.sw?

app/__init__.py

Whitespace-only changes.

app/config.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from pydantic import BaseSettings
2+
3+
4+
class Settings(BaseSettings):
5+
SQLALCHEMY_DATABASE_URL: str
6+
APP_NAME: str = "DB Backup API"
7+
8+
class Config:
9+
case_sensitive = True
10+
env_file = ".env"
11+
12+
13+
settings = Settings() # type: ignore

app/crud.py

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from sqlalchemy.orm import Session
2+
3+
from . import models, schemas
4+
5+
6+
def create_user(db: Session, user: schemas.User):
7+
db_user = models.User(username=user.username, password=user.password)
8+
db.add(db_user)
9+
db.commit()
10+
db.refresh(db_user)
11+
return db_user
12+
13+
14+
def get_user(db: Session, username: str):
15+
return db.query(models.User).filter(models.User.username == username).first()

app/database.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from sqlalchemy import create_engine
2+
from sqlalchemy.ext.declarative import declarative_base
3+
from sqlalchemy.orm import sessionmaker
4+
5+
from .config import settings
6+
7+
engine = create_engine(settings.SQLALCHEMY_DATABASE_URL)
8+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
9+
10+
Base = declarative_base()

app/main.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from fastapi import FastAPI
2+
from fastapi.middleware.cors import CORSMiddleware
3+
4+
from .router import router
5+
6+
app = FastAPI()
7+
8+
origins = [
9+
"http://localhost:8080",
10+
"http://localhost:5173",
11+
]
12+
13+
app.add_middleware(
14+
CORSMiddleware,
15+
allow_origins=origins,
16+
allow_credentials=True,
17+
allow_methods=["*"],
18+
allow_headers=["*"],
19+
)
20+
21+
app.include_router(router)

app/models.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from sqlalchemy import Column, String, Integer
2+
3+
from .database import Base
4+
5+
6+
class User(Base):
7+
__tablename__ = "users"
8+
id = Column(Integer, primary_key=True)
9+
username = Column(String)
10+
password = Column(String)

app/router.py

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
from datetime import datetime, timedelta
2+
from typing import Annotated
3+
from jose import JWTError, jwt
4+
from fastapi import APIRouter, Depends, HTTPException, status
5+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
6+
7+
from . import crud, schemas
8+
from . import models
9+
from .database import SessionLocal, engine
10+
from passlib.context import CryptContext
11+
12+
# to get a string like this run:
13+
# openssl rand -hex 32
14+
SECRET_KEY = "89afa0ea05e272f5a746f466be7c256d982d307903e8201ad8f6c11450e71d7f"
15+
ALGORITHM = "HS256"
16+
ACCESS_TOKEN_EXPIRE_MINUTES = 24 * 60
17+
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
18+
19+
20+
models.Base.metadata.create_all(bind=engine)
21+
22+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
23+
24+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
25+
26+
27+
router = APIRouter()
28+
29+
30+
# Dependency
31+
"""
32+
def get_db():
33+
db = SessionLocal()
34+
try:
35+
yield db
36+
finally:
37+
db.close()
38+
"""
39+
40+
db = SessionLocal()
41+
42+
43+
def verify_password(plain_password, hashed_password):
44+
return pwd_context.verify(plain_password, hashed_password)
45+
46+
47+
def get_password_hash(password):
48+
return pwd_context.hash(password)
49+
50+
51+
def create_access_token(data: dict, expires_delta: timedelta | None = None):
52+
to_encode = data.copy()
53+
if expires_delta:
54+
expire = datetime.utcnow() + expires_delta
55+
else:
56+
expire = datetime.utcnow() + timedelta(minutes=15)
57+
to_encode.update({"exp": expire})
58+
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
59+
return encoded_jwt
60+
61+
62+
def authenticate_user(username: str, password: str):
63+
user = crud.get_user(db, username)
64+
if not user:
65+
return False
66+
if not verify_password(password, user.password):
67+
return False
68+
return user
69+
70+
71+
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
72+
credentials_exception = HTTPException(
73+
status_code=status.HTTP_401_UNAUTHORIZED,
74+
detail="Could not validate credentials",
75+
headers={"WWW-Authenticate": "Bearer"},
76+
)
77+
try:
78+
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
79+
username = payload.get("sub")
80+
if username is None:
81+
raise credentials_exception
82+
token_data = schemas.TokenData(username=username)
83+
except JWTError:
84+
raise credentials_exception
85+
user = crud.get_user(db, token_data.username)
86+
if user is None:
87+
raise credentials_exception
88+
return user
89+
90+
91+
@router.post("/token")
92+
async def login_for_access_token(
93+
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
94+
):
95+
user = authenticate_user(form_data.username, form_data.password)
96+
if not user:
97+
raise HTTPException(
98+
status_code=status.HTTP_401_UNAUTHORIZED,
99+
detail="Incorrect username or password",
100+
headers={"WWW-Authenticate": "Bearer"},
101+
)
102+
103+
access_token = create_access_token(
104+
data={"sub": user.username}, expires_delta=access_token_expires
105+
)
106+
return {"access_token": access_token, "token_type": "bearer"}
107+
108+
109+
@router.get("/users/me")
110+
async def read_users_me(
111+
current_user: Annotated[schemas.User, Depends(get_current_user)]
112+
):
113+
return current_user.username

app/schemas.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from pydantic import BaseModel
2+
3+
4+
class Token(BaseModel):
5+
access_token: str
6+
token_type: str
7+
8+
9+
class TokenData(BaseModel):
10+
username: str
11+
12+
13+
class User(BaseModel):
14+
username: str
15+
password: str
16+
17+
class Config:
18+
orm_mode = True

0 commit comments

Comments
 (0)