diff --git a/src/main.py b/src/main.py index 138c8f90..ef4d5405 100644 --- a/src/main.py +++ b/src/main.py @@ -1,18 +1,13 @@ -from .routers import users from fastapi import APIRouter -from .routers import users, auth, houses -from starlette.responses import FileResponse +from src.routers import users, auth, houses, orgs +from starlette.responses import FileResponse global_router = APIRouter(prefix="/api") -## API Routes +# 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"]) - - - - - +global_router.include_router(orgs.router, prefix="/orgs", tags=["orgs"]) diff --git a/src/routers/houses.py b/src/routers/houses.py index 3ba509c6..9f6a58af 100644 --- a/src/routers/houses.py +++ b/src/routers/houses.py @@ -24,7 +24,7 @@ async def api_get_house(house_id: str): return await get_house(house_id) -@router.get("/page/{house_id}/limit/{limit}") +@router.get("/page/{page}/limit/{limit}") async def api_get_house_by(page: int, limit: int): """ Get houses by page and limit diff --git a/src/routers/orgs.py b/src/routers/orgs.py new file mode 100644 index 00000000..3c54abf7 --- /dev/null +++ b/src/routers/orgs.py @@ -0,0 +1,48 @@ +from fastapi import APIRouter, Depends +from src.services.auth import get_current_user +from src.services.orgs import Organization, create_org, delete_org, get_organization, get_orgs, update_org +from src.services.users import User + + +router = APIRouter() + + +@router.post("/") +async def api_create_org(org_object: Organization, current_user: User = Depends(get_current_user)): + """ + Create new organization + """ + return await create_org(org_object, current_user) + + +@router.get("/{org_id}") +async def api_get_org(org_id: str): + """ + Get single Org by ID + """ + return await get_organization(org_id) + + +@router.get("/page/{page}/limit/{limit}") +async def api_get_org_by(page: int, limit: int): + """ + Get orgs by page and limit + """ + return await get_orgs(page, limit) + + +@router.put("/{org_id}") +async def api_update_org(org_object: Organization, org_id: str, current_user: User = Depends(get_current_user)): + """ + Update Org by ID + """ + return await update_org(org_object, org_id, current_user) + + +@router.delete("/{org_id}") +async def api_delete_org(org_id: str, current_user: User = Depends(get_current_user)): + """ + Delete Org by ID + """ + + return await delete_org(org_id, current_user) diff --git a/src/services/houses.py b/src/services/houses.py index edca4128..6bd8eadc 100644 --- a/src/services/houses.py +++ b/src/services/houses.py @@ -22,6 +22,7 @@ class House(BaseModel): class HouseInDB(House): house_id: str owners: List[str] + admins: List[str] #### Classes #################################################### @@ -55,6 +56,7 @@ async def create_house(house_object: House, current_user: User): house_id = str(f"house_{uuid4()}") house = HouseInDB(house_id=house_id, owners=[ + current_user.username], admins=[ current_user.username], **house_object.dict()) house_in_db = houses.insert_one(house.dict()) @@ -68,22 +70,24 @@ async def create_house(house_object: House, current_user: User): 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) - + await verify_house_rights(house_id, current_user) + houses = learnhouseDB["houses"] house = houses.find_one({"house_id": house_id}) - - ## get owner value from house object database + + # get owner value from house object database owners = house["owners"] + admins = house["admins"] 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()) + updated_house = HouseInDB( + house_id=house_id, owners=owners, admins=admins, **house_object.dict()) houses.update_one({"house_id": house_id}, {"$set": updated_house.dict()}) @@ -92,10 +96,10 @@ async def update_house(house_object: House, house_id: str, current_user: User): async def delete_house(house_id: str, current_user: User): await check_database() - + # verify house rights - await verify_house_ownership(house_id, current_user) - + await verify_house_rights(house_id, current_user) + houses = learnhouseDB["houses"] house = houses.find_one({"house_id": house_id}) @@ -125,7 +129,7 @@ async def get_houses(page: int = 1, limit: int = 10): #### Security #################################################### -async def verify_house_ownership(house_id: str, current_user: User): +async def verify_house_rights(house_id: str, current_user: User): await check_database() houses = learnhouseDB["houses"] @@ -135,9 +139,12 @@ async def verify_house_ownership(house_id: str, current_user: User): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="House does not exist") - if current_user.username not in house["owners"]: + isAdmin = current_user.username in house["admins"] + isOwner = current_user.username in house["owners"] + + if not isAdmin and not isOwner: raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="User does not own this house") + status_code=status.HTTP_403_FORBIDDEN, detail="You do not have rights to this house") return True diff --git a/src/services/orgs.py b/src/services/orgs.py new file mode 100644 index 00000000..5083d3a7 --- /dev/null +++ b/src/services/orgs.py @@ -0,0 +1,149 @@ +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 Organization(BaseModel): + name: str + description: str + email: str + + +class OrganizationInDB(Organization): + org_id: str + owners: List[str] + admins: List[str] + + +#### Classes #################################################### + + +async def get_organization(org_id: str): + await check_database() + orgs = learnhouseDB["organizations"] + + org = orgs.find_one({"org_id": org_id}) + + if not org: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist") + + org = Organization(**org) + return org + + +async def create_org(org_object: Organization, current_user: User): + await check_database() + orgs = learnhouseDB["organizations"] + + # find if org already exists using name + isOrgAvailable = orgs.find_one({"name": org_object.name}) + + if isOrgAvailable: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Organization name already exists") + + # generate org_id with uuid4 + org_id = str(f"org_{uuid4()}") + + org = OrganizationInDB(org_id=org_id, owners=[ + current_user.username], admins=[ + current_user.username], **org_object.dict()) + + org_in_db = orgs.insert_one(org.dict()) + + if not org_in_db: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database") + + return org.dict() + + +async def update_org(org_object: Organization, org_id: str, current_user: User): + await check_database() + + # verify org rights + await verify_org_rights(org_id, current_user) + + orgs = learnhouseDB["organizations"] + + org = orgs.find_one({"org_id": org_id}) + + # get owner & adminds value from org object database + owners = org["owners"] + admins = org["admins"] + + if not org: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist") + + updated_org = OrganizationInDB( + org_id=org_id, owners=owners, admins=admins, **org_object.dict()) + + orgs.update_one({"org_id": org_id}, {"$set": updated_org.dict()}) + + return Organization(**updated_org.dict()) + + +async def delete_org(org_id: str, current_user: User): + await check_database() + + await verify_org_rights(org_id, current_user) + + orgs = learnhouseDB["organizations"] + + org = orgs.find_one({"org_id": org_id}) + + if not org: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist") + + isDeleted = orgs.delete_one({"org_id": org_id}) + + if isDeleted: + return {"detail": "Org deleted"} + else: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database") + + +async def get_orgs(page: int = 1, limit: int = 10): + await check_database() + orgs = learnhouseDB["orgs"] + + # get all orgs from database + all_orgs = orgs.find().sort("name", 1).skip(10 * (page - 1)).limit(limit) + + return [json.loads(json.dumps(house, default=str)) for house in all_orgs] + + +#### Security #################################################### + +async def verify_org_rights(org_id: str, current_user: User): + await check_database() + orgs = learnhouseDB["organizations"] + + org = orgs.find_one({"org_id": org_id}) + + if not org: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist") + + isAdmin = current_user.username in org["admins"] + isOwner = current_user.username in org["owners"] + + if not isAdmin and not isOwner: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="You do not have rights to this organization") + + return True + +#### Security ####################################################