diff --git a/etebase_fastapi/app.py b/etebase_fastapi/app.py index 0ee7aae..acfb42f 100644 --- a/etebase_fastapi/app.py +++ b/etebase_fastapi/app.py @@ -9,10 +9,14 @@ from fastapi import FastAPI, Request from .execptions import CustomHttpException from .authentication import authentication_router +from .collection import collection_router from .msgpack import MsgpackResponse app = FastAPI() -app.include_router(authentication_router, prefix="/api/v1/authentication") +VERSION = "v1" +BASE_PATH = f"/api/{VERSION}" +app.include_router(authentication_router, prefix=f"{BASE_PATH}/authentication") +app.include_router(collection_router, prefix=f"{BASE_PATH}/collection") app.add_middleware( CORSMiddleware, allow_origin_regex="https?://.*", allow_credentials=True, allow_methods=["*"], allow_headers=["*"] ) @@ -26,4 +30,4 @@ async def custom_exception_handler(request: Request, exc: CustomHttpException): if __name__ == "__main__": import uvicorn - uvicorn.run(app, host="0.0.0.0", port=8080) + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/etebase_fastapi/authentication.py b/etebase_fastapi/authentication.py index b1a3272..697f3f4 100644 --- a/etebase_fastapi/authentication.py +++ b/etebase_fastapi/authentication.py @@ -50,6 +50,9 @@ class LoginResponse(BaseModel): class Authentication(BaseModel): + class Config: + keep_untouched = (cached_property,) + response: bytes signature: bytes diff --git a/etebase_fastapi/collection.py b/etebase_fastapi/collection.py new file mode 100644 index 0000000..ec0125d --- /dev/null +++ b/etebase_fastapi/collection.py @@ -0,0 +1,72 @@ +import typing as t + +from django.contrib.auth import get_user_model +from django.db.models import Q +from django.db.models import QuerySet +from fastapi import APIRouter, Depends +from pydantic import BaseModel +from asgiref.sync import sync_to_async + +from django_etebase.models import Collection, Stoken, AccessLevels, CollectionMember +from .authentication import get_authenticated_user +from .msgpack import MsgpackRoute, MsgpackResponse + +User = get_user_model() +collection_router = APIRouter(route_class=MsgpackRoute) +default_queryset = Collection.objects.all() + + +class ListMulti(BaseModel): + collectionTypes: t.List[bytes] + + +class CollectionItemOut(BaseModel): + uid: str + + +class CollectionOut(BaseModel): + collectionKey: bytes + collectionType: bytes + accessLevel: AccessLevels + stoken: str + item: CollectionItemOut + + @classmethod + def from_orm_user(cls: t.Type["CollectionOut"], obj: Collection, user: User) -> "CollectionOut": + member: CollectionMember = obj.members.get(user=user) + collection_type = member.collectionType + return cls( + collectionType=collection_type and collection_type.uid, + collectionKey=member.encryptionKey, + accessLevel=member.accessLevel, + stoken=obj.stoken, + item=CollectionItemOut(uid=obj.main_item.uid), + ) + + +class ListResponse(BaseModel): + data: t.List[CollectionOut] + stoken: t.Optional[str] + done: bool + + +@sync_to_async +def list_common(queryset: QuerySet, stoken: t.Optional[str], user: User) -> MsgpackResponse: + data: t.List[CollectionOut] = [CollectionOut.from_orm_user(item, user) for item in queryset] + ret = ListResponse(data=data, stoken=stoken, done=True) + return MsgpackResponse(content=ret) + + +def get_collection_queryset(user: User, queryset: QuerySet) -> QuerySet: + return queryset.filter(members__user=user) + + +@collection_router.post("/list_multi/") +async def list_multi(limit: int, data: ListMulti, user: User = Depends(get_authenticated_user)): + queryset = get_collection_queryset(user, default_queryset) + # FIXME: Remove the isnull part once we attach collection types to all objects ("collection-type-migration") + queryset = queryset.filter( + Q(members__collectionType__uid__in=data.collectionTypes) | Q(members__collectionType__isnull=True) + ) + response = await list_common(queryset, None, user) + return response diff --git a/etebase_fastapi/collections.py b/etebase_fastapi/collections.py deleted file mode 100644 index e69de29..0000000