def send_rsc(self, request): recipient_id = request.data.get('recipient_id', '') amount = request.data.get('amount', 0) if recipient_id: user = request.user user_id = user.id content_type = ContentType.objects.get(model='distribution') proof_content_type = ContentType.objects.get(model='user') proof = { 'table': 'user_user', 'record': { 'id': user_id, 'email': user.email, 'name': user.first_name + ' ' + user.last_name } } distribution = Distribution('MOD_PAYOUT', amount, give_rep=False) timestamp = time.time() user_proof = User.objects.get(id=recipient_id) distributor = Distributor(distribution, user_proof, user_proof, timestamp) distributor.distribute() return Response({"message": 'RSC Sent!'})
def distribute_for_censor(sender, instance, created, update_fields, **kwargs): timestamp = time() distributor = None hubs = None if check_censored(created, update_fields) is True: try: if isinstance(instance, BulletPoint): distribution = distributions.BulletPointCensored recipient = instance.bullet_point.created_by hubs = instance.bullet_point.paper.hubs elif check_is_discussion_item(instance): distribution = get_discussion_censored_distribution(instance) recipient = instance.created_by hubs = get_discussion_hubs(instance) else: raise TypeError all_hubs = None if hubs is not None: all_hubs = hubs.all() if is_eligible_user(recipient): distributor = Distributor(distribution, recipient, instance, timestamp, all_hubs) except TypeError as e: error = ReputationSignalError(e, "Failed to distribute") print(error) sentry.log_error(error) if distributor is not None: distributor.distribute()
def distribute_for_create_summary(sender, instance, created, update_fields, **kwargs): timestamp = time() recipient = instance.proposed_by if is_eligible_for_create_summary(created, recipient): distribution = distributions.CreateSummary elif is_eligible_for_create_first_summary(created, update_fields, instance): distribution = distributions.CreateFirstSummary else: return last_distribution = recipient.reputation_records.filter( Q(distribution_type=distributions.CreateSummary) | Q(distribution_type=distributions.CreateFirstSummary)).last() if check_summary_distribution_interval(last_distribution): distributor = Distributor( distribution, recipient, instance, timestamp, instance.paper.hubs.all(), ) record = distributor.distribute()
def deposit_rsc(self, request): """ This is a request to deposit RSC from our researchhub-async-service TODO: Add a websocket call here so we can ping the frontend that the transaction completed """ deposit = Deposit.objects.get(id=request.data.get("deposit_id")) amt = deposit.amount user = deposit.user distribution = Dist("DEPOSIT", amt, give_rep=False) distributor = Distributor(distribution, user, user, time.time()) distributor.distribute() return Response({"message": "Deposit successful"})
def distribute_rsc(request): data = request.data recipient_id = data.get("recipient_id") amount = data.get("amount") user = User.objects.get(id=recipient_id) distribution = Dist("REWARD", amount, give_rep=False) distributor = Distributor(distribution, user, user, time.time()) distributor.distribute() response = Response({"data": f"Gave {amount} RSC to {user.email}"}, status=200) return response
def distribute_for_bullet_point_vote(sender, instance, created, update_fields, **kwargs): timestamp = time() voter = instance.created_by recipient = instance.bulletpoint.created_by if created and is_eligible_for_bulletpoint_vote(recipient, voter): hubs = instance.bulletpoint.paper.hubs distribution = get_bulletpoint_vote_item_distribution(instance) distributor = Distributor(distribution, recipient, instance, timestamp, hubs.all()) record = distributor.distribute()
def distribute_for_censor_paper(sender, instance, using, **kwargs): timestamp = time() flags = instance.flags.select_related("created_by").all() for flag in flags: recipient = flag.created_by if is_eligible_user(recipient): distributor = Distributor( distributions.FlagPaper, recipient, instance, timestamp, instance.hubs.all(), ) record = distributor.distribute()
def distribute_for_create_bullet_point(sender, instance, created, **kwargs): timestamp = time() recipient = instance.created_by hubs = None if created and is_eligible_for_create_bullet_point(recipient): if isinstance(instance, BulletPoint) and check_key_takeaway_interval( instance, recipient): distribution = distributions.CreateBulletPoint hubs = instance.paper.hubs else: return distributor = Distributor(distribution, recipient, instance, timestamp, hubs.all()) record = distributor.distribute()
def distribute_for_paper_upvoted(sender, instance, created, update_fields, **kwargs): """Distributes reputation to the uploader.""" timestamp = time() recipient = instance.paper.uploaded_by if is_eligible_for_paper_upvoted(created, instance.created_by, recipient): distributor = Distributor( distributions.create_upvote_distribution( distributions.PaperUpvoted.name, instance.paper, instance), recipient, instance, timestamp, instance.paper.hubs.all(), ) record = distributor.distribute()
def distribute_referral_reward(self, user): timestamp = time() referrer = user.invited_by if not referrer: referrer = user distribution = Distributor( distributions.ReferralApproved, user, referrer, timestamp, None, ) referred_distribution_record = distribution.distribute() if referrer: distribution = Distributor( distributions.ReferralApproved, referrer, referrer, timestamp, None, ) distribution.distribute() return referred_distribution_record
def create_upvote_distribution(vote_type, paper=None, vote=None): from user.utils import calculate_eligible_enhanced_upvotes eligible_enhanced_upvote = False if vote: eligible_enhanced_upvote = calculate_eligible_enhanced_upvotes( vote.created_by) if not eligible_enhanced_upvote: return Distribution(vote_type, 1, 1) distribution_amount = calculate_rsc_per_upvote() if paper: from reputation.distributor import Distributor from researchhub_case.models import AuthorClaimCase author_distribution_amount = distribution_amount * .75 distribution_amount *= .25 # authors get 75% of the upvote score distributed_amount = 0 author_count = paper.true_author_count() for author in paper.authors.all(): if author.user and AuthorClaimCase.objects.filter( target_paper=paper, requestor=author.user, status=APPROVED).exists(): timestamp = time() amt = author_distribution_amount / author_count distributor = Distributor( Distribution(vote_type, amt), author.user, paper, timestamp, paper.hubs.all(), ) record = distributor.distribute() distributed_amount += amt from reputation.models import AuthorRSC AuthorRSC.objects.create( paper=paper, amount=author_distribution_amount - distributed_amount, ) return Distribution(vote_type, distribution_amount, 1)
def reward_author_claim_case(requestor_author, paper, claim_case): vote_reward = requestor_author.calculate_score() author_pot_query = AuthorRSC.objects.filter(paper=paper, ) author_pot_amount = author_pot_query.aggregate(Sum('amount')).get( 'amount__sum', 0) or 0 author_count = paper.true_author_count() author_pot_amount /= author_count if author_pot_amount: distributor = Distributor( dist('UPVOTE_RSC_POT', author_pot_amount), requestor_author.user, requestor_author, time.time(), ) record = distributor.distribute() claim_case.claimed_rsc.add(*author_pot_query) try: distributor = Distributor(dist('REWARD', vote_reward, False), requestor_author.user, requestor_author, time.time()) distribution = distributor.distribute() return distribution except Exception as exception: print("reward_author_claim_case: ", exception) sentry.log_error(exception)
def test_no_doi_with_sufficient_funds(self): author = create_random_default_user('author') hub = create_hub() self.client.force_authenticate(author) distributor = Distributor(Distribution('TEST_REWARD', 5, False), author, None, time.time()) distributor.distribute() doc_response = self.client.post( "/api/researchhub_posts/", { "assign_doi": False, "document_type": "DISCUSSION", "created_by": author.id, "full_src": "body", "is_public": True, "renderable_text": "body", "title": "title", "hubs": [hub.id], }) self.assertEqual(doc_response.status_code, 200) self.assertEqual(int(author.get_balance()), 5)
def distribute_for_discussion_vote(sender, instance, created, update_fields, **kwargs): """Distributes reputation to the creator of the item voted on.""" timestamp = time() distributor = None try: recipient = instance.item.created_by except Exception as e: error = ReputationSignalError(e, "Invalid recipient") sentry.log_error(e) return voter = instance.created_by if (created or vote_type_updated(update_fields) ) and is_eligible_for_discussion_vote(recipient, voter): hubs = None item = instance.item if isinstance(item, Comment): if item.parent.paper is not None: hubs = item.parent.paper.hubs elif item.parent.post is not None: hubs = item.parent.post.unified_document.hubs elif item.parent.hypothesis is not None: hubs = item.parent.hypothesis.unified_document.hubs elif isinstance(item, Reply): try: if item.parent.parent.paper is not None: hubs = item.parent.parent.paper.hubs elif item.parent.parent.post is not None: hubs = item.parent.parent.post.unified_document.hubs elif item.parent.parent.hypothesis is not None: hubs = item.parent.parent.hypothesis.unified_document.hubs except Exception as e: sentry.log_error(e) elif isinstance(item, Thread): if item.paper is not None: hubs = item.paper.hubs elif item.post is not None: hubs = item.post.unified_document.hubs elif item.hypothesis is not None: hubs = item.hypothesis.unified_document.hubs elif isinstance(item, ResearchhubPost): hubs = item.unified_document.hubs elif isinstance(item, Hypothesis): hubs = item.unified_document.hubs elif isinstance(item, Citation): hubs = item.source.hubs # TODO: This needs to be altered so that if the vote changes the # original distribution is deleted if not yet withdrawn if created: try: # NOTE: Only comment seems to be supporting distribution distribution = get_discussion_vote_item_distribution(instance) distributor = Distributor(distribution, recipient, instance, timestamp, hubs.all()) except TypeError as e: error = ReputationSignalError( e, "Failed to distribute for reaction vote") sentry.log_error(error) if distributor is not None and recipient != instance.created_by: record = distributor.distribute()
def create_action(sender, instance, created, **kwargs): if created: if sender == Summary: user = instance.proposed_by elif sender == Paper or sender == PaperSubmission: user = instance.uploaded_by else: if sender == Thread: thread = instance if thread.is_removed: content_id = f"{type(thread).__name__}_{thread.id}" decisions_api.apply_bad_content_decision( thread.created_by, content_id) events_api.track_flag_content( thread.created_by, content_id, 1, ) user = instance.created_by """ If we're creating an action for the first time, check if we've been referred """ referral_content_types = [ get_content_type_for_model(Thread), get_content_type_for_model(Reply), get_content_type_for_model(Comment), get_content_type_for_model(Paper), get_content_type_for_model(ResearchhubPost), get_content_type_for_model(Hypothesis), ] if (user is not None and user.invited_by and not Action.objects.filter( user=user, content_type__in=referral_content_types).exists() and sender in [ Thread, Reply, Comment, Paper, ResearchhubPost, Hypothesis ]): timestamp = time() if calculate_show_referral(user.invited_by): referred = Distributor( distributions.Referral, user, user.invited_by, timestamp, None, ) referred.distribute() referrer = Distributor( distributions.Referral, user.invited_by, user.invited_by, timestamp, None, ) referrer.distribute() vote_types = [PaperVote, ReactionVote, BulletPointVote, SummaryVote] display = (False if (sender in vote_types or sender == PaperSubmission or sender != ReactionVote and (hasattr(instance, "is_removed") and instance.is_removed)) else True) action = Action.objects.create(item=instance, user=user, display=display) hubs = [] if sender == Paper: hubs = instance.hubs.all() elif sender != BulletPointVote and sender != SummaryVote: hubs = get_related_hubs(instance) if hubs: action.hubs.add(*hubs) create_notification(sender, instance, created, action, **kwargs) return action
def create(self, request): user = request.user data = request.data amount = data['amount'] purchase_method = data['purchase_method'] purchase_type = data['purchase_type'] content_type_str = data['content_type'] object_id = data['object_id'] transfer_rsc = False recipient = None if content_type_str not in self.ALLOWED_CONTENT_TYPES: return Response(status=400) if purchase_method not in (Purchase.OFF_CHAIN, Purchase.ON_CHAIN): return Response(status=400) decimal_amount = decimal.Decimal(amount) if decimal_amount <= 0: return Response(status=400) content_type = ContentType.objects.get(model=content_type_str) with transaction.atomic(): if purchase_method == Purchase.ON_CHAIN: purchase = Purchase.objects.create( user=user, content_type=content_type, object_id=object_id, purchase_method=purchase_method, purchase_type=purchase_type, amount=amount) else: user_balance = user.get_balance() if user_balance - decimal_amount < 0: return Response('Insufficient Funds', status=402) purchase = Purchase.objects.create( user=user, content_type=content_type, object_id=object_id, purchase_method=purchase_method, purchase_type=purchase_type, amount=amount, paid_status=Purchase.PAID) source_type = ContentType.objects.get_for_model(purchase) Balance.objects.create( user=user, content_type=source_type, object_id=purchase.id, amount=f'-{amount}', ) purchase_hash = purchase.hash() purchase.purchase_hash = purchase_hash purchase_boost_time = purchase.get_boost_time(amount) purchase.boost_time = purchase_boost_time purchase.group = purchase.get_aggregate_group() purchase.save() item = purchase.item context = { 'purchase_minimal_serialization': True, 'exclude_stats': True } # transfer_rsc is set each time just in case we want # to disable rsc transfer for a specific item if content_type_str == 'paper': paper = Paper.objects.get(id=object_id) unified_doc = paper.unified_document paper.calculate_hot_score() recipient = paper.uploaded_by cache_key = get_cache_key('paper', object_id) cache.delete(cache_key) transfer_rsc = True hub_ids = paper.hubs.values_list('id', flat=True) reset_unified_document_cache( hub_ids, document_type=['all', 'paper'], filters=[TRENDING], ) elif content_type_str == 'thread': transfer_rsc = True recipient = item.created_by unified_doc = item.unified_document elif content_type_str == 'comment': transfer_rsc = True unified_doc = item.unified_document recipient = item.created_by elif content_type_str == 'reply': transfer_rsc = True unified_doc = item.unified_document recipient = item.created_by elif content_type_str == 'summary': transfer_rsc = True recipient = item.proposed_by unified_doc = item.paper.unified_document elif content_type_str == 'bulletpoint': transfer_rsc = True recipient = item.created_by unified_doc = item.paper.unified_document elif content_type_str == 'researchhubpost': transfer_rsc = True recipient = item.created_by unified_doc = item.unified_document hub_ids = unified_doc.hubs.values_list('id', flat=True) reset_unified_document_cache( hub_ids, document_type=['all', 'posts'], filters=[TRENDING], ) if unified_doc.is_removed: return Response('Content is removed', status=403) if transfer_rsc and recipient and recipient != user: distribution = create_purchase_distribution(amount) distributor = Distributor(distribution, recipient, purchase, time.time()) distributor.distribute() serializer = self.serializer_class(purchase, context=context) serializer_data = serializer.data if recipient and user: self.send_purchase_notification(purchase, unified_doc, recipient) self.send_purchase_email(purchase, recipient, unified_doc) create_contribution.apply_async((Contribution.SUPPORTER, { 'app_label': 'purchase', 'model': 'purchase' }, user.id, unified_doc.id, purchase.id), priority=2, countdown=10) return Response(serializer_data, status=201)
def editor_daily_payout_task(): from reputation.distributor import Distributor User = apps.get_model('user.User') today = datetime.date.today() num_days_this_month = monthrange(today.year, today.month)[1] result = get_daily_rsc_payout_amount_from_deep_index(num_days_this_month) # Keeping record of exchange rate used today RscExchangeRate.objects.create( rate=result['rate'], target_currency=USD, ) excluded_user_email = Gatekeeper.objects.filter( type__in=[EDITOR_PAYOUT_ADMIN, PAYOUT_EXCLUSION_LIST] ).values_list('email', flat=True) editors = User.objects.filter( permissions__isnull=False, permissions__access_type=EDITOR, permissions__content_type=ContentType.objects.get_for_model(Hub) ).distinct().exclude(email__in=(excluded_user_email)) csv_prep = { 'amount-rsc': [], 'emails': [], 'names': [], 'usd-rsc-rate': [], } for editor in editors.iterator(): try: pay_amount = result['pay_amount'] distributor = Distributor( # this is NOT the model. It's a simple object Distribution('EDITOR_PAYOUT', pay_amount, False), editor, None, today ) distributor.distribute() csv_prep['names'].append( editor.first_name or "" + editor.last_name or "" ) csv_prep['emails'].append(editor.email) csv_prep['amount-rsc'].append(pay_amount) except Exception as error: sentry.log_error(error) print('error: ', error) pass try: title = f'Editor Payout {today}' csv_file = StringIO() csv_writer = csv.DictWriter( csv_file, # logical ordering fieldnames=['names', 'emails', 'amount-rsc', 'usd-rsc-rate'] ) csv_writer.writeheader() prepped_rows = [] editor_count = editors.count() for index in range(editor_count): prepped_rows.append({ 'names': csv_prep['names'][index], 'emails': csv_prep['emails'][index], 'amount-rsc': csv_prep['amount-rsc'][index], 'usd-rsc-rate': result['rate'], }) csv_writer.writerows(prepped_rows) payout_admin_emails = Gatekeeper.objects.filter( type__in=[EDITOR_PAYOUT_ADMIN] ).values_list('email', flat=True) email_tag = '' if APP_ENV == 'production' else "[Staging - TEST] " email = EmailMessage( subject=f'{email_tag}{title}', body=f'{email_tag}Editor payout csv - {today}', from_email=f'{email_tag}ResearchHub <*****@*****.**>', to=payout_admin_emails, ) email.attach(f'{title}.csv', csv_file.getvalue(), 'text/csv') email.send() return f"""{APP_ENV}: Users - {editor_count}. Rate - {result['rate']}. RSC - {pay_amount}""" except Exception as error: sentry.log_error(error) print('error: ', error) pass