diff --git a/app/models/wallet.py b/app/models/wallet.py index c5771e290..b514d80b2 100644 --- a/app/models/wallet.py +++ b/app/models/wallet.py @@ -1,5 +1,43 @@ +from typing import List, Optional + +from aries_cloudcontroller.models.indy_cred_info import ( + IndyCredInfo as IndyCredInfoAcaPy, +) +from aries_cloudcontroller.models.vc_record import VCRecord as VCRecordAcaPy from pydantic import BaseModel, Field class SetDidEndpointRequest(BaseModel): endpoint: str = Field(...) + + +class VCRecord(VCRecordAcaPy): + credential_id: str = Field( + ..., alias="record_id", description="Credential identifier" + ) + record_id: str = Field( + ..., + alias="credential_id", + description="(deprecated - renamed to credential_id) Credential identifier", + deprecated=True, + ) + + +class VCRecordList(BaseModel): + results: Optional[List[VCRecord]] = None + + +class IndyCredInfo(IndyCredInfoAcaPy): + credential_id: str = Field( + ..., alias="referent", description="Credential identifier" + ) + referent: str = Field( + ..., + alias="credential_id", + description="(deprecated - renamed to credential_id) Credential identifier", + deprecated=True, + ) + + +class CredInfoList(BaseModel): + results: Optional[List[IndyCredInfo]] = None diff --git a/app/routes/wallet/credentials.py b/app/routes/wallet/credentials.py index 4bd785045..b0af2c1d3 100644 --- a/app/routes/wallet/credentials.py +++ b/app/routes/wallet/credentials.py @@ -1,12 +1,8 @@ -from typing import Optional +from typing import List, Optional from aries_cloudcontroller import ( AttributeMimeTypesResult, - CredInfoList, CredRevokedResult, - IndyCredInfo, - VCRecord, - VCRecordList, W3CCredentialsListRequest, ) from fastapi import APIRouter, Depends @@ -14,6 +10,8 @@ from app.dependencies.acapy_clients import client_from_auth from app.dependencies.auth import AcaPyAuth, acapy_auth_from_header from app.exceptions import handle_acapy_call +from app.models.wallet import CredInfoList, IndyCredInfo, VCRecord, VCRecordList +from app.util.pagination import limit_query_parameter, offset_query_parameter from shared.log_config import get_logger logger = get_logger(__name__) @@ -21,14 +19,41 @@ router = APIRouter(prefix="/v1/wallet/credentials", tags=["wallet"]) -@router.get("", response_model=CredInfoList) +@router.get( + "", + response_model=CredInfoList, + summary="Fetch a list of credentials from the wallet", +) async def list_credentials( - count: Optional[str] = None, - start: Optional[str] = None, + limit: Optional[int] = limit_query_parameter, + offset: Optional[int] = offset_query_parameter, wql: Optional[str] = None, auth: AcaPyAuth = Depends(acapy_auth_from_header), ) -> CredInfoList: - """Fetch a list of credentials from the wallet.""" + """ + Fetch a list of credentials from the wallet + --- + + The `wql` (Wallet Query Language) parameter can be used to filter credentials returned from the wallet. + + The following string will look for the credential with the attribute `age` with value `21`: + + {"attr::age::value": "21"} + + Optional Parameters: + --- + limit: int + The number of records to return. + offset: int + The number of records to skip before starting to return records. + wql: str + A WQL query to filter records. + + Returns: + --- + CredInfoList + A list of credential records. + """ logger.debug("GET request received: List credentials") async with client_from_auth(auth) as aries_controller: @@ -36,8 +61,8 @@ async def list_credentials( results = await handle_acapy_call( logger=logger, acapy_call=aries_controller.credentials.get_records, - count=count, - start=start, + count=str(limit), + start=str(offset), wql=wql, ) @@ -45,12 +70,29 @@ async def list_credentials( return results -@router.get("/{credential_id}", response_model=IndyCredInfo) +@router.get( + "/{credential_id}", + response_model=IndyCredInfo, + summary="Fetch a credential by ID", +) async def get_credential_record( credential_id: str, auth: AcaPyAuth = Depends(acapy_auth_from_header), ) -> IndyCredInfo: - """Fetch a specific credential by ID.""" + """ + Fetch a specific credential by ID + --- + + Parameters: + --- + credential_id: str + The ID of the credential to fetch. + + Returns: + --- + IndyCredInfo + The credential record. + """ bound_logger = logger.bind(credential_id=credential_id) bound_logger.debug("GET request received: Fetch specific credential by ID") @@ -66,12 +108,24 @@ async def get_credential_record( return result -@router.delete("/{credential_id}", status_code=204) +@router.delete("/{credential_id}", status_code=204, summary="Delete a credential by ID") async def delete_credential( credential_id: str, auth: AcaPyAuth = Depends(acapy_auth_from_header), ) -> None: - """Remove a specific credential from the wallet by ID.""" + """ + Remove a specific credential from the wallet by ID + --- + + Parameters: + --- + credential_id: str + The ID of the credential to delete. + + Returns: + --- + status_code: 204 + """ bound_logger = logger.bind(credential_id=credential_id) bound_logger.debug("DELETE request received: Remove specific credential by ID") @@ -86,12 +140,29 @@ async def delete_credential( bound_logger.debug("Successfully deleted credential.") -@router.get("/{credential_id}/mime-types", response_model=AttributeMimeTypesResult) +@router.get( + "/{credential_id}/mime-types", + response_model=AttributeMimeTypesResult, + summary="Retrieve attribute MIME types of a credential", +) async def get_credential_mime_types( credential_id: str, auth: AcaPyAuth = Depends(acapy_auth_from_header), ) -> AttributeMimeTypesResult: - """Retrieve attribute MIME types of a specific credential by ID.""" + """ + Retrieve attribute MIME types of a specific credential by ID + --- + + Parameters: + --- + credential_id: str + The ID of the credential to fetch attribute MIME types for. + + Returns: + --- + AttributeMimeTypesResult + The attribute MIME types of the credential. + """ bound_logger = logger.bind(credential_id=credential_id) bound_logger.debug( "GET request received: Retrieve attribute MIME types for a specific credential" @@ -109,14 +180,39 @@ async def get_credential_mime_types( return result -@router.get("/{credential_id}/revocation-status", response_model=CredRevokedResult) +@router.get( + "/{credential_id}/revocation-status", + response_model=CredRevokedResult, + summary="Get revocation status of a credential", +) async def get_credential_revocation_status( credential_id: str, from_: Optional[str] = None, to: Optional[str] = None, auth: AcaPyAuth = Depends(acapy_auth_from_header), ) -> CredRevokedResult: - """Query the revocation status of a specific credential by ID.""" + """ + Query the revocation status of a specific credential by ID + --- + + The revocation status of a credential can be queried over a specific time range + by passing unix timestamps to the `from_` and `to` parameters. + Leaving these parameters blank will return the current revocation status. + + Parameters: + --- + credential_id: str + The ID of the credential to query revocation status for. + from_: Optional[str] + The timestamp to start the query from. + to: Optional[str] + The timestamp to end the query at. + + Returns: + --- + CredRevokedResult + The revocation status of the credential. + """ bound_logger = logger.bind(credential_id=credential_id) bound_logger.debug( "GET request received: Query revocation status for a specific credential" @@ -136,25 +232,50 @@ async def get_credential_revocation_status( return result -@router.get("/w3c", response_model=VCRecordList) +@router.get( + "/list/w3c", + response_model=VCRecordList, + summary="Fetch a list of W3C credentials from the wallet", +) async def list_w3c_credentials( - count: Optional[str] = None, - start: Optional[str] = None, - wql: Optional[str] = None, - body: Optional[W3CCredentialsListRequest] = None, + schema_ids: Optional[List[str]] = None, + issuer_did: Optional[str] = None, + limit: Optional[int] = None, auth: AcaPyAuth = Depends(acapy_auth_from_header), ) -> VCRecordList: - """Fetch a list of W3C credentials from the wallet.""" + """ + Fetch a list of W3C credentials from the wallet + --- + + The W3C credentials can be filtered by the parameters provided. + + Optional Parameters: + --- + schema_ids: List[str] + Schema identifiers to match + issuer_did: str + Credential issuer did to match + limit: int + Maximum number of results to return + + Returns: + --- + VCRecordList + A list of W3C credential records. + """ logger.debug("GET request received: List W3C credentials") + body = W3CCredentialsListRequest( + schema_ids=schema_ids, + issuer_id=issuer_did, + max_results=limit, + ) + async with client_from_auth(auth) as aries_controller: logger.debug("Fetching W3C credentials") results = await handle_acapy_call( logger=logger, acapy_call=aries_controller.credentials.get_w3c_credentials, - count=count, - start=start, - wql=wql, body=body, ) @@ -162,12 +283,29 @@ async def list_w3c_credentials( return results -@router.get("/w3c/{credential_id}", response_model=VCRecord) +@router.get( + "/w3c/{credential_id}", + response_model=VCRecord, + summary="Fetch a W3C credential by ID", +) async def get_w3c_credential( credential_id: str, auth: AcaPyAuth = Depends(acapy_auth_from_header), ) -> VCRecord: - """Fetch a specific W3C credential by ID.""" + """ + Fetch a specific W3C credential by ID + --- + + Parameters: + --- + credential_id: str + The ID of the W3C credential to fetch. + + Returns: + --- + VCRecord + The W3C credential. + """ bound_logger = logger.bind(credential_id=credential_id) bound_logger.debug("GET request received: Fetch specific W3C credential by ID") @@ -183,12 +321,24 @@ async def get_w3c_credential( return result -@router.delete("/w3c/{credential_id}", status_code=204) +@router.delete("/w3c/{credential_id}", status_code=204, summary="Delete W3C credential") async def delete_w3c_credential( credential_id: str, auth: AcaPyAuth = Depends(acapy_auth_from_header), ) -> None: - """Remove a specific W3C credential from the wallet by ID.""" + """ + Remove a specific W3C credential from the wallet by ID + --- + + Parameters: + --- + credential_id: str + The ID of the W3C credential to delete. + + Returns: + --- + status_code: 204 + """ bound_logger = logger.bind(credential_id=credential_id) bound_logger.debug("DELETE request received: Remove specific W3C credential by ID")