Skip to content

Commit

Permalink
3064 embed extend ceramiccache model to include scorer id for stamps (#…
Browse files Browse the repository at this point in the history
…769)

* feat[api]: (WIP) adding fields to track source of stamps in CeramicCache model

* feat[api]: (WIP) adding related DB migration

* feat: adding tests for storing the stamps & score when using the embed api

* feat: (API) test setting the proper stamp creator and scorer id on the ceramic_cache API

* fix: tests related to changes in ceramic cache models

* feat: rename new attribute in CeramicCache, fixing failing tests

* fix: delete migrations before rebase

* fix: recreate migration after rebase

* fix: import order in bulk_POST.py lambda

* fix: remove print statements

---------

Co-authored-by: Gerald Iakobinyi-Pich <[email protected]>
  • Loading branch information
nutrina and Gerald Iakobinyi-Pich authored Jan 22, 2025
1 parent 830212c commit 6ceeb92
Show file tree
Hide file tree
Showing 22 changed files with 926 additions and 412 deletions.
10 changes: 8 additions & 2 deletions api/aws_lambdas/scorer_api_passport/v1/score_POST.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@
from aws_lambdas.scorer_api_passport.utils import (
authenticate_and_get_address,
format_response,
with_request_exception_handling,
parse_body,
with_request_exception_handling,
)
from ceramic_cache.api.v1 import get_detailed_score_response_for_address

DUMMY_MARKER = "import django.db stuff after this line"
# django.db needs to be imported after the aws helpers
from django.conf import settings
from django.db import close_old_connections


@with_request_exception_handling
def _handler(event, context):
address = authenticate_and_get_address(event)
body = parse_body(event)
alternate_scorer_id = body.get("alternate_scorer_id", None)
alternate_scorer_id = (
body.get("alternate_scorer_id", None) or settings.CERAMIC_CACHE_SCORER_ID
)

return format_response(
get_detailed_score_response_for_address(address, alternate_scorer_id)
Expand Down
12 changes: 10 additions & 2 deletions api/aws_lambdas/scorer_api_passport/v1/stamps/bulk_POST.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@
parse_body,
with_request_exception_handling,
)
from ceramic_cache.api.v1 import CacheStampPayload, handle_add_stamps

"""
Imports after aws_lambdas.scorer_api_passport.utils
"""
from django.db import close_old_connections

from ceramic_cache.api.v1 import CacheStampPayload, handle_add_stamps
from ceramic_cache.models import CeramicCache


@with_request_exception_handling
def _handler(event, context):
Expand All @@ -19,7 +25,9 @@ def _handler(event, context):

payload = [CacheStampPayload(**p) for p in body]

return format_response(handle_add_stamps(address, payload))
return format_response(
handle_add_stamps(address, payload, CeramicCache.SourceApp.PASSPORT)
)


def handler(*args, **kwargs):
Expand Down
6 changes: 4 additions & 2 deletions api/ceramic_cache/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,15 @@ class CeramicCacheAdmin(ScorerModelAdmin):
"id",
"address",
"provider",
"stamp",
"deleted_at",
"compose_db_save_status",
"compose_db_stream_id",
"proof_value",
"source_app",
"source_scorer_id",
"stamp",
)
list_filter = ("deleted_at", "compose_db_save_status")
list_filter = ("deleted_at", "compose_db_save_status", "source_app")
search_fields = ("address__exact", "compose_db_stream_id__exact", "proof_value")
search_help_text = (
"This will perform a search by 'address' and 'compose_db_stream_id'"
Expand Down
54 changes: 34 additions & 20 deletions api/ceramic_cache/api/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from typing import Any, Dict, List, Optional, Type

import requests
from asgiref.sync import async_to_sync
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractUser
Expand Down Expand Up @@ -36,7 +35,7 @@
DetailedScoreResponse,
ErrorMessageResponse,
SubmitPassportPayload,
ahandle_submit_passport,
handle_submit_passport,
)
from registry.exceptions import (
InvalidAddressException,
Expand Down Expand Up @@ -139,14 +138,22 @@ def cache_stamps(request, payload: List[CacheStampPayload]):
try:
address = get_address_from_did(request.did)

return handle_add_stamps(address, payload)
return handle_add_stamps(
address,
payload,
CeramicCache.SourceApp.PASSPORT,
settings.CERAMIC_CACHE_SCORER_ID,
)

except Exception as e:
raise e


def handle_add_stamps_only(
address, payload: List[CacheStampPayload], alternate_scorer_id: Optional[int] = None
address,
payload: List[CacheStampPayload],
source_app: CeramicCache.SourceApp,
alternate_scorer_id: Optional[int] = None,
) -> GetStampResponse:
if len(payload) > settings.MAX_BULK_CACHE_SIZE:
raise TooManyStampsException()
Expand All @@ -173,6 +180,8 @@ def handle_add_stamps_only(
compose_db_save_status=CeramicCache.ComposeDBSaveStatus.PENDING,
issuance_date=p.stamp.get("issuanceDate", None),
expiration_date=p.stamp.get("expirationDate", None),
source_app=source_app,
source_scorer_id=alternate_scorer_id,
)
for p in payload
]
Expand Down Expand Up @@ -201,16 +210,19 @@ def handle_add_stamps_only(


def handle_add_stamps(
address, payload: List[CacheStampPayload], alternate_scorer_id: Optional[int] = None
address,
payload: List[CacheStampPayload],
stamp_creator: CeramicCache.SourceApp,
alternate_scorer_id: Optional[int] = None,
) -> GetStampsWithScoreResponse:

stamps_response = handle_add_stamps_only(address, payload, alternate_scorer_id)
stamps_response = handle_add_stamps_only(
address, payload, stamp_creator, alternate_scorer_id
)
scorer_id = alternate_scorer_id or settings.CERAMIC_CACHE_SCORER_ID
return GetStampsWithScoreResponse(
success=stamps_response.success,
stamps=stamps_response.stamps,
score=get_detailed_score_response_for_address(
address, alternate_scorer_id=alternate_scorer_id
),
score=get_detailed_score_response_for_address(address, scorer_id=scorer_id),
)


Expand All @@ -222,13 +234,13 @@ def patch_stamps(request, payload: List[CacheStampPayload]):
address = get_address_from_did(request.did)
return handle_patch_stamps(address, payload)

except Exception:
except Exception as exc:
log.error(
"Failed patch_stamps request: '%s'",
[p.dict() for p in payload],
[p.model_dump_json() for p in payload],
exc_info=True,
)
raise InternalServerException()
raise InternalServerException() from exc


def handle_patch_stamps(
Expand Down Expand Up @@ -287,7 +299,9 @@ def handle_patch_stamps(
)
for stamp in updated_passport_state
],
score=get_detailed_score_response_for_address(address),
score=get_detailed_score_response_for_address(
address, settings.CERAMIC_CACHE_SCORER_ID
),
)


Expand Down Expand Up @@ -391,7 +405,9 @@ def handle_delete_stamps(
)
for stamp in updated_passport_state
],
score=get_detailed_score_response_for_address(address),
score=get_detailed_score_response_for_address(
address, settings.CERAMIC_CACHE_SCORER_ID
),
)


Expand Down Expand Up @@ -454,7 +470,7 @@ def handle_get_stamps(address):
passport__community_id=scorer_id,
).exists()
):
get_detailed_score_response_for_address(address)
get_detailed_score_response_for_address(address, scorer_id)

return GetStampResponse(
success=True,
Expand Down Expand Up @@ -650,9 +666,8 @@ def handle_authenticate(payload: CacaoVerifySubmit) -> AccessTokenResponse:


def get_detailed_score_response_for_address(
address: str, alternate_scorer_id: Optional[int] = None
address: str, scorer_id: Optional[int]
) -> DetailedScoreResponse:
scorer_id = alternate_scorer_id or settings.CERAMIC_CACHE_SCORER_ID
if not scorer_id:
raise InternalServerException("Scorer ID not set")

Expand All @@ -663,8 +678,7 @@ def get_detailed_score_response_for_address(
scorer_id=str(scorer_id),
)

score = async_to_sync(ahandle_submit_passport)(submit_passport_payload, account)

score = handle_submit_passport(submit_passport_payload, account)
return score


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 4.2.6 on 2025-01-14 16:16

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("ceramic_cache", "0030_alter_ceramiccache_created_at_and_more"),
]

operations = [
migrations.AddField(
model_name="ceramiccache",
name="source_app",
field=models.IntegerField(
blank=True,
choices=[(1, "Passport"), (2, "Embed")],
db_index=True,
help_text="Which entity created the stamp. At the moment there are 2 options: the 'Passport App' and 'Embed Widget'",
null=True,
verbose_name="Creating Enity",
),
),
migrations.AddField(
model_name="ceramiccache",
name="source_scorer_id",
field=models.BigIntegerField(
blank=True,
db_index=True,
help_text="This is field is only used to indicate for analytic purposes which scorer was targeted \n when claiming the users credential (when used from embed, it will indicate what scorer id was set in \n the embed component)",
null=True,
verbose_name="Scorer ID",
),
),
]
28 changes: 28 additions & 0 deletions api/ceramic_cache/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class StampType(IntEnum):
V1 = 1
V2 = 2

class SourceApp(models.IntegerChoices):
PASSPORT = 1
EMBED = 2

class ComposeDBSaveStatus(models.TextChoices):
PENDING = "pending"
SAVED = "saved"
Expand Down Expand Up @@ -84,6 +88,30 @@ class ComposeDBSaveStatus(models.TextChoices):
null=True, db_index=True
) # stamp['expirationDate']

################################################################################################
# Begin metadata fields, to determine the scorer that initiated the creation of a stamp
#################################################################################################
source_scorer_id = models.BigIntegerField(
verbose_name="Scorer ID",
help_text="""This is field is only used to indicate for analytic purposes which scorer was targeted
when claiming the users credential (when used from embed, it will indicate what scorer id was set in
the embed component)""",
null=True,
blank=True,
db_index=True,
)
source_app = models.IntegerField(
verbose_name="Creating Enity",
help_text="""Which entity created the stamp. At the moment there are 2 options: the 'Passport App' and 'Embed Widget'""",
choices=SourceApp.choices,
null=True,
blank=True,
db_index=True,
)
################################################################################################
# End metadata fields
#################################################################################################

class Meta:
unique_together = ["type", "address", "provider", "deleted_at"]

Expand Down
28 changes: 24 additions & 4 deletions api/ceramic_cache/test/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from datetime import datetime, timedelta, timezone

import pytest
from django.conf import settings

Expand Down Expand Up @@ -28,11 +30,29 @@ def sample_providers():


@pytest.fixture
def sample_stamps():
def sample_expiration_dates(sample_providers):
now = datetime.now(timezone.utc)
return [now + timedelta(days=idx) for idx, _ in enumerate(sample_providers, 1)]


@pytest.fixture
def sample_stamps(sample_expiration_dates, sample_providers, sample_address):
return [
{"stamp": 1, "proof": {"proofValue": "test1"}},
{"stamp": 2, "proof": {"proofValue": "test2"}},
{"stamp": 3, "proof": {"proofValue": "test3"}},
{
"type": ["VerifiableCredential"],
"credentialSubject": {
"id": sample_address,
"hash": "v0.0.0:1Vzw/OyM9CBUkVi/3mb+BiwFnHzsSRZhVH1gaQIyHvM=",
"provider": sample_providers[idx],
},
"issuer": settings.TRUSTED_IAM_ISSUERS[0],
"issuanceDate": (expiration_date - timedelta(days=30)).isoformat(),
"expirationDate": expiration_date.isoformat(),
"proof": {
"proofValue": "proof-v0.0.0:1Vzw/OyM9CBUkVi/3mb+BiwFnHzsSRZhVH1gaQIyHvM=",
},
}
for idx, expiration_date in enumerate(sample_expiration_dates)
]


Expand Down
Loading

0 comments on commit 6ceeb92

Please sign in to comment.