feat: orgs init + changes

This commit is contained in:
swve 2023-11-13 22:37:40 +01:00
parent aa0eda5682
commit afa8e4ea98
5 changed files with 242 additions and 181 deletions

View file

@ -12,10 +12,12 @@ class OrganizationBase(SQLModel):
class Organization(OrganizationBase, table=True): class Organization(OrganizationBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True) id: Optional[int] = Field(default=None, primary_key=True)
org_uuid: str org_uuid: str = ""
creation_date: str creation_date: str = ""
update_date: str update_date: str = ""
class OrganizationUpdate(OrganizationBase):
org_id: int
class OrganizationCreate(OrganizationBase): class OrganizationCreate(OrganizationBase):
pass pass

View file

@ -33,6 +33,8 @@ class UserUpdatePassword(SQLModel):
class UserRead(UserBase): class UserRead(UserBase):
id: int id: int
class PublicUser(UserRead):
pass
class User(UserBase, table=True): class User(UserBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True) id: Optional[int] = Field(default=None, primary_key=True)

View file

@ -1,63 +1,120 @@
from fastapi import APIRouter, Depends, Request, UploadFile from fastapi import APIRouter, Depends, Request, UploadFile
from sqlmodel import Session
from src.db.users import PublicUser
from src.db.organizations import OrganizationCreate, OrganizationUpdate
from src.core.events.database import get_db_session
from src.security.auth import get_current_user from src.security.auth import get_current_user
from src.services.orgs.orgs import Organization, create_org, delete_org, get_organization, get_organization_by_slug, get_orgs_by_user, update_org, update_org_logo from src.services.orgs.orgs import (
from src.services.users.users import PublicUser, User create_org,
delete_org,
get_organization,
get_organization_by_slug,
get_orgs_by_user,
update_org,
update_org_logo,
)
router = APIRouter() router = APIRouter()
@router.post("/") @router.post("/")
async def api_create_org(request: Request, org_object: Organization, current_user: PublicUser = Depends(get_current_user)): async def api_create_org(
request: Request,
org_object: OrganizationCreate,
current_user: PublicUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
):
""" """
Create new organization Create new organization
""" """
return await create_org(request, org_object, current_user) return await create_org(request, org_object, current_user, db_session)
@router.get("/{org_id}") @router.get("/{org_id}")
async def api_get_org(request: Request, org_id: str, current_user: PublicUser = Depends(get_current_user)): async def api_get_org(
request: Request,
org_id: str,
current_user: PublicUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
):
""" """
Get single Org by ID Get single Org by ID
""" """
return await get_organization(request, org_id) return await get_organization(request, org_id, db_session)
@router.get("/slug/{org_slug}") @router.get("/slug/{org_slug}")
async def api_get_org_by_slug(request: Request, org_slug: str, current_user: User = Depends(get_current_user)): async def api_get_org_by_slug(
request: Request,
org_slug: str,
current_user: PublicUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
):
""" """
Get single Org by Slug Get single Org by Slug
""" """
return await get_organization_by_slug(request, org_slug) return await get_organization_by_slug(request, org_slug, db_session)
@router.put("/{org_id}/logo") @router.put("/{org_id}/logo")
async def api_update_org_logo(request: Request, org_id: str, logo_file:UploadFile, current_user: PublicUser = Depends(get_current_user)): async def api_update_org_logo(
request: Request,
org_id: str,
logo_file: UploadFile,
current_user: PublicUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
):
""" """
Get single Org by Slug Get single Org by Slug
""" """
return await update_org_logo(request=request,logo_file=logo_file, org_id=org_id, current_user=current_user) return await update_org_logo(
request=request,
logo_file=logo_file,
org_id=org_id,
current_user=current_user,
db_session=db_session,
)
@router.get("/user/page/{page}/limit/{limit}") @router.get("/user/page/{page}/limit/{limit}")
async def api_user_orgs(request: Request, page: int, limit: int, current_user: PublicUser = Depends(get_current_user)): async def api_user_orgs(
request: Request,
page: int,
limit: int,
current_user: PublicUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
):
""" """
Get orgs by page and limit by user Get orgs by page and limit by user
""" """
return await get_orgs_by_user(request, current_user.user_id, page, limit) return await get_orgs_by_user(
request, db_session, str(current_user.id), page, limit
)
@router.put("/{org_id}") @router.put("/")
async def api_update_org(request: Request, org_object: Organization, org_id: str, current_user: PublicUser = Depends(get_current_user)): async def api_update_org(
request: Request,
org_object: OrganizationUpdate,
current_user: PublicUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
):
""" """
Update Org by ID Update Org by ID
""" """
return await update_org(request, org_object, org_id, current_user) return await update_org(request, org_object, current_user, db_session)
@router.delete("/{org_id}") @router.delete("/{org_id}")
async def api_delete_org(request: Request, org_id: str, current_user: PublicUser = Depends(get_current_user)): async def api_delete_org(
request: Request,
org_id: str,
current_user: PublicUser = Depends(get_current_user),
db_session: Session = Depends(get_db_session),
):
""" """
Delete Org by ID Delete Org by ID
""" """
return await delete_org(request, org_id, current_user) return await delete_org(request, org_id, current_user, db_session)

