Browse Source

Port wizard app to ninja

David Burke 9 months ago
parent
commit
4fc43e23cd
4 changed files with 66 additions and 10 deletions
  1. 37 0
      apps/organizations_ext/schemas.py
  2. 7 2
      apps/projects/schema.py
  3. 8 8
      apps/wizard/api.py
  4. 14 0
      glitchtip/api/authentication.py

+ 37 - 0
apps/organizations_ext/schemas.py

@@ -0,0 +1,37 @@
+from datetime import datetime
+from typing import Optional
+
+from ninja import Field, ModelSchema
+
+from glitchtip.schema import CamelSchema
+
+from .models import Organization
+
+
+class OrganizationSchema(CamelSchema, ModelSchema):
+    date_created: datetime = Field(validation_alias="created")
+    status: dict[str, str]
+    avatar: dict[str, Optional[str]]
+    is_early_adopter: bool = False
+    require2FA: bool = False
+
+    class Meta:
+        model = Organization
+        fields = (
+            "id",
+            "name",
+            "slug",
+            "is_accepting_events",
+        )
+
+    @staticmethod
+    def resolve_status(obj):
+        return {"id": "active", "name": "active"}
+
+    @staticmethod
+    def resolve_avatar(obj):
+        return {"avatarType": "", "avatarUuid": None}
+
+    @staticmethod
+    def resolve_require2FA(obj):
+        return False

+ 7 - 2
apps/projects/schema.py

@@ -6,6 +6,7 @@ from ninja import Field, ModelSchema
 
 
 from glitchtip.schema import CamelSchema
 from glitchtip.schema import CamelSchema
 
 
+from apps.organizations_ext.schemas import OrganizationSchema
 from .models import Project, ProjectKey
 from .models import Project, ProjectKey
 
 
 
 
@@ -67,5 +68,9 @@ class ProjectKeySchema(CamelSchema, ModelSchema):
         }
         }
 
 
 
 
-class ProjectWithKeysSchema(ProjectSchema):
-    keys: list[ProjectKeySchema]
+class ProjectOrganizationSchema(ProjectSchema):
+    organization: OrganizationSchema
+
+
+class ProjectWithKeysSchema(ProjectOrganizationSchema):
+    keys: list[ProjectKeySchema] = Field(validation_alias="projectkey_set")

+ 8 - 8
apps/wizard/api.py

@@ -34,7 +34,7 @@ router = Router()
 
 
 
 
 @router.get("wizard/", response=SetupWizardSchema, auth=None)
 @router.get("wizard/", response=SetupWizardSchema, auth=None)
-def setup_wizard(request: AuthHttpRequest):
+def setup_wizard(request):
     wizard_hash = get_random_string(
     wizard_hash = get_random_string(
         64, allowed_chars=string.ascii_lowercase + string.digits
         64, allowed_chars=string.ascii_lowercase + string.digits
     )
     )
@@ -44,7 +44,7 @@ def setup_wizard(request: AuthHttpRequest):
 
 
 
 
 @router.get("wizard/{wizard_hash}/")
 @router.get("wizard/{wizard_hash}/")
-async def setup_wizard_hash(request: AuthHttpRequest, wizard_hash: str, auth=None):
+async def setup_wizard_hash(request, wizard_hash: str, auth=None):
     key = SETUP_WIZARD_CACHE_KEY + wizard_hash
     key = SETUP_WIZARD_CACHE_KEY + wizard_hash
     wizard_data = cache.get(key)
     wizard_data = cache.get(key)
 
 
@@ -52,11 +52,12 @@ async def setup_wizard_hash(request: AuthHttpRequest, wizard_hash: str, auth=Non
         raise Http404
         raise Http404
     elif wizard_data == SETUP_WIZARD_CACHE_EMPTY:
     elif wizard_data == SETUP_WIZARD_CACHE_EMPTY:
         raise HttpError(400)
         raise HttpError(400)
+
     return wizard_data
     return wizard_data
 
 
 
 
 @router.delete("wizard/{wizard_hash}/")
 @router.delete("wizard/{wizard_hash}/")
-def setup_wizard_delete(request: AuthHttpRequest, wizard_hash: str, auth=None):
+def setup_wizard_delete(request, wizard_hash: str, auth=None):
     cache.delete(SETUP_WIZARD_CACHE_KEY + wizard_hash)
     cache.delete(SETUP_WIZARD_CACHE_KEY + wizard_hash)
 
 
 
 
@@ -71,11 +72,10 @@ async def setup_wizard_set_token(request: AuthHttpRequest, payload: SetupWizardS
     user_id = request.auth.user_id
     user_id = request.auth.user_id
     projects = [
     projects = [
         project
         project
-        async for project in Project.objects.filter(
-            organization__users=user_id
-        ).annotate(
-            is_member=Count("team__members", filter=Q(team__members__id=user_id))
-        )[:50]
+        async for project in Project.objects.filter(organization__users=user_id)
+        .annotate(is_member=Count("team__members", filter=Q(team__members__id=user_id)))
+        .select_related("organization")
+        .prefetch_related("projectkey_set")[:50]
     ]
     ]
 
 
     scope = getattr(APIToken.scopes, "project:releases")
     scope = getattr(APIToken.scopes, "project:releases")

+ 14 - 0
glitchtip/api/authentication.py

@@ -4,6 +4,7 @@ from typing import Any, Literal, Optional
 from asgiref.sync import sync_to_async
 from asgiref.sync import sync_to_async
 from django.conf import settings
 from django.conf import settings
 from django.contrib.auth import SESSION_KEY
 from django.contrib.auth import SESSION_KEY
+from django.contrib.auth.models import AnonymousUser
 from django.http import HttpRequest
 from django.http import HttpRequest
 from ninja.security import HttpBearer
 from ninja.security import HttpBearer
 from ninja.security import SessionAuth as BaseSessionAuth
 from ninja.security import SessionAuth as BaseSessionAuth
@@ -48,6 +49,19 @@ class TokenAuth(HttpBearer):
     Store the token object under data for checking scopes permissions.
     Store the token object under data for checking scopes permissions.
     """
     """
 
 
+    def __call__(self, request: HttpRequest) -> Optional[Any]:
+        # Workaround https://github.com/vitalik/django-ninja/issues/989
+        if request.resolver_match and request.resolver_match.url_name in [
+            "setup_wizard_hash"
+        ]:
+            request.auth = None  # type: ignore
+            return self.get_anon()
+
+        return super().__call__(request)
+
+    async def get_anon(self):
+        return AnonymousUser()
+
     async def authenticate(self, request: HttpRequest, key: str) -> Optional[Auth]:
     async def authenticate(self, request: HttpRequest, key: str) -> Optional[Auth]:
         try:
         try:
             token = await APIToken.objects.aget(token=key, user__is_active=True)
             token = await APIToken.objects.aget(token=key, user__is_active=True)