feat: init collections

This commit is contained in:
swve 2023-11-16 21:30:01 +01:00
parent 727f17ba7c
commit e6adbca562
4 changed files with 219 additions and 178 deletions

View file

@ -1,3 +1,40 @@
from typing import Optional from typing import Optional
from sqlmodel import Field, SQLModel from sqlmodel import Field, SQLModel
from enum import Enum
class CollectionBase(SQLModel):
name: str
public: bool
description: Optional[str] = ""
class Collection(CollectionBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
org_id: int = Field(default=None, foreign_key="organization.id")
collection_uuid: str = ""
creation_date: str = ""
update_date: str = ""
class CollectionCreate(CollectionBase):
courses: list
org_id: int = Field(default=None, foreign_key="organization.id")
pass
class CollectionUpdate(CollectionBase):
collection_id: int
courses: Optional[list]
name: Optional[str]
public: Optional[bool]
description: Optional[str]
class CollectionRead(CollectionBase):
id: int
courses: list
collection_uuid: str
creation_date: str
update_date: str
pass

View file

@ -0,0 +1,11 @@
from typing import Optional
from sqlmodel import Field, SQLModel
class CollectionCourse(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
collection_id: int = Field(default=None, foreign_key="collection.id")
course_id: int = Field(default=None, foreign_key="course.id")
org_id: int = Field(default=None, foreign_key="organization.id")
creation_date: str
update_date: str

View file

@ -1,4 +1,6 @@
from fastapi import APIRouter, Depends, Request from fastapi import APIRouter, Depends, Request
from src.core.events.database import get_db_session
from src.db.collections import CollectionCreate, CollectionUpdate
from src.security.auth import get_current_user from src.security.auth import get_current_user
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from src.services.courses.collections import ( from src.services.courses.collections import (
@ -17,13 +19,14 @@ router = APIRouter()
@router.post("/") @router.post("/")
async def api_create_collection( async def api_create_collection(
request: Request, request: Request,
collection_object: Collection, collection_object: CollectionCreate,
current_user: PublicUser = Depends(get_current_user), current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
): ):
""" """
Create new Collection Create new Collection
""" """
return await create_collection(request, collection_object, current_user) return await create_collection(request, collection_object, current_user, db_session)
@router.get("/{collection_id}") @router.get("/{collection_id}")
@ -31,11 +34,12 @@ async def api_get_collection(
request: Request, request: Request,
collection_id: str, collection_id: str,
current_user: PublicUser = Depends(get_current_user), current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
): ):
""" """
Get single collection by ID Get single collection by ID
""" """
return await get_collection(request, collection_id, current_user) return await get_collection(request, collection_id, current_user, db_session)
@router.get("/org_id/{org_id}/page/{page}/limit/{limit}") @router.get("/org_id/{org_id}/page/{page}/limit/{limit}")
@ -45,26 +49,25 @@ async def api_get_collections_by(
limit: int, limit: int,
org_id: str, org_id: str,
current_user: PublicUser = Depends(get_current_user), current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
): ):
""" """
Get collections by page and limit Get collections by page and limit
""" """
return await get_collections(request, org_id, current_user, page, limit) return await get_collections(request, org_id, current_user, db_session, page, limit)
@router.put("/{collection_id}") @router.put("/{collection_id}")
async def api_update_collection( async def api_update_collection(
request: Request, request: Request,
collection_object: Collection, collection_object: CollectionUpdate,
collection_id: str,
current_user: PublicUser = Depends(get_current_user), current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
): ):
""" """
Update collection by ID Update collection by ID
""" """
return await update_collection( return await update_collection(request, collection_object, current_user, db_session)
request, collection_object, collection_id, current_user
)
@router.delete("/{collection_id}") @router.delete("/{collection_id}")
@ -72,9 +75,10 @@ async def api_delete_collection(
request: Request, request: Request,
collection_id: str, collection_id: str,
current_user: PublicUser = Depends(get_current_user), current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
): ):
""" """
Delete collection by ID Delete collection by ID
""" """
return await delete_collection(request, collection_id, current_user) return await delete_collection(request, collection_id, current_user, db_session)

View file

@ -1,27 +1,31 @@
from datetime import datetime
from gc import collect
from typing import List, Literal from typing import List, Literal
from uuid import uuid4 from uuid import uuid4
from pydantic import BaseModel from pydantic import BaseModel
from src.security.rbac.rbac import authorization_verify_based_on_roles_and_authorship, authorization_verify_if_user_is_anon from sqlmodel import Session, select
from src.db.collections import (
Collection,
CollectionCreate,
CollectionRead,
CollectionUpdate,
)
from src.db.collections_courses import CollectionCourse
from src.db.courses import Course
from src.security.rbac.rbac import (
authorization_verify_based_on_roles_and_authorship,
authorization_verify_if_user_is_anon,
)
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from fastapi import HTTPException, status, Request from fastapi import HTTPException, status, Request
from typing import List
from fastapi import HTTPException, Request
from sqlmodel import Session, select
from src.db.collections import Collection
from src.db.courses import Course
from src.db.collections_courses import CollectionCourse
from src.services.users.users import PublicUser
#### Classes ####################################################
class Collection(BaseModel):
name: str
description: str
courses: List[str] # course_id
public: bool
org_id: str # org_id
class CollectionInDB(Collection):
collection_id: str
authors: List[str] # user_id
#### Classes ####################################################
#################################################### ####################################################
# CRUD # CRUD
@ -29,134 +33,164 @@ class CollectionInDB(Collection):
async def get_collection( async def get_collection(
request: Request, collection_id: str, current_user: PublicUser request: Request, collection_id: str, current_user: PublicUser, db_session: Session
): ) -> CollectionRead:
collections = request.app.db["collections"] statement = select(Collection).where(Collection.id == collection_id)
collection = db_session.exec(statement).first()
collection = await collections.find_one({"collection_id": collection_id})
# verify collection rights
await verify_collection_rights(
request, collection_id, current_user, "read", collection["org_id"]
)
if not collection: if not collection:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist" status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist"
) )
collection = Collection(**collection) # get courses in collection
statement = (
select(Course)
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
.distinct(Course.id)
)
courses = db_session.exec(statement).all()
# add courses to collection collection = CollectionRead(**collection.dict(), courses=courses)
courses = request.app.db["courses"]
courseids = [course for course in collection.courses]
collection.courses = []
collection.courses = courses.find({"course_id": {"$in": courseids}}, {"_id": 0})
collection.courses = [
course for course in await collection.courses.to_list(length=100)
]
return collection return collection
async def create_collection( async def create_collection(
request: Request, collection_object: Collection, current_user: PublicUser request: Request,
): collection_object: CollectionCreate,
collections = request.app.db["collections"] current_user: PublicUser,
db_session: Session,
) -> CollectionRead:
collection = Collection.from_orm(collection_object)
# find if collection already exists using name # Complete the collection object
isCollectionNameAvailable = await collections.find_one( collection.collection_uuid = f"collection_{uuid4()}"
{"name": collection_object.name} collection.creation_date = str(datetime.now())
) collection.update_date = str(datetime.now())
# TODO # Add collection to database
# await verify_collection_rights("*", current_user, "create") db_session.add(collection)
db_session.commit()
if isCollectionNameAvailable: db_session.refresh(collection)
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, # Link courses to collection
detail="Collection name already exists", for course in collection_object.courses:
collection_course = CollectionCourse(
collection_id=int(collection.id is not None),
course_id=int(course),
org_id=int(collection_object.org_id),
creation_date=str(datetime.now()),
update_date=str(datetime.now()),
) )
# Add collection_course to database
db_session.add(collection_course)
# generate collection_id with uuid4 db_session.commit()
collection_id = str(f"collection_{uuid4()}") db_session.refresh(collection)
collection = CollectionInDB( # Get courses once again
collection_id=collection_id, statement = (
authors=[current_user.user_id], select(Course)
**collection_object.dict(), .join(CollectionCourse, Course.id == CollectionCourse.course_id)
.distinct(Course.id)
) )
courses = db_session.exec(statement).all()
collection_in_db = await collections.insert_one(collection.dict()) collection = CollectionRead(**collection.dict(), courses=courses)
if not collection_in_db: return CollectionRead.from_orm(collection)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Unavailable database",
)
return collection.dict()
async def update_collection( async def update_collection(
request: Request, request: Request,
collection_object: Collection, collection_object: CollectionUpdate,
collection_id: str,
current_user: PublicUser, current_user: PublicUser,
): db_session: Session,
# verify collection rights ) -> CollectionRead:
statement = select(Collection).where(
collections = request.app.db["collections"] Collection.id == collection_object.collection_id
collection = await collections.find_one({"collection_id": collection_id})
await verify_collection_rights(
request, collection_id, current_user, "update", collection["org_id"]
) )
collection = db_session.exec(statement).first()
if not collection: if not collection:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist" status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist"
) )
updated_collection = CollectionInDB( courses = collection_object.courses
collection_id=collection_id, **collection_object.dict()
del collection_object.collection_id
del collection_object.courses
# Update only the fields that were passed in
for var, value in vars(collection_object).items():
if value is not None:
setattr(collection, var, value)
collection.update_date = str(datetime.now())
# Update only the fields that were passed in
for var, value in vars(collection_object).items():
if value is not None:
setattr(collection, var, value)
statement = select(CollectionCourse).where(
CollectionCourse.collection_id == collection.id
)
collection_courses = db_session.exec(statement).all()
# Delete all collection_courses
for collection_course in collection_courses:
db_session.delete(collection_course)
# Add new collection_courses
for course in courses or []:
collection_course = CollectionCourse(
collection_id=int(collection.id is not None),
course_id=int(course),
org_id=int(collection.org_id),
creation_date=str(datetime.now()),
update_date=str(datetime.now()),
)
# Add collection_course to database
db_session.add(collection_course)
db_session.commit()
db_session.refresh(collection)
# Get courses once again
statement = (
select(Course)
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
.distinct(Course.id)
) )
await collections.update_one( courses = db_session.exec(statement).all()
{"collection_id": collection_id}, {"$set": updated_collection.dict()}
)
return Collection(**updated_collection.dict()) collection = CollectionRead(**collection.dict(), courses=courses)
return collection
async def delete_collection( async def delete_collection(
request: Request, collection_id: str, current_user: PublicUser request: Request, collection_id: str, current_user: PublicUser, db_session: Session
): ):
collections = request.app.db["collections"] statement = select(Collection).where(Collection.id == collection_id)
collection = db_session.exec(statement).first()
collection = await collections.find_one({"collection_id": collection_id})
await verify_collection_rights(
request, collection_id, current_user, "delete", collection["org_id"]
)
if not collection: if not collection:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist" status_code=404,
detail="Collection not found",
) )
isDeleted = await collections.delete_one({"collection_id": collection_id}) # delete collection from database
db_session.delete(collection)
db_session.commit()
if isDeleted: return {"detail": "Collection deleted"}
return {"detail": "collection deleted"}
else:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Unavailable database",
)
#################################################### ####################################################
@ -168,75 +202,30 @@ async def get_collections(
request: Request, request: Request,
org_id: str, org_id: str,
current_user: PublicUser, current_user: PublicUser,
db_session: Session,
page: int = 1, page: int = 1,
limit: int = 10, limit: int = 10,
): ) -> List[CollectionRead]:
collections = request.app.db["collections"] statement = (
select(Collection).where(Collection.org_id == org_id).distinct(Collection.id)
if current_user.user_id == "anonymous":
all_collections = collections.find(
{"org_id": org_id, "public": True}, {"_id": 0}
)
else:
# get all collections from database without ObjectId
all_collections = (
collections.find({"org_id": org_id})
.sort("name", 1)
.skip(10 * (page - 1))
.limit(limit)
)
# create list of collections and include courses in each collection
collections_list = []
for collection in await all_collections.to_list(length=100):
collection = CollectionInDB(**collection)
collections_list.append(collection)
collection_courses = [course for course in collection.courses]
# add courses to collection
courses = request.app.db["courses"]
collection.courses = []
collection.courses = courses.find(
{"course_id": {"$in": collection_courses}}, {"_id": 0}
)
collection.courses = [
course for course in await collection.courses.to_list(length=100)
]
return collections_list
#### Security ####################################################
async def verify_collection_rights(
request: Request,
collection_id: str,
current_user: PublicUser,
action: Literal["create", "read", "update", "delete"],
org_id: str,
):
collections = request.app.db["collections"]
users = request.app.db["users"]
user = await users.find_one({"user_id": current_user.user_id})
collection = await collections.find_one({"collection_id": collection_id})
if not collection and action != "create" and collection_id != "*":
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist"
)
# Collections are public by default for now
if current_user.user_id == "anonymous" and action == "read":
return True
await authorization_verify_if_user_is_anon(current_user.user_id)
await authorization_verify_based_on_roles_and_authorship(
request, current_user.user_id, action, user["roles"], collection_id
) )
collections = db_session.exec(statement).all()
if not collections:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="No collections found"
)
#### Security #################################################### collections_with_courses = []
for collection in collections:
statement = (
select(Course)
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
.distinct(Course.id)
)
courses = db_session.exec(statement).all()
collection = CollectionRead(**collection.dict(), courses=courses)
collections_with_courses.append(collection)
return collections_with_courses