View file

@ -1,230 +1,230 @@
from datetime import datetime
import json import json
from operator import or_
from typing import Literal from typing import Literal
from uuid import uuid4 from uuid import uuid4
from sqlmodel import Session, select
from src.db.users import UserRead, PublicUser
from src.db.user_organizations import UserOrganization
from src.db.organizations import (
Organization,
OrganizationCreate,
OrganizationRead,
OrganizationUpdate,
)
from src.security.rbac.rbac import ( from src.security.rbac.rbac import (
authorization_verify_based_on_roles, authorization_verify_based_on_roles,
authorization_verify_if_user_is_anon, authorization_verify_if_user_is_anon,
) )
from src.services.orgs.logos import upload_org_logo from src.services.orgs.logos import upload_org_logo
from src.services.orgs.schemas.orgs import (
Organization,
OrganizationInDB,
PublicOrganization,
)
from src.services.users.schemas.users import UserOrganization
from src.services.users.users import PublicUser
from fastapi import HTTPException, UploadFile, status, Request from fastapi import HTTPException, UploadFile, status, Request
async def get_organization(request: Request, org_id: str): async def get_organization(request: Request, org_id: str, db_session: Session):
orgs = request.app.db["organizations"] statement = select(Organization).where(Organization.id == org_id)
result = db_session.exec(statement)
org = await orgs.find_one({"org_id": org_id}) org = result.first()
if not org: if not org:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist" status_code=404,
detail="Organization not found",
) )
org = PublicOrganization(**org)
return org return org
async def get_organization_by_slug(request: Request, org_slug: str): async def get_organization_by_slug(
orgs = request.app.db["organizations"] request: Request, org_slug: str, db_session: Session
):
statement = select(Organization).where(Organization.slug == org_slug)
result = db_session.exec(statement)
org = await orgs.find_one({"slug": org_slug}) org = result.first()
if not org: if not org:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist" status_code=404,
detail="Organization not found",
) )
org = PublicOrganization(**org)
return org return org
async def create_org( async def create_org(
request: Request, org_object: Organization, current_user: PublicUser request: Request,
org_object: OrganizationCreate,
current_user: PublicUser,
db_session: Session,
): ):
orgs = request.app.db["organizations"] statement = select(Organization).where(Organization.slug == org_object.slug)
user = request.app.db["users"] result = db_session.exec(statement)
# find if org already exists using name org = result.first()
isOrgAvailable = await orgs.find_one({"slug": org_object.slug})
if isOrgAvailable: if org:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Organization already exists",
)
org = Organization.from_orm(org_object)
# Complete the org object
org.org_uuid = f"org_{uuid4()}"
org.creation_date = str(datetime.now())
org.update_date = str(datetime.now())
db_session.add(org)
db_session.commit()
db_session.refresh(org)
# Link user to org
user_org = UserOrganization(
user_id=int(current_user.id),
org_id=int(org.id is not None),
role_id=1,
creation_date=str(datetime.now()),
update_date=str(datetime.now()),
)
db_session.add(user_org)
db_session.commit()
db_session.refresh(user_org)
return OrganizationRead.from_orm(org)
async def update_org(
request: Request,
org_object: OrganizationUpdate,
current_user: PublicUser,
db_session: Session,
):
statement = select(Organization).where(Organization.id == org_object.org_id)
result = db_session.exec(statement)
org = result.first()
if not org:
raise HTTPException(
status_code=404,
detail="Organization slug not found",
)
org = Organization.from_orm(org_object)
# Verify if the new slug is already in use
statement = select(Organization).where(Organization.slug == org_object.slug)
result = db_session.exec(statement)
slug_available = result.first()
if slug_available:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, status_code=status.HTTP_409_CONFLICT,
detail="Organization slug already exists", detail="Organization slug already exists",
) )
# generate org_id with uuid4 # Remove the org_id from the org_object
org_id = str(f"org_{uuid4()}") del org_object.org_id
# force lowercase slug # Update only the fields that were passed in
org_object.slug = org_object.slug.lower() for var, value in vars(org_object).items():
if value is not None:
setattr(org, var, value)
org = OrganizationInDB( # Complete the org object
org_id=org_id, **org_object.dict() org.update_date = str(datetime.now())
)
org_in_db = await orgs.insert_one(org.dict()) db_session.add(org)
db_session.commit()
db_session.refresh(org)
user_organization: UserOrganization = UserOrganization( return org
org_id=org_id, org_role="owner"
)
# add org to user
await user.update_one(
{"user_id": current_user.user_id},
{"$addToSet": {"orgs": user_organization.dict()}},
)
# add role admin to org
await user.update_one(
{"user_id": current_user.user_id},
{"$addToSet": {"roles": {"org_id": org_id, "role_id": "role_admin"}}},
)
if not org_in_db:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Unavailable database",
)
return org.dict()
async def update_org(
request: Request, org_object: Organization, org_id: str, current_user: PublicUser
):
# verify org rights
await verify_org_rights(request, org_id, current_user, "update")
orgs = request.app.db["organizations"]
await orgs.find_one({"org_id": org_id})
updated_org = OrganizationInDB(org_id=org_id, **org_object.dict())
# update org
await orgs.update_one({"org_id": org_id}, {"$set": updated_org.dict()})
return updated_org.dict()
async def update_org_logo( async def update_org_logo(
request: Request, logo_file: UploadFile, org_id: str, current_user: PublicUser request: Request,
logo_file: UploadFile,
org_id: str,
current_user: PublicUser,
db_session: Session,
): ):
# verify org rights statement = select(Organization).where(Organization.id == org_id)
await verify_org_rights(request, org_id, current_user, "update") result = db_session.exec(statement)
orgs = request.app.db["organizations"] org = result.first()
await orgs.find_one({"org_id": org_id}) if not org:
raise HTTPException(
status_code=404,
detail="Organization not found",
)
# Upload logo
name_in_disk = await upload_org_logo(logo_file, org_id) name_in_disk = await upload_org_logo(logo_file, org_id)
# update org # Update org
await orgs.update_one({"org_id": org_id}, {"$set": {"logo": name_in_disk}}) org.logo_image = name_in_disk
# Complete the org object
org.update_date = str(datetime.now())
db_session.add(org)
db_session.commit()
db_session.refresh(org)
return {"detail": "Logo updated"} return {"detail": "Logo updated"}
async def delete_org(request: Request, org_id: str, current_user: PublicUser): async def delete_org(
await verify_org_rights(request, org_id, current_user, "delete") request: Request, org_id: str, current_user: PublicUser, db_session: Session
):
statement = select(Organization).where(Organization.id == org_id)
result = db_session.exec(statement)
orgs = request.app.db["organizations"] org = result.first()
org = await orgs.find_one({"org_id": org_id})
if not org: if not org:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist" status_code=404,
detail="Organization not found",
) )
isDeleted = await orgs.delete_one({"org_id": org_id}) db_session.delete(org)
db_session.commit()
# remove org from all users # Delete links to org
users = request.app.db["users"] statement = select(UserOrganization).where(UserOrganization.org_id == org_id)
await users.update_many({}, {"$pull": {"orgs": {"org_id": org_id}}}) result = db_session.exec(statement)
if isDeleted: user_orgs = result.all()
return {"detail": "Org deleted"}
else: for user_org in user_orgs:
raise HTTPException( db_session.delete(user_org)
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, db_session.commit()
detail="Unavailable database",
) db_session.refresh(org)
return {"detail": "Organization deleted"}
async def get_orgs_by_user( async def get_orgs_by_user(
request: Request, user_id: str, page: int = 1, limit: int = 10
):
orgs = request.app.db["organizations"]
user = request.app.db["users"]
if user_id == "anonymous":
# raise error
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User not logged in"
)
# get user orgs
user_orgs = await user.find_one({"user_id": user_id})
org_ids: list[UserOrganization] = []
for org in user_orgs["orgs"]:
if (
org["org_role"] == "owner"
or org["org_role"] == "editor"
or org["org_role"] == "member"
):
org_ids.append(org["org_id"])
# find all orgs where org_id is in org_ids array
all_orgs = (
orgs.find({"org_id": {"$in": org_ids}})
.sort("name", 1)
.skip(10 * (page - 1))
.limit(100)
)
return [
json.loads(json.dumps(org, default=str))
for org in await all_orgs.to_list(length=100)
]
#### Security ####################################################
async def verify_org_rights(
request: Request, request: Request,
org_id: str, db_session: Session,
current_user: PublicUser, user_id: str,
action: Literal["create", "read", "update", "delete"], page: int = 1,
limit: int = 10,
): ):
orgs = request.app.db["organizations"] statement = (
users = request.app.db["users"] select(Organization)
.join(UserOrganization)
user = await users.find_one({"user_id": current_user.user_id}) .where(Organization.id == UserOrganization.org_id)
org = await orgs.find_one({"org_id": org_id})
if not org:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
) )
result = db_session.exec(statement)
await authorization_verify_if_user_is_anon(current_user.user_id) orgs = result.all()
await authorization_verify_based_on_roles( return orgs
request, current_user.user_id, action, user["roles"], org_id
)
#### Security ####################################################

View file

@ -3,8 +3,8 @@ from typing import List, Literal, Optional
from uuid import uuid4 from uuid import uuid4
from fastapi import HTTPException, Request, status from fastapi import HTTPException, Request, status
from pydantic import BaseModel from pydantic import BaseModel
from src.services.orgs.schemas.orgs import PublicOrganization
from src.services.courses.chapters import get_coursechapters_meta from src.services.courses.chapters import get_coursechapters_meta
from src.services.orgs.orgs import PublicOrganization
from src.services.users.users import PublicUser from src.services.users.users import PublicUser