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 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 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.services.users.users import PublicUser
from src.services.courses.collections import (
@ -17,13 +19,14 @@ router = APIRouter()
@router.post("/")
async def api_create_collection(
request: Request,
collection_object: Collection,
collection_object: CollectionCreate,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
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}")
@ -31,11 +34,12 @@ async def api_get_collection(
request: Request,
collection_id: str,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
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}")
@ -45,26 +49,25 @@ async def api_get_collections_by(
limit: int,
org_id: str,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
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}")
async def api_update_collection(
request: Request,
collection_object: Collection,
collection_id: str,
collection_object: CollectionUpdate,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
Update collection by ID
"""
return await update_collection(
request, collection_object, collection_id, current_user
)
return await update_collection(request, collection_object, current_user, db_session)
@router.delete("/{collection_id}")
@ -72,9 +75,10 @@ async def api_delete_collection(
request: Request,
collection_id: str,
current_user: PublicUser = Depends(get_current_user),
db_session=Depends(get_db_session),
):
"""
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 uuid import uuid4
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 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
@ -29,134 +33,164 @@ class CollectionInDB(Collection):
async def get_collection(
request: Request, collection_id: str, current_user: PublicUser
):
collections = request.app.db["collections"]
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"]
)
request: Request, collection_id: str, current_user: PublicUser, db_session: Session
) -> CollectionRead:
statement = select(Collection).where(Collection.id == collection_id)
collection = db_session.exec(statement).first()
if not collection:
raise HTTPException(
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
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)
]
collection = CollectionRead(**collection.dict(), courses=courses)
return collection
async def create_collection(
request: Request, collection_object: Collection, current_user: PublicUser
):
collections = request.app.db["collections"]
request: Request,
collection_object: CollectionCreate,
current_user: PublicUser,
db_session: Session,
) -> CollectionRead:
collection = Collection.from_orm(collection_object)
# find if collection already exists using name
isCollectionNameAvailable = await collections.find_one(
{"name": collection_object.name}
# Complete the collection object
collection.collection_uuid = f"collection_{uuid4()}"
collection.creation_date = str(datetime.now())
collection.update_date = str(datetime.now())
# Add collection to database
db_session.add(collection)
db_session.commit()
db_session.refresh(collection)
# Link courses to collection
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)
# TODO
# await verify_collection_rights("*", current_user, "create")
db_session.commit()
db_session.refresh(collection)
if isCollectionNameAvailable:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Collection name already exists",
# Get courses once again
statement = (
select(Course)
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
.distinct(Course.id)
)
courses = db_session.exec(statement).all()
# generate collection_id with uuid4
collection_id = str(f"collection_{uuid4()}")
collection = CollectionRead(**collection.dict(), courses=courses)
collection = CollectionInDB(
collection_id=collection_id,
authors=[current_user.user_id],
**collection_object.dict(),
)
collection_in_db = await collections.insert_one(collection.dict())
if not collection_in_db:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Unavailable database",
)
return collection.dict()
return CollectionRead.from_orm(collection)
async def update_collection(
request: Request,
collection_object: Collection,
collection_id: str,
collection_object: CollectionUpdate,
current_user: PublicUser,
):
# verify collection rights
collections = request.app.db["collections"]
collection = await collections.find_one({"collection_id": collection_id})
await verify_collection_rights(
request, collection_id, current_user, "update", collection["org_id"]
db_session: Session,
) -> CollectionRead:
statement = select(Collection).where(
Collection.id == collection_object.collection_id
)
collection = db_session.exec(statement).first()
if not collection:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist"
)
updated_collection = CollectionInDB(
collection_id=collection_id, **collection_object.dict()
courses = collection_object.courses
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(
{"collection_id": collection_id}, {"$set": updated_collection.dict()}
)
courses = db_session.exec(statement).all()
return Collection(**updated_collection.dict())
collection = CollectionRead(**collection.dict(), courses=courses)
return 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"]
collection = await collections.find_one({"collection_id": collection_id})
await verify_collection_rights(
request, collection_id, current_user, "delete", collection["org_id"]
)
statement = select(Collection).where(Collection.id == collection_id)
collection = db_session.exec(statement).first()
if not collection:
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"}
else:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Unavailable database",
)
return {"detail": "Collection deleted"}
####################################################
@ -168,75 +202,30 @@ async def get_collections(
request: Request,
org_id: str,
current_user: PublicUser,
db_session: Session,
page: int = 1,
limit: int = 10,
):
collections = request.app.db["collections"]
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)
) -> List[CollectionRead]:
statement = (
select(Collection).where(Collection.org_id == org_id).distinct(Collection.id)
)
collections = db_session.exec(statement).all()
# 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 != "*":
if not collections:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Collection does not exist"
status_code=status.HTTP_409_CONFLICT, detail="No collections found"
)
# 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_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)
#### Security ####################################################
return collections_with_courses