From 671731cf8787b1af0b8aea002658fed7193e33f7 Mon Sep 17 00:00:00 2001 From: Akiff Manji Date: Sat, 21 Sep 2024 13:51:06 -0700 Subject: [PATCH] feat: add credential endpoint with ability to return only raw data Signed-off-by: Akiff Manji --- .../api/v4/serializers/rest/credential.py | 16 +++++- .../api/v4/tests/test_rest_view_credential.py | 56 +++++++++++++++++++ server/vcr-server/api/v4/urls.py | 17 +++--- .../api/v4/views/rest/credential.py | 40 +++++++++++++ 4 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 server/vcr-server/api/v4/tests/test_rest_view_credential.py create mode 100644 server/vcr-server/api/v4/views/rest/credential.py diff --git a/server/vcr-server/api/v4/serializers/rest/credential.py b/server/vcr-server/api/v4/serializers/rest/credential.py index 5d92835e0..9aca9a183 100644 --- a/server/vcr-server/api/v4/serializers/rest/credential.py +++ b/server/vcr-server/api/v4/serializers/rest/credential.py @@ -6,7 +6,6 @@ from api.v2.models.Attribute import Attribute from api.v2.models.Credential import Credential from api.v2.models.CredentialType import CredentialType -from api.v2.models.Issuer import Issuer from api.v2.models.Schema import Schema from api.v2.serializers.rest import CredentialNameSerializer, IssuerSerializer @@ -20,6 +19,8 @@ class Meta: class CredentialAttributeSerializer(AttributeSerializer): class Meta(AttributeSerializer.Meta): + ref_name = "V4CredentialAttributeSerializer" + fields = ( "id", "type", @@ -43,9 +44,10 @@ class Meta: "processor_config", "highlighted_attributes", "credential_title", - "issuer" + "issuer", ) + class CredentialTypeExtendedSerializer(ModelSerializer): issuer = IssuerSerializer() has_logo = BooleanField(source="get_has_logo", read_only=True) @@ -110,3 +112,13 @@ class Meta: class RestSerializer(CredentialSerializer): credential_type = CredentialTypeExtendedSerializer() + + +class RawCredentialSerializer(ModelSerializer): + class Meta: + model = Credential + fields = ("raw_data",) + read_only_fields = fields + + def to_representation(self, instance): + return instance.raw_data or {} diff --git a/server/vcr-server/api/v4/tests/test_rest_view_credential.py b/server/vcr-server/api/v4/tests/test_rest_view_credential.py new file mode 100644 index 000000000..3ce952483 --- /dev/null +++ b/server/vcr-server/api/v4/tests/test_rest_view_credential.py @@ -0,0 +1,56 @@ +from django.urls import reverse +from rest_framework.test import APITestCase + +from api.v2.models import Credential, CredentialType, Issuer, Schema, Topic + + +class TestRestViewCredential(APITestCase): + def setUp(self) -> None: + """Add a test credential type and credential to the database.""" + + self.test_credential_type = CredentialType( + schema=Schema.objects.create( + name="test_schema", version="0.0.1", origin_did="a:did:123" + ), + issuer=Issuer.objects.create( + did="a:did:123", + name="Test Issuer 1", + abbreviation="TI-1", + email="", + url="", + ), + ) + self.test_credential_type.save() + + self.test_topic = Topic(source_id="test_source_id", type="test_topic_type") + self.test_topic.save() + + self.test_credential = Credential( + credential_id="test_credential", + credential_type=self.test_credential_type, + topic=self.test_topic, + ) + self.test_credential.save() + + def test_get_credential(self): + """Test that the API returns the correct credential data.""" + response = self.client.get("/api/v4/credential/test_credential") + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data.get("credential_id"), "test_credential") + self.assertEqual( + response.data.get("credential_type"), self.test_credential_type.id + ) + self.assertIn("attributes", response.data) + self.assertIn("names", response.data) + + def test_get_credential_raw(self): + """Test that the API returns the correct credential data when raw_data is requested""" + response = self.client.get("/api/v4/credential/test_credential?raw_data=true") + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data, {}) + self.assertNotIn("credential_id", response.data) + self.assertNotIn("credential_type", response.data) + self.assertNotIn("attributes", response.data) + self.assertNotIn("names", response.data) diff --git a/server/vcr-server/api/v4/urls.py b/server/vcr-server/api/v4/urls.py index 06c5451fb..207ee60d1 100644 --- a/server/vcr-server/api/v4/urls.py +++ b/server/vcr-server/api/v4/urls.py @@ -13,7 +13,7 @@ # DEPRECATED: this should not be used in new code and will be removed imminently autocomplete as search_autocomplete, ) -from api.v4.views.rest import credential_type, issuer, topic, schema +from api.v4.views.rest import credential, credential_type, issuer, topic, schema from api.v4.views.misc.contact import send_contact from api.v4.views.misc.feedback import send_feedback @@ -35,18 +35,18 @@ router = SimpleRouter(trailing_slash=False) -router.register(r"credential-type", - credential_type.RestView, "Credential Type") +router.register(r"credential", credential.RestView, "Credential") +router.register(r"credential-type", credential_type.RestView, "Credential Type") router.register(r"issuer", issuer.RestView, "Issuer") router.register(r"schema", schema.RestView, "Schema") router.register(r"topic", topic.RestView, "Topic") -router.register(r"search/credential", - search_credential.SearchView, "Credential Search") +router.register(r"search/credential", search_credential.SearchView, "Credential Search") router.register(r"search/topic", search_topic.SearchView, "Topic Search") router.register(r"search/fuzzy", search_fuzzy.SearchView, "Fuzzy Search") # DEPRECATED: this should not be used in new code and will be removed imminently -router.register(r"search/autocomplete", - search_autocomplete.SearchView, "Aggregate Autocomplete") +router.register( + r"search/autocomplete", search_autocomplete.SearchView, "Aggregate Autocomplete" +) # Misc endpoints miscPatterns = [ @@ -58,5 +58,4 @@ path("", schema_view.with_ui("swagger", cache_timeout=None), name="api-docs") ] -urlpatterns = format_suffix_patterns( - router.urls) + miscPatterns + swaggerPatterns +urlpatterns = format_suffix_patterns(router.urls) + miscPatterns + swaggerPatterns diff --git a/server/vcr-server/api/v4/views/rest/credential.py b/server/vcr-server/api/v4/views/rest/credential.py new file mode 100644 index 000000000..125c15f7f --- /dev/null +++ b/server/vcr-server/api/v4/views/rest/credential.py @@ -0,0 +1,40 @@ +from django.http import Http404 +from django.shortcuts import get_object_or_404 +from rest_framework import mixins +from rest_framework.response import Response +from rest_framework.viewsets import GenericViewSet + +from api.v2.models.Credential import Credential + +from api.v4.serializers.rest.credential import CredentialSerializer, RawCredentialSerializer + +class RestView(mixins.RetrieveModelMixin, GenericViewSet): + serializer_class = CredentialSerializer + queryset = Credential.objects.all() + lookup_field = "credential_id" + + def get_serializer_class(self): + if self.request.query_params.get("raw_data", None) == "true": + return RawCredentialSerializer + return CredentialSerializer + + def get_object(self): + credential_id = self.kwargs.get("credential_id", None) + + if not credential_id: + raise Http404() + + filter = {"credential_id": credential_id} + # If the input parameter is a pure int, treat as an internal database pk + try: + filter = {"pk": int(credential_id)} + except (ValueError, TypeError): + pass + + queryset = self.filter_queryset(self.get_queryset()) + obj = get_object_or_404(queryset, **filter) + + # May raise a permission denied + self.check_object_permissions(self.request, obj) + + return obj \ No newline at end of file