mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
🎉 first commit
This commit is contained in:
parent
8c00f9a074
commit
91f4291d9b
21 changed files with 614 additions and 3 deletions
37
.gitignore
vendored
37
.gitignore
vendored
|
|
@ -20,7 +20,6 @@ parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
wheels/
|
wheels/
|
||||||
pip-wheel-metadata/
|
|
||||||
share/python-wheels/
|
share/python-wheels/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
|
|
@ -50,6 +49,7 @@ coverage.xml
|
||||||
*.py,cover
|
*.py,cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
|
|
@ -72,6 +72,7 @@ instance/
|
||||||
docs/_build/
|
docs/_build/
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
target/
|
target/
|
||||||
|
|
||||||
# Jupyter Notebook
|
# Jupyter Notebook
|
||||||
|
|
@ -82,7 +83,9 @@ profile_default/
|
||||||
ipython_config.py
|
ipython_config.py
|
||||||
|
|
||||||
# pyenv
|
# pyenv
|
||||||
.python-version
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
# pipenv
|
# pipenv
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
|
@ -91,7 +94,22 @@ ipython_config.py
|
||||||
# install all needed dependencies.
|
# install all needed dependencies.
|
||||||
#Pipfile.lock
|
#Pipfile.lock
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
__pypackages__/
|
__pypackages__/
|
||||||
|
|
||||||
# Celery stuff
|
# Celery stuff
|
||||||
|
|
@ -127,3 +145,16 @@ dmypy.json
|
||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#
|
||||||
|
FROM python:3.10.5
|
||||||
|
|
||||||
|
#
|
||||||
|
WORKDIR /usr/learnhouse
|
||||||
|
|
||||||
|
#
|
||||||
|
COPY ./requirements.txt /usr/learnhouse/requirements.txt
|
||||||
|
|
||||||
|
#
|
||||||
|
RUN pip install --no-cache-dir --upgrade -r /usr/learnhouse/requirements.txt
|
||||||
|
|
||||||
|
#
|
||||||
|
COPY ./ /usr/learnhouse
|
||||||
|
|
||||||
|
#
|
||||||
|
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80" , "--reload"]
|
||||||
0
__init__.py
Normal file
0
__init__.py
Normal file
20
app.py
Normal file
20
app.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
from typing import Union
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from src import main
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from src.main import global_router
|
||||||
|
import pymongo
|
||||||
|
|
||||||
|
# Init
|
||||||
|
app = FastAPI(
|
||||||
|
title="LearnHouse",
|
||||||
|
description="LearnHouse is a new open-source platform tailored for learning experiences.",
|
||||||
|
version="0.1.0",
|
||||||
|
root_path="/"
|
||||||
|
)
|
||||||
|
|
||||||
|
app.include_router(global_router)
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
async def root():
|
||||||
|
return {"Message": "Welcome to LearnHouse ✨"}
|
||||||
0
config/__init__.py
Normal file
0
config/__init__.py
Normal file
0
config/config.py
Normal file
0
config/config.py
Normal file
0
config/config.yml
Normal file
0
config/config.yml
Normal file
25
docker-compose.yml
Normal file
25
docker-compose.yml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
version: "3.9"
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "1338:80"
|
||||||
|
volumes:
|
||||||
|
- .:/usr/learnhouse
|
||||||
|
mongo:
|
||||||
|
image: mongo:5.0
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
environment:
|
||||||
|
- MONGO_INITDB_ROOT_USERNAME=learnhouse
|
||||||
|
- MONGO_INITDB_ROOT_PASSWORD=learnhouse
|
||||||
|
mongo-express:
|
||||||
|
image: mongo-express
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 8081:8081
|
||||||
|
environment:
|
||||||
|
ME_CONFIG_MONGODB_ADMINUSERNAME: learnhouse
|
||||||
|
ME_CONFIG_MONGODB_ADMINPASSWORD: learnhouse
|
||||||
|
ME_CONFIG_MONGODB_URL: mongodb://learnhouse:learnhouse@mongo:27017/
|
||||||
7
requirements.txt
Normal file
7
requirements.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
fastapi==0.78.0
|
||||||
|
pydantic>=1.8.0,<2.0.0
|
||||||
|
uvicorn>=0.15.0,<0.16.0
|
||||||
|
pymongo==4.1.1
|
||||||
|
python-multipart
|
||||||
|
python-jose
|
||||||
|
passlib
|
||||||
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
18
src/main.py
Normal file
18
src/main.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
from .routers import users
|
||||||
|
from fastapi import APIRouter
|
||||||
|
from .routers import users, auth, houses
|
||||||
|
from starlette.responses import FileResponse
|
||||||
|
|
||||||
|
|
||||||
|
global_router = APIRouter(prefix="/api")
|
||||||
|
|
||||||
|
|
||||||
|
## API Routes
|
||||||
|
global_router.include_router(users.router, prefix="/users", tags=["users"])
|
||||||
|
global_router.include_router(auth.router, prefix="/auth", tags=["auth"])
|
||||||
|
global_router.include_router(houses.router, prefix="/houses", tags=["houses"])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
0
src/routers/__init__.py
Normal file
0
src/routers/__init__.py
Normal file
27
src/routers/auth.py
Normal file
27
src/routers/auth.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
from fastapi import Depends, FastAPI, APIRouter, HTTPException, status
|
||||||
|
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from ..services.auth import *
|
||||||
|
from ..services.users import *
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/token", response_model=Token)
|
||||||
|
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||||
|
"""
|
||||||
|
OAuth2 compatible token login, get access token for future requests
|
||||||
|
"""
|
||||||
|
user = await authenticate_user(form_data.username, form_data.password)
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Incorrect username or password",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||||
|
access_token = create_access_token(
|
||||||
|
data={"sub": user.username}, expires_delta=access_token_expires
|
||||||
|
)
|
||||||
|
return {"access_token": access_token, "token_type": "bearer"}
|
||||||
49
src/routers/houses.py
Normal file
49
src/routers/houses.py
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from src.services.auth import get_current_user
|
||||||
|
|
||||||
|
from src.services.houses import House, HouseInDB, create_house, get_house, get_houses, update_house, delete_house
|
||||||
|
from src.services.users import User
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/")
|
||||||
|
async def api_create_house(house_object: House, current_user: User = Depends(get_current_user)):
|
||||||
|
"""
|
||||||
|
Create new house
|
||||||
|
"""
|
||||||
|
return await create_house(house_object, current_user)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{house_id}")
|
||||||
|
async def api_get_house(house_id: str):
|
||||||
|
"""
|
||||||
|
Get single House by house_id
|
||||||
|
"""
|
||||||
|
return await get_house(house_id)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/page/{house_id}/limit/{limit}")
|
||||||
|
async def api_get_house_by(page: int, limit: int):
|
||||||
|
"""
|
||||||
|
Get houses by page and limit
|
||||||
|
"""
|
||||||
|
return await get_houses(page, limit)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/{house_id}")
|
||||||
|
async def api_update_house(house_object: House, house_id: str, current_user: User = Depends(get_current_user)):
|
||||||
|
"""
|
||||||
|
Update House by house_id
|
||||||
|
"""
|
||||||
|
return await update_house(house_object, house_id, current_user)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{house_id}")
|
||||||
|
async def api_delete_house(house_id: str, current_user: User = Depends(get_current_user)):
|
||||||
|
"""
|
||||||
|
Delete House by ID
|
||||||
|
"""
|
||||||
|
|
||||||
|
return await delete_house(house_id, current_user)
|
||||||
51
src/routers/users.py
Normal file
51
src/routers/users.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
from fastapi import Depends, FastAPI, APIRouter
|
||||||
|
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from ..services.auth import *
|
||||||
|
from ..services.users import *
|
||||||
|
|
||||||
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token")
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/me")
|
||||||
|
async def api_get_current_user(current_user: User = Depends(get_current_user)):
|
||||||
|
"""
|
||||||
|
Get current user
|
||||||
|
"""
|
||||||
|
return current_user.dict()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/username/{username}")
|
||||||
|
async def api_get_user_by_username(username: str):
|
||||||
|
"""
|
||||||
|
Get single user by username
|
||||||
|
"""
|
||||||
|
return await get_user(username)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/")
|
||||||
|
async def api_create_user(user_object: UserInDB):
|
||||||
|
"""
|
||||||
|
Create new user
|
||||||
|
"""
|
||||||
|
return await create_user(user_object)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/username/{username}")
|
||||||
|
async def api_delete_user(username: str):
|
||||||
|
"""
|
||||||
|
Delete user by ID
|
||||||
|
"""
|
||||||
|
|
||||||
|
return await delete_user(username)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/username/{username}")
|
||||||
|
async def api_update_user(user_object: UserInDB):
|
||||||
|
"""
|
||||||
|
Update user by ID
|
||||||
|
"""
|
||||||
|
return await update_user(user_object)
|
||||||
0
src/services/__init__.py
Normal file
0
src/services/__init__.py
Normal file
64
src/services/auth.py
Normal file
64
src/services/auth.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from fastapi import Depends, FastAPI, APIRouter, HTTPException, status
|
||||||
|
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||||
|
from passlib.context import CryptContext
|
||||||
|
from jose import JWTError, jwt
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from ..services.users import *
|
||||||
|
from ..services.security import *
|
||||||
|
|
||||||
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token")
|
||||||
|
|
||||||
|
#### Classes ####################################################
|
||||||
|
|
||||||
|
|
||||||
|
class Token(BaseModel):
|
||||||
|
access_token: str
|
||||||
|
token_type: str
|
||||||
|
|
||||||
|
|
||||||
|
class TokenData(BaseModel):
|
||||||
|
username: str | None = None
|
||||||
|
|
||||||
|
#### Classes ####################################################
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def authenticate_user(username: str, password: str):
|
||||||
|
user = await security_get_user(username)
|
||||||
|
if not user:
|
||||||
|
return False
|
||||||
|
if not await security_verify_password(password, user.password):
|
||||||
|
return False
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def create_access_token(data: dict, expires_delta: timedelta | None = None):
|
||||||
|
to_encode = data.copy()
|
||||||
|
if expires_delta:
|
||||||
|
expire = datetime.utcnow() + expires_delta
|
||||||
|
else:
|
||||||
|
expire = datetime.utcnow() + timedelta(minutes=15)
|
||||||
|
to_encode.update({"exp": expire})
|
||||||
|
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||||
|
return encoded_jwt
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_user(token: str = Depends(oauth2_scheme)):
|
||||||
|
credentials_exception = HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Could not validate credentials",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||||
|
username: str = payload.get("sub")
|
||||||
|
if username is None:
|
||||||
|
raise credentials_exception
|
||||||
|
token_data = TokenData(username=username)
|
||||||
|
except JWTError:
|
||||||
|
raise credentials_exception
|
||||||
|
user = await get_user(username=token_data.username)
|
||||||
|
if user is None:
|
||||||
|
raise credentials_exception
|
||||||
|
return User(**user.dict())
|
||||||
27
src/services/database.py
Normal file
27
src/services/database.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import pymongo
|
||||||
|
|
||||||
|
# MongoDB
|
||||||
|
client = pymongo.MongoClient("mongodb://learnhouse:learnhouse@mongo:27017/")
|
||||||
|
learnhouseDB = client["learnhouse"]
|
||||||
|
|
||||||
|
|
||||||
|
async def create_database():
|
||||||
|
learnhouseDB = client["learnhouse"]
|
||||||
|
|
||||||
|
|
||||||
|
async def check_database():
|
||||||
|
# Check if database learnhouse exists
|
||||||
|
|
||||||
|
if "learnhouse" in client.list_database_names():
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
create_database()
|
||||||
|
|
||||||
|
|
||||||
|
async def create_config_collection():
|
||||||
|
# Create config collection if it doesn't exist
|
||||||
|
|
||||||
|
learnhouseDB = client["learnhouse"]
|
||||||
|
config = learnhouseDB["config"]
|
||||||
|
config.insert_one({"name": "LearnHouse", "date": "2022"})
|
||||||
|
return config.find_one()
|
||||||
144
src/services/houses.py
Normal file
144
src/services/houses.py
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
import json
|
||||||
|
from typing import List
|
||||||
|
from uuid import uuid4
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from src.services.users import User
|
||||||
|
from ..services.database import create_config_collection, check_database, create_database, learnhouseDB, learnhouseDB
|
||||||
|
from ..services.security import *
|
||||||
|
from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
#### Classes ####################################################
|
||||||
|
|
||||||
|
|
||||||
|
class House(BaseModel):
|
||||||
|
name: str
|
||||||
|
photo: str
|
||||||
|
description: str
|
||||||
|
email: str
|
||||||
|
org: str
|
||||||
|
|
||||||
|
|
||||||
|
class HouseInDB(House):
|
||||||
|
house_id: str
|
||||||
|
owners: List[str]
|
||||||
|
|
||||||
|
#### Classes ####################################################
|
||||||
|
|
||||||
|
|
||||||
|
async def get_house(house_id: str):
|
||||||
|
await check_database()
|
||||||
|
houses = learnhouseDB["houses"]
|
||||||
|
|
||||||
|
house = houses.find_one({"house_id": house_id})
|
||||||
|
|
||||||
|
if not house:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT, detail="House does not exist")
|
||||||
|
|
||||||
|
house = House(**house)
|
||||||
|
return house
|
||||||
|
|
||||||
|
|
||||||
|
async def create_house(house_object: House, current_user: User):
|
||||||
|
await check_database()
|
||||||
|
houses = learnhouseDB["houses"]
|
||||||
|
|
||||||
|
# find if house already exists using name
|
||||||
|
isHouseAvailable = houses.find_one({"name": house_object.name})
|
||||||
|
|
||||||
|
if isHouseAvailable:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT, detail="House name already exists")
|
||||||
|
|
||||||
|
# generate house_id with uuid4
|
||||||
|
house_id = str(f"house_{uuid4()}")
|
||||||
|
|
||||||
|
house = HouseInDB(house_id=house_id, owners=[
|
||||||
|
current_user.username], **house_object.dict())
|
||||||
|
|
||||||
|
house_in_db = houses.insert_one(house.dict())
|
||||||
|
|
||||||
|
if not house_in_db:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
|
||||||
|
|
||||||
|
return house.dict()
|
||||||
|
|
||||||
|
|
||||||
|
async def update_house(house_object: House, house_id: str, current_user: User):
|
||||||
|
await check_database()
|
||||||
|
|
||||||
|
# verify house rights
|
||||||
|
await verify_house_ownership(house_id, current_user)
|
||||||
|
|
||||||
|
houses = learnhouseDB["houses"]
|
||||||
|
|
||||||
|
house = houses.find_one({"house_id": house_id})
|
||||||
|
|
||||||
|
## get owner value from house object database
|
||||||
|
owners = house["owners"]
|
||||||
|
|
||||||
|
if not house:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT, detail="House does not exist")
|
||||||
|
|
||||||
|
updated_house = HouseInDB(house_id=house_id, owners=owners, **house_object.dict())
|
||||||
|
|
||||||
|
houses.update_one({"house_id": house_id}, {"$set": updated_house.dict()})
|
||||||
|
|
||||||
|
return HouseInDB(**updated_house.dict())
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_house(house_id: str, current_user: User):
|
||||||
|
await check_database()
|
||||||
|
|
||||||
|
# verify house rights
|
||||||
|
await verify_house_ownership(house_id, current_user)
|
||||||
|
|
||||||
|
houses = learnhouseDB["houses"]
|
||||||
|
|
||||||
|
house = houses.find_one({"house_id": house_id})
|
||||||
|
|
||||||
|
if not house:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT, detail="House does not exist")
|
||||||
|
|
||||||
|
isDeleted = houses.delete_one({"house_id": house_id})
|
||||||
|
|
||||||
|
if isDeleted:
|
||||||
|
return {"detail": "House deleted"}
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
|
||||||
|
|
||||||
|
|
||||||
|
async def get_houses(page: int = 1, limit: int = 10):
|
||||||
|
await check_database()
|
||||||
|
houses = learnhouseDB["houses"]
|
||||||
|
|
||||||
|
# get all houses from database
|
||||||
|
all_houses = houses.find().sort("name", 1).skip(10 * (page - 1)).limit(limit)
|
||||||
|
|
||||||
|
return [json.loads(json.dumps(house, default=str)) for house in all_houses]
|
||||||
|
|
||||||
|
|
||||||
|
#### Security ####################################################
|
||||||
|
|
||||||
|
async def verify_house_ownership(house_id: str, current_user: User):
|
||||||
|
await check_database()
|
||||||
|
houses = learnhouseDB["houses"]
|
||||||
|
|
||||||
|
house = houses.find_one({"house_id": house_id})
|
||||||
|
|
||||||
|
if not house:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT, detail="House does not exist")
|
||||||
|
|
||||||
|
if current_user.username not in house["owners"]:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT, detail="User does not own this house")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
#### Security ####################################################
|
||||||
25
src/services/security.py
Normal file
25
src/services/security.py
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
from passlib.context import CryptContext
|
||||||
|
from jose import JWTError, jwt
|
||||||
|
from passlib.hash import pbkdf2_sha256
|
||||||
|
|
||||||
|
### 🔒 JWT ##############################################################
|
||||||
|
|
||||||
|
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||||
|
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||||
|
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
|
||||||
|
ALGORITHM = "HS256"
|
||||||
|
|
||||||
|
### 🔒 JWT ##############################################################
|
||||||
|
|
||||||
|
|
||||||
|
### 🔒 Passwords Hashing ##############################################################
|
||||||
|
|
||||||
|
async def security_hash_password(password: str):
|
||||||
|
return pbkdf2_sha256.hash(password)
|
||||||
|
|
||||||
|
|
||||||
|
async def security_verify_password(plain_password: str, hashed_password: str):
|
||||||
|
return pbkdf2_sha256.verify(plain_password, hashed_password)
|
||||||
|
|
||||||
|
### 🔒 Passwords Hashing ##############################################################
|
||||||
106
src/services/users.py
Normal file
106
src/services/users.py
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from ..services.database import create_config_collection, check_database, create_database, learnhouseDB, learnhouseDB
|
||||||
|
from ..services.security import *
|
||||||
|
from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
#### Classes ####################################################
|
||||||
|
|
||||||
|
|
||||||
|
class User(BaseModel):
|
||||||
|
username: str
|
||||||
|
email: str
|
||||||
|
full_name: str | None = None
|
||||||
|
disabled: bool | None = None
|
||||||
|
avatar_url: str | None = None
|
||||||
|
verified: bool
|
||||||
|
created_date: str
|
||||||
|
bio : str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class UserInDB(User):
|
||||||
|
password: str
|
||||||
|
|
||||||
|
#### Classes ####################################################
|
||||||
|
|
||||||
|
|
||||||
|
async def get_user(username: str):
|
||||||
|
check_database()
|
||||||
|
users = learnhouseDB["users"]
|
||||||
|
|
||||||
|
user = users.find_one({"username": username})
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT, detail="User does not exist")
|
||||||
|
|
||||||
|
user = User(**user)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
async def security_get_user(username: str):
|
||||||
|
check_database()
|
||||||
|
users = learnhouseDB["users"]
|
||||||
|
|
||||||
|
user = users.find_one({"username": username})
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT, detail="User does not exist")
|
||||||
|
|
||||||
|
return UserInDB(**user)
|
||||||
|
|
||||||
|
|
||||||
|
async def update_user(user_object: UserInDB):
|
||||||
|
check_database()
|
||||||
|
users = learnhouseDB["users"]
|
||||||
|
|
||||||
|
isUserAvailable = users.find_one({"username": user_object.username})
|
||||||
|
|
||||||
|
if not isUserAvailable:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT, detail="User does not exist")
|
||||||
|
|
||||||
|
user_object.password = security_hash_password(user_object.password)
|
||||||
|
|
||||||
|
updated_user = {"$set": user_object.dict()}
|
||||||
|
users.update_one({"username": user_object.username}, updated_user)
|
||||||
|
|
||||||
|
return User(**user_object.dict())
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_user(username: str):
|
||||||
|
check_database()
|
||||||
|
users = learnhouseDB["users"]
|
||||||
|
|
||||||
|
isUserAvailable = users.find_one({"username": username})
|
||||||
|
|
||||||
|
if not isUserAvailable:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT, detail="User does not exist")
|
||||||
|
|
||||||
|
users.delete_one({"username": username})
|
||||||
|
|
||||||
|
return {"detail": "User deleted"}
|
||||||
|
|
||||||
|
|
||||||
|
async def create_user(user_object: UserInDB):
|
||||||
|
check_database()
|
||||||
|
users = learnhouseDB["users"]
|
||||||
|
|
||||||
|
isUserAvailable = users.find_one({"username": user_object.username})
|
||||||
|
|
||||||
|
if isUserAvailable:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_409_CONFLICT, detail="User already exists")
|
||||||
|
|
||||||
|
# lowercase username
|
||||||
|
user_object.username = user_object.username.lower()
|
||||||
|
|
||||||
|
user_object.created_date = str(datetime.now())
|
||||||
|
|
||||||
|
user_object.password = await security_hash_password(user_object.password)
|
||||||
|
|
||||||
|
users.insert_one(user_object.dict())
|
||||||
|
|
||||||
|
return User(**user_object.dict())
|
||||||
Loading…
Add table
Add a link
Reference in a new issue