This is an automated email from the ASF dual-hosted git repository.

yasithdev pushed a commit to branch portal-trackd-credentials
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git

commit 341a70dc2b0501224b3a4b895d0b63c3d770a57a
Author: yasithdev <[email protected]>
AuthorDate: Mon Jun 8 19:14:13 2026 -0400

    feat(portal): repoint credential summary reads to gRPC (Track D, D2)
    
    Migrate CredentialSummaryViewSet reads (get_list, get_instance, ssh and
    password actions) from the Thrift client to the gRPC credential facade
    (get_all_credential_summaries / get_credential_summary). Write actions
    (create_ssh, create_password, destroy) stay on Thrift pending D3.
    
    Add a credential_summary adapter mapping the protobuf CredentialSummary
    to the Thrift attribute names the existing serializer reads, so the REST
    contract is unchanged. proto and Thrift SummaryType assign different
    integers to the same member name (proto SSH=1 vs Thrift SSH=0), so the
    adapter bridges the enum by NAME via a reusable _thrift_enum helper, and
    proto_summary_type converts the other direction for facade requests.
    CredentialSummarySerializer.userHasWriteAccess now resolves via the gRPC
    sharing helper.
    
    Verified: manage.py check clean; empty list path returns 200 against the
    live backend; structural adapter test (all Thrift fields, enum bridge by
    name); offline serializer render confirms type labels as SSH/PASSWD and
    persistedTime renders ISO.
---
 .../django_airavata/apps/api/grpc_adapters.py      | 49 ++++++++++++++++++++++
 .../django_airavata/apps/api/serializers.py        |  6 +--
 .../django_airavata/apps/api/views.py              | 31 +++++++-------
 3 files changed, 67 insertions(+), 19 deletions(-)

diff --git a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py 
b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
index dd4280f49..df6135482 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
@@ -13,6 +13,37 @@ serializers are made protobuf-native.
 
 from types import SimpleNamespace
 
+from airavata.model.credential.store.ttypes import SummaryType as 
_ThriftSummaryType
+
+
+def _thrift_enum(pb, field, thrift_enum):
+    """Map a protobuf enum field to the Thrift enum value of the SAME name.
+
+    proto and Thrift enums frequently assign different integers to the same
+    member name (e.g. ``SummaryType.SSH`` is 1 in proto but 0 in Thrift), so 
the
+    bridge must go by NAME, never by raw integer — otherwise an adapter would
+    silently mislabel one enum member as another.
+    """
+    enum_descriptor = pb.DESCRIPTOR.fields_by_name[field].enum_type
+    name = enum_descriptor.values_by_number[getattr(pb, field)].name
+    return getattr(thrift_enum, name)
+
+
+def proto_summary_type(thrift_summary_type):
+    """Thrift ``SummaryType`` -> proto ``SummaryType`` enum value (by name).
+
+    The credential facade's request messages take the proto enum value, so 
views
+    that still speak in Thrift ``SummaryType`` (e.g. for delete dispatch) 
convert
+    through here. Imported lazily so this module stays importable without the
+    gRPC SDK on the path (the SDK is required only once ``request.airavata`` is
+    actually used).
+    """
+    from airavata_sdk.generated.org.apache.airavata.model.credential.store 
import (
+        credential_store_pb2,
+    )
+    return credential_store_pb2.SummaryType.Value(
+        _ThriftSummaryType(thrift_summary_type).name)
+
 
 def project(pb):
     """gRPC ``Project`` protobuf -> ``ProjectSerializer`` (Thrift ``Project``) 
shape."""
@@ -55,3 +86,21 @@ def experiment_summary(pb):
         experimentStatus=pb.experiment_status,
         statusUpdateTime=pb.status_update_time or None,
     )
+
+
+def credential_summary(pb):
+    """gRPC ``CredentialSummary`` protobuf -> ``CredentialSummarySerializer`` 
shape."""
+    return SimpleNamespace(
+        # proto/Thrift SummaryType have different ints per name -> bridge by 
name
+        # so the serializer's ThriftEnumField labels it correctly and
+        # perform_destroy's ``instance.type == SummaryType.SSH`` (Thrift) 
holds.
+        type=_thrift_enum(pb, 'type', _ThriftSummaryType),
+        gatewayId=pb.gateway_id,
+        username=pb.username,
+        publicKey=pb.public_key,
+        # int64 epoch millis, like the Thrift field; the serializer's
+        # UTCPosixTimestampDateTimeField divides by 1000, so keep it an int.
+        persistedTime=pb.persisted_time,
+        token=pb.token,
+        description=pb.description,
+    )
diff --git a/airavata-django-portal/django_airavata/apps/api/serializers.py 
b/airavata-django-portal/django_airavata/apps/api/serializers.py
index 0a45162c3..901f99969 100644
--- a/airavata-django-portal/django_airavata/apps/api/serializers.py
+++ b/airavata-django-portal/django_airavata/apps/api/serializers.py
@@ -1811,10 +1811,8 @@ class CredentialSummarySerializer(
     userHasWriteAccess = serializers.SerializerMethodField()
 
     def get_userHasWriteAccess(self, credential_summary):
-        request = self.context['request']
-        return request.airavata_client.userHasAccess(
-            request.authz_token, credential_summary.token,
-            ResourcePermissionType.WRITE)
+        return user_has_access(
+            self.context['request'], credential_summary.token)
 
 
 class StoragePreferenceSerializer(
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py 
b/airavata-django-portal/django_airavata/apps/api/views.py
index 431d95d1b..ddb6e7fcd 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -1366,31 +1366,32 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin,
 class CredentialSummaryViewSet(APIBackedViewSet):
     serializer_class = serializers.CredentialSummarySerializer
 
+    def _credential_summaries(self, summary_type):
+        return [
+            grpc_adapters.credential_summary(s)
+            for s in 
self.request.airavata.credential.get_all_credential_summaries(
+                self.gateway_id, 
grpc_adapters.proto_summary_type(summary_type))
+        ]
+
     def get_list(self):
-        ssh_creds = self.request.airavata_client.getAllCredentialSummaries(
-            self.authz_token, SummaryType.SSH)
-        pwd_creds = self.request.airavata_client.getAllCredentialSummaries(
-            self.authz_token, SummaryType.PASSWD)
-        return ssh_creds + pwd_creds
+        return (self._credential_summaries(SummaryType.SSH) +
+                self._credential_summaries(SummaryType.PASSWD))
 
     def get_instance(self, lookup_value):
-        return self.request.airavata_client.getCredentialSummary(
-            self.authz_token, lookup_value)
+        return grpc_adapters.credential_summary(
+            self.request.airavata.credential.get_credential_summary(
+                lookup_value, self.gateway_id))
 
     @action(detail=False)
     def ssh(self, request):
-        summaries = self.request.airavata_client.getAllCredentialSummaries(
-            self.authz_token, SummaryType.SSH
-        )
-        serializer = self.get_serializer(summaries, many=True)
+        serializer = self.get_serializer(
+            self._credential_summaries(SummaryType.SSH), many=True)
         return Response(serializer.data)
 
     @action(detail=False)
     def password(self, request):
-        summaries = self.request.airavata_client.getAllCredentialSummaries(
-            self.authz_token, SummaryType.PASSWD
-        )
-        serializer = self.get_serializer(summaries, many=True)
+        serializer = self.get_serializer(
+            self._credential_summaries(SummaryType.PASSWD), many=True)
         return Response(serializer.data)
 
     @action(methods=['post'], detail=False)

Reply via email to