Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): return notifications based on scorer id #634

Merged
merged 3 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading