Skip to content

Commit 8210cb4

Browse files
authored
API for validating conditions needed for passport stamps (#10755)
* Adding API endpoints required for verifying stamp issuance condition * Adding information about the number of rounds a user contributed to * Adding to contributor_statistics api * Adding API for grantee statistics * Adding verification that grantee was elligible in ecosystem and cause rounds * Updating file header comment
1 parent eb4dd09 commit 8210cb4

File tree

2 files changed

+206
-0
lines changed

2 files changed

+206
-0
lines changed

app/grants/urls.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
leaderboard, manage_ethereum_cart_data, matching_funds, profile, remove_grant_from_collection, save_collection,
3434
toggle_grant_favorite, upload_sybil_csv, verify_grant,
3535
)
36+
from grants.views_api_vc import contributor_statistics, grantee_statistics
3637

3738
app_name = 'grants/'
3839
urlpatterns = [
@@ -126,4 +127,7 @@
126127
path('v1/api/upload_sybil_csv', upload_sybil_csv, name='upload_sybil_csv'),
127128
path('v1/api/ingest_merkle_claim_to_clr_match', ingest_merkle_claim_to_clr_match, name='ingest_merkle_claim_to_clr_match'),
128129

130+
# VC verification API (when issuing a VC)
131+
path('v1/api/vc/contributor_statistics', contributor_statistics, name='contributor_statistics'),
132+
path('v1/api/vc/grantee_statistics', grantee_statistics, name='grantee_statistics'),
129133
]

app/grants/views_api_vc.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# -*- coding: utf-8 -*-
2+
"""Define the Grant views for API used by the passport IAM
3+
4+
Copyright (C) 2021 Gitcoin Core
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU Affero General Public License as published
8+
by the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU Affero General Public License for more details.
15+
16+
You should have received a copy of the GNU Affero General Public License
17+
along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
19+
"""
20+
import logging
21+
22+
from django.conf import settings
23+
from django.db.models import Sum
24+
from django.http import JsonResponse
25+
from django.utils.translation import gettext_lazy as _
26+
27+
from grants.models import Contribution, Grant
28+
from perftools.models import StaticJsonEnv
29+
from townsquare.models import SquelchProfile
30+
31+
logger = logging.getLogger(__name__)
32+
33+
34+
def ami_api_token_required(func):
35+
def decorator(request, *args, **kwargs):
36+
try:
37+
apiToken = StaticJsonEnv.objects.get(key="AMI_API_TOKEN")
38+
expectedToken = apiToken.data["token"]
39+
receivedToken = request.headers.get("Authorization")
40+
41+
if receivedToken:
42+
# Token shall look like "token <bearer token>", and we need only the <bearer token> part
43+
receivedToken = receivedToken.split(" ")[1]
44+
45+
if expectedToken == receivedToken:
46+
return func(request, *args, **kwargs)
47+
else:
48+
return JsonResponse(
49+
{
50+
"error": "Access denied",
51+
},
52+
status=403,
53+
)
54+
except Exception as e:
55+
logger.error("Error in ami_api_token_required %s", e)
56+
return JsonResponse(
57+
{
58+
"error": "An unexpected error occured",
59+
},
60+
status=500,
61+
)
62+
63+
return decorator
64+
65+
66+
@ami_api_token_required
67+
def contributor_statistics(request):
68+
handle = request.GET.get("handle")
69+
70+
if not handle:
71+
return JsonResponse(
72+
{"error": "Bad request, 'handle' parameter is missing or invalid"},
73+
status=400,
74+
)
75+
76+
# Get number of grants the user contributed to
77+
num_grants_contribute_to = (
78+
Contribution.objects.filter(profile_for_clr__handle=handle, success=True)
79+
.order_by("grant_id")
80+
.distinct("grant_id")
81+
.count()
82+
)
83+
84+
# Get the number of grants the user contributed to
85+
num_rounds_contribute_to = (
86+
Contribution.objects.filter(
87+
success=True,
88+
subscription__contributor_profile__handle=handle,
89+
subscription__network="mainnet",
90+
subscription__grant__clr_calculations__latest=True,
91+
)
92+
.order_by("subscription__grant__clr_calculations__grantclr__round_num")
93+
.distinct("subscription__grant__clr_calculations__grantclr__round_num")
94+
.count()
95+
)
96+
97+
total_contribution_amount = Contribution.objects.filter(
98+
profile_for_clr__handle=handle, success=True
99+
).aggregate(Sum("amount_per_period_usdt"))["amount_per_period_usdt__sum"]
100+
total_contribution_amount = (
101+
total_contribution_amount if total_contribution_amount is not None else 0
102+
)
103+
104+
# GR14 contributor (and not squelched by FDD)
105+
profile_squelch = SquelchProfile.objects.filter(
106+
profile__handle=handle, active=True
107+
).values_list("profile_id", flat=True)
108+
109+
num_gr14_contributions = (
110+
Contribution.objects.filter(
111+
success=True,
112+
subscription__contributor_profile__handle=handle,
113+
subscription__network="mainnet",
114+
subscription__grant__clr_calculations__latest=True,
115+
subscription__grant__clr_calculations__grantclr__round_num=14,
116+
)
117+
.exclude(subscription__contributor_profile_id__in=profile_squelch)
118+
.count()
119+
)
120+
121+
return JsonResponse(
122+
{
123+
"num_grants_contribute_to": num_grants_contribute_to,
124+
"num_rounds_contribute_to": num_rounds_contribute_to,
125+
"total_contribution_amount": total_contribution_amount,
126+
"is_gr14_contributor": num_gr14_contributions > 0,
127+
}
128+
)
129+
130+
131+
@ami_api_token_required
132+
def grantee_statistics(request):
133+
handle = request.GET.get("handle")
134+
135+
if not handle:
136+
return JsonResponse(
137+
{"error": "Bad request, 'handle' parameter is missing or invalid"},
138+
status=400,
139+
)
140+
141+
# Get number of owned grants
142+
num_owned_grants = Grant.objects.filter(
143+
admin_profile__handle=handle,
144+
hidden=False,
145+
active=True,
146+
is_clr_eligible=True,
147+
).count()
148+
149+
# Get the total amount of contrinutors for ane users grants that where not squelched and are not the owner himself
150+
all_squelched = SquelchProfile.objects.filter(active=True).values_list(
151+
"profile_id", flat=True
152+
)
153+
num_grant_contributors = (
154+
Contribution.objects.filter(
155+
success=True,
156+
subscription__network="mainnet",
157+
subscription__grant__hidden=False,
158+
subscription__grant__active=True,
159+
subscription__grant__is_clr_eligible=True,
160+
subscription__grant__admin_profile__handle=handle,
161+
)
162+
.exclude(subscription__contributor_profile_id__in=all_squelched)
163+
.exclude(subscription__contributor_profile__handle=handle)
164+
.order_by("subscription__contributor_profile_id")
165+
.distinct("subscription__contributor_profile_id")
166+
.count()
167+
)
168+
169+
# Get the total amount of contributions received by the owned grants (excluding the contributions made by the owner)
170+
total_contribution_amount = (
171+
Contribution.objects.filter(
172+
success=True,
173+
subscription__network="mainnet",
174+
subscription__grant__hidden=False,
175+
subscription__grant__active=True,
176+
subscription__grant__is_clr_eligible=True,
177+
subscription__grant__admin_profile__handle=handle,
178+
)
179+
.exclude(subscription__contributor_profile__handle=handle)
180+
.aggregate(Sum("amount_per_period_usdt"))["amount_per_period_usdt__sum"]
181+
)
182+
total_contribution_amount = (
183+
total_contribution_amount if total_contribution_amount is not None else 0
184+
)
185+
186+
# [IAM] As an IAM server, I want to issue stamps for grant owners whose project have tagged matching-eligibel in an eco-system and/or cause round
187+
num_grants_in_eco_and_cause_rounds = Grant.objects.filter(
188+
admin_profile__handle=handle,
189+
hidden=False,
190+
active=True,
191+
is_clr_eligible=True,
192+
clr_calculations__grantclr__type__in=["ecosystem", "cause"],
193+
).count()
194+
195+
return JsonResponse(
196+
{
197+
"num_owned_grants": num_owned_grants,
198+
"num_grant_contributors": num_grant_contributors,
199+
"num_grants_in_eco_and_cause_rounds": num_grants_in_eco_and_cause_rounds,
200+
"total_contribution_amount": total_contribution_amount,
201+
}
202+
)

0 commit comments

Comments
 (0)