Skip to content

Commit

Permalink
feat(api): return notifications based on scorer id (#634)
Browse files Browse the repository at this point in the history
* feat(api): return notifications based on scorer id

* fix: link text

* fix: link text
  • Loading branch information
tim-schultz authored Jul 15, 2024
1 parent 0cddc9e commit eda9c81
Show file tree
Hide file tree
Showing 10 changed files with 351 additions and 124 deletions.
34 changes: 17 additions & 17 deletions api/ceramic_cache/api/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
from datetime import timedelta
from typing import Any, Dict, List, Optional, Type


import api_logging as logging
import tos.api
import tos.schema
from account.models import Account, Nonce, Community
from account.models import Account, Community, Nonce
from asgiref.sync import async_to_sync
from django.conf import settings
from django.contrib.auth import get_user_model
Expand All @@ -25,19 +24,26 @@
# from ninja_jwt.schema import RefreshToken
from ninja_jwt.settings import api_settings
from ninja_jwt.tokens import RefreshToken, Token, TokenError

import tos.api
import tos.schema
from registry.api.utils import (
is_valid_address,
)
from registry.api.v1 import (
DetailedScoreResponse,
ErrorMessageResponse,
SubmitPassportPayload,
ahandle_submit_passport,
)
from registry.exceptions import (
InvalidAddressException,
InvalidCommunityScoreRequestException,
NotFoundApiException,
)
from registry.models import Score
from stake.api import handle_get_gtc_stake
from stake.schema import GetSchemaResponse

from ceramic_cache.utils import get_utc_time

from ..exceptions import (
InternalServerException,
InvalidDeleteCacheRequestException,
Expand All @@ -57,15 +63,6 @@
GetStampResponse,
GetStampsWithScoreResponse,
)
from ceramic_cache.utils import get_utc_time
from registry.api.utils import (
is_valid_address,
)
from registry.exceptions import (
InvalidAddressException,
InvalidCommunityScoreRequestException,
NotFoundApiException,
)

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -381,8 +378,11 @@ def get_scorer_weights(request):
return handle_get_scorer_weights()


def handle_get_scorer_weights() -> Dict[str, str]:
cache_key = f"ceramic_cache_scorer_weights_{settings.CERAMIC_CACHE_SCORER_ID}"
def handle_get_scorer_weights(community_id=None) -> Dict[str, str]:
if not community_id:
community_id = settings.CERAMIC_CACHE_SCORER_ID

cache_key = f"ceramic_cache_scorer_weights_{community_id}"
weights = cache.get(cache_key)
if weights:
try:
Expand All @@ -391,7 +391,7 @@ def handle_get_scorer_weights() -> Dict[str, str]:
log.error("Failed to parse weights from cache!", exc_info=True)

try:
community = Community.objects.get(id=settings.CERAMIC_CACHE_SCORER_ID)
community = Community.objects.get(id=community_id)
weights = community.get_scorer().weights
# Cache the value for 1 minute
cache.set(cache_key, json.dumps(weights), 1 * 60)
Expand Down
4 changes: 2 additions & 2 deletions api/ceramic_cache/test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
sample_token, # noqa
scorer_account, # noqa
scorer_community, # noqa
scorer_passport, # noqa
scorer_community_with_binary_scorer, # noqa
scorer_passport, # noqa
scorer_user, # noqa
ui_scorer, # noqa
verifiable_credential, # noqa
Expand Down Expand Up @@ -36,4 +36,4 @@ def sample_stamps():

def pytest_configure():
settings.CERAMIC_CACHE_API_KEY = "supersecret"
settings.CERAMIC_CACHE_SCORER_ID = ""
settings.CERAMIC_CACHE_SCORER_ID = "1"
36 changes: 21 additions & 15 deletions api/passport_admin/api.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
from typing import List, Optional

from account.models import Community
from ceramic_cache.api.v1 import JWTDidAuth
from django.db.models import Subquery, Q, OuterRef, Value
from django.db.models import OuterRef, Q, Subquery, Value
from django.db.models.functions import Coalesce
from django.utils import timezone
from ninja import Router
from passport_admin.schema import (
Banner,
GenericResponse,
NotificationSchema,
NotificationResponse,
NotificationPayload,
DismissPayload,
)

from passport_admin.notification_generators.deduplication_events import (
generate_deduplication_notifications,
)
Expand All @@ -21,15 +16,21 @@
from passport_admin.notification_generators.on_chain_expired import (
generate_on_chain_expired_notifications,
)

from passport_admin.schema import (
Banner,
DismissPayload,
GenericResponse,
NotificationPayload,
NotificationResponse,
NotificationSchema,
)

from .models import (
DismissedBanners,
PassportBanner,
Notification,
NotificationStatus,
PassportBanner,
)
from django.utils import timezone

router = Router()

Expand Down Expand Up @@ -111,9 +112,14 @@ def get_notifications(request, payload: NotificationPayload):
try:
address = get_address(request.auth.did)
current_date = timezone.now().date()

generate_deduplication_notifications(address=address)
generate_stamp_expired_notifications(address=address)
scorer_id = payload.scorer_id
try:
community = Community.objects.get(id=scorer_id)
except Community.DoesNotExist:
raise Exception("Scorer for provided id does not exist")

generate_deduplication_notifications(address=address, community=community)
generate_stamp_expired_notifications(address=address, community=community)
if payload.expired_chain_ids:
generate_on_chain_expired_notifications(
address=address, expired_chains=payload.expired_chain_ids
Expand Down
26 changes: 26 additions & 0 deletions api/passport_admin/migrations/0007_notification_community.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 4.2.6 on 2024-07-12 15:55

import django.db.models.deletion
from django.db import migrations, models

import passport_admin.models


class Migration(migrations.Migration):
dependencies = [
("account", "0032_customization_body_display_info_tooltip_and_more"),
("passport_admin", "0006_notification_alter_dismissedbanners_address_and_more"),
]

operations = [
migrations.AddField(
model_name="notification",
name="community",
field=models.ForeignKey(
default=passport_admin.models.get_default_ceramic_cache_community,
on_delete=django.db.models.deletion.CASCADE,
related_name="notifications",
to="account.community",
),
),
]
17 changes: 15 additions & 2 deletions api/passport_admin/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
Includes models for PassportBanner and DismissedBanners.
"""

from account.models import EthAddressField
from account.models import Community, EthAddressField
from django.conf import settings
from django.db import models
from account.models import EthAddressField

APPLICATION_CHOICES = [
("passport", "Passport"),
Expand Down Expand Up @@ -52,6 +52,13 @@ class DismissedBanners(models.Model):
]


def get_default_ceramic_cache_community():
if not settings.CERAMIC_CACHE_SCORER_ID:
return Community.objects.first().id
else:
return settings.CERAMIC_CACHE_SCORER_ID


class Notification(models.Model):
"""
Model representing a Notification.
Expand All @@ -74,6 +81,12 @@ class Notification(models.Model):
eth_address = EthAddressField(
null=True, blank=True
) # account/ eth address for which the notification is created. If null then it is a global notification wgich will be shown to all users.
community = models.ForeignKey(
Community,
on_delete=models.CASCADE,
related_name="notifications",
default=get_default_ceramic_cache_community,
)


class NotificationStatus(models.Model):
Expand Down
61 changes: 35 additions & 26 deletions api/passport_admin/notification_generators/deduplication_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
This module contains the logic for generating deduplication notifications for a specific address.
"""

import dag_cbor
import hashlib
from datetime import timedelta

import dag_cbor
from account.models import Community
from ceramic_cache.api.v1 import handle_get_scorer_weights
from django.utils import timezone
from datetime import timedelta
from registry.models import Event

from passport_admin.models import Notification


def generate_deduplication_notifications(address):
def generate_deduplication_notifications(address, community: Community):
"""
Generate deduplication notifications for a specific address.
Expand All @@ -27,33 +30,39 @@ def generate_deduplication_notifications(address):
address=address,
action=Event.Action.LIFO_DEDUPLICATION,
created_at__gte=thirty_days_ago,
community=community,
)

weights = handle_get_scorer_weights(community.id)
# for each deduplication event, generate a notification
# if the notification does not already exist
for event in deduplication_events:
encoded_data = dag_cbor.encode(
{
"action": event.action,
"address": event.address,
"data": event.data,
"id": event.id,
}
)

notification_id = hashlib.sha256(encoded_data).hexdigest()
notification_exists = Notification.objects.filter(
notification_id=notification_id
).exists()
stamp_name = event.data.get("provider", "<StampName>")
if not notification_exists:
Notification.objects.create(
notification_id=notification_id,
type="deduplication",
is_active=True,
content=f"You have claimed the same '{stamp_name}' stamp in two Passports. We only count your stamp once. This duplicate is in your wallet {address}. Learn more about deduplication",
link="https://github.com/orgs/gitcoinco/projects/6/views/link",
link_text="here",
created_at=timezone.now().date(),
eth_address=address,
stamp_weight = weights.get(stamp_name)
# check that stamp weight is greater than 0 which means it is an active stamp
if stamp_weight and float(stamp_weight) > 0:
encoded_data = dag_cbor.encode(
{
"action": event.action,
"address": event.address,
"data": event.data,
"id": event.id,
}
)

notification_id = hashlib.sha256(encoded_data).hexdigest()
notification_exists = Notification.objects.filter(
notification_id=notification_id
).exists()

if not notification_exists:
Notification.objects.create(
notification_id=notification_id,
type="deduplication",
is_active=True,
content=f"You have claimed the same '{stamp_name}' stamp in two Passports. We only count your stamp once. This duplicate is in your wallet {address}. Learn more about deduplication",
link="https://support.passport.xyz/passport-knowledge-base/using-passport/common-questions/why-is-my-passport-score-not-adding-up",
link_text="here",
created_at=timezone.now().date(),
eth_address=address,
)
57 changes: 32 additions & 25 deletions api/passport_admin/notification_generators/expired_stamp.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import dag_cbor
import hashlib

from django.utils import timezone
import dag_cbor
from account.models import Community
from ceramic_cache.api.v1 import handle_get_scorer_weights
from ceramic_cache.models import CeramicCache
from django.utils import timezone

from passport_admin.models import Notification


def generate_stamp_expired_notifications(address):
def generate_stamp_expired_notifications(address, community: Community):
"""
Generate stamp expired notifications for a specific address
"""
Expand All @@ -16,27 +19,31 @@ def generate_stamp_expired_notifications(address):
address=address, deleted_at__isnull=True, expiration_date__lt=current_date
)

weights = handle_get_scorer_weights(community.id)

for cc in ceramic_cache:
encoded_data = dag_cbor.encode(
{
"cc_id": cc.id,
"cc_provider": cc.provider,
"cc_stamp_hash": cc.stamp["credentialSubject"]["hash"],
"cc_stamp_id": cc.stamp["credentialSubject"]["id"],
"address": address,
}
)
notification_id = hashlib.sha256(encoded_data).hexdigest()

notification_exists = Notification.objects.filter(
notification_id=notification_id
).exists()

if not notification_exists:
Notification.objects.create(
notification_id=notification_id,
type="stamp_expiry",
is_active=True,
content=f"Your {cc.provider} stamp has expired. Please reverify to keep your Passport up to date.",
link=cc.provider,
stamp_weight = weights.get(cc.provider)
if stamp_weight and float(stamp_weight) > 0:
encoded_data = dag_cbor.encode(
{
"cc_id": cc.id,
"cc_provider": cc.provider,
"cc_stamp_hash": cc.stamp["credentialSubject"]["hash"],
"cc_stamp_id": cc.stamp["credentialSubject"]["id"],
"address": address,
}
)
notification_id = hashlib.sha256(encoded_data).hexdigest()

notification_exists = Notification.objects.filter(
notification_id=notification_id
).exists()

if not notification_exists:
Notification.objects.create(
notification_id=notification_id,
type="stamp_expiry",
is_active=True,
content=f"Your {cc.provider} stamp has expired. Please reverify to keep your Passport up to date.",
link=cc.provider,
)
Loading

0 comments on commit eda9c81

Please sign in to comment.