Skip to content

Commit

Permalink
Merge pull request #795 from bcgov/feat/w3c
Browse files Browse the repository at this point in the history
feat: add credential endpoints to api/v4
  • Loading branch information
amanji authored Sep 24, 2024
2 parents dbbb261 + 3ace5d3 commit 52b2675
Show file tree
Hide file tree
Showing 12 changed files with 126 additions and 76 deletions.
1 change: 0 additions & 1 deletion server/vcr-server/api/v4/search/filters/autocomplete.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# DEPRECATED: this should not be used in new code and will be removed imminently

from api.v3.search_filters import AutocompleteFilter as V3AutocompleteFilter
from api.v3.search_filters import get_autocomplete_builder

Expand Down
2 changes: 0 additions & 2 deletions server/vcr-server/api/v4/search/filters/credential.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from haystack.inputs import Clean, Exact, Raw

from api.v2.search.filters import CredNameFilter, CredNameFilterBuilder


Expand Down
1 change: 0 additions & 1 deletion server/vcr-server/api/v4/search/filters/topic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from haystack.inputs import Clean, Exact, Raw
from drf_haystack.query import BaseQueryBuilder

from api.v2.search.filters import (
Expand Down
16 changes: 14 additions & 2 deletions server/vcr-server/api/v4/serializers/rest/credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,6 +19,8 @@ class Meta:

class CredentialAttributeSerializer(AttributeSerializer):
class Meta(AttributeSerializer.Meta):
ref_name = "V4CredentialAttributeSerializer"

fields = (
"id",
"type",
Expand All @@ -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)
Expand Down Expand Up @@ -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 {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# DEPRECATED: this should not be used in new code and will be removed imminently

from rest_framework.serializers import SerializerMethodField

from api.v3.indexes.Topic import TopicIndex
Expand Down
56 changes: 56 additions & 0 deletions server/vcr-server/api/v4/tests/test_rest_view_credential.py
Original file line number Diff line number Diff line change
@@ -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)
17 changes: 8 additions & 9 deletions server/vcr-server/api/v4/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 = [
Expand All @@ -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
35 changes: 35 additions & 0 deletions server/vcr-server/api/v4/views/rest/credential.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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}

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
6 changes: 1 addition & 5 deletions server/vcr-server/api/v4/views/rest/credential_type.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
from django.http import Http404, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet

from api.v2.models.CredentialType import CredentialType
Expand All @@ -15,7 +11,7 @@ class RestView(ReadOnlyModelViewSet):

def list(self, request):
paging = request.query_params.get("paging", None)
if (paging and paging == 'false'):
if paging and paging == "false":
self.pagination_class = None
response = super(ReadOnlyModelViewSet, self).list(request)
item_count = self.queryset.count()
Expand Down
6 changes: 1 addition & 5 deletions server/vcr-server/api/v4/views/rest/issuer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
from django.http import Http404, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet

from api.v2.models.CredentialType import Issuer
Expand All @@ -15,7 +11,7 @@ class RestView(ReadOnlyModelViewSet):

def list(self, request):
paging = request.query_params.get("paging", None)
if (paging and paging == 'false'):
if paging and paging == "false":
self.pagination_class = None
response = super(ReadOnlyModelViewSet, self).list(request)
item_count = self.queryset.count()
Expand Down
28 changes: 9 additions & 19 deletions server/vcr-server/api/v4/views/rest/schema.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,22 @@
from django.http import Http404, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet
from django_filters import rest_framework as filters
import json

from drf_yasg.utils import swagger_auto_schema


from api.v2.models.CredentialType import Issuer, Schema
from api.v2.models.CredentialType import Schema

from api.v4.serializers.rest.credential import SchemaSerializer


class RestView(ReadOnlyModelViewSet):
serializer_class = SchemaSerializer
queryset = Schema.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ("name", "origin_did")

@swagger_auto_schema(responses={200: SchemaSerializer(many=True)})
def list(self, request):
data = SchemaSerializer(self.queryset, many=True).data
cred_type_x_schema = {}
for schema in data:
key = f"{schema['name']}:{schema['origin_did']}"
if not key in cred_type_x_schema:
cred_type_x_schema[key] = []
[cred_type_x_schema[key].append(x) for x in schema['credential_types']]
return Response(cred_type_x_schema)
paging = request.query_params.get("paging", None)
if paging and paging == "false":
self.pagination_class = None
response = super(ReadOnlyModelViewSet, self).list(request)
item_count = self.queryset.count()
response["item_count"] = item_count
return response
33 changes: 2 additions & 31 deletions server/vcr-server/api/v4/views/rest/topic.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,41 +77,12 @@ def credential_set_list(self, request, *args, **kwargs):
response["item_count"] = len(data)
return response

@swagger_auto_schema(operation_id="topic_type_read")
@action(
detail=False,
url_path="(?P<source_id>[^/.]+)/type/(?P<type>[^/]+)",
methods=["get"],
)
def read(self, request, *args, **kwargs):
item = self.get_object()
return Response(self.get_serializer(item).data)

@action(
detail=False,
methods=["get"],
url_path="(?P<source_id>[^/.]+)/type/(?P<type>[^/]+)/credential/(?P<credential_id>[^/.]+)",
)
def raw_credential_read(self, request, *args, **kwargs):
item = self.get_object()

credential_id = self.kwargs.get("credential_id")

credential = item.credentials.filter(
format="vc_di", credential_id=credential_id, revoked=False
).first()

if not (credential and credential.raw_data):
raise Http404()

return Response(credential.raw_data, content_type="application/ld+json")

def get_object(self):
if self.kwargs.get("pk"):
return super(RestView, self).get_object()

source_id = self.kwargs.get("source_id")
type = self.kwargs.get("type")
source_id = self.kwargs.get("source_id")
if not type or not source_id:
raise Http404()

Expand All @@ -120,7 +91,7 @@ def get_object(self):
type = settings.CRED_TYPE_SYNONYMS[type.lower()]

queryset = self.filter_queryset(self.get_queryset())
obj = get_object_or_404(queryset, source_id=source_id, type=type)
obj = get_object_or_404(queryset, type=type, source_id=source_id)

# May raise a permission denied
self.check_object_permissions(self.request, obj)
Expand Down

0 comments on commit 52b2675

Please sign in to comment.