From d0bde5d606f1e3965972618aac1d0d17c759712d Mon Sep 17 00:00:00 2001 From: Alexander Orvik Date: Fri, 25 Oct 2024 22:04:04 +0200 Subject: [PATCH] feat(economy): add soci ranked season model endpoints --- economy/admin.py | 10 ++ economy/migrations/0006_socirankedseason.py | 24 ++++ economy/models.py | 6 + economy/schema.py | 133 +++++++++++++++++++- ksg_nett/schema.py | 2 + 5 files changed, 169 insertions(+), 6 deletions(-) create mode 100644 economy/migrations/0006_socirankedseason.py diff --git a/economy/admin.py b/economy/admin.py index 721dcde8..5f5e1984 100644 --- a/economy/admin.py +++ b/economy/admin.py @@ -11,6 +11,7 @@ SociOrderSessionOrder, ExternalCharge, ProductGhostOrder, + SociRankedSeason, ) @@ -91,3 +92,12 @@ class ExternalChargeAdmin(admin.ModelAdmin): @admin.register(ProductGhostOrder) class ProductGhostOrderAdmin(admin.ModelAdmin): pass + + +@admin.register(SociRankedSeason) +class SociRankedSeasonAdmin(admin.ModelAdmin): + list_display = ["id", "participants", "season_start_date", "season_end_date"] + + @staticmethod + def participants(soci_ranked_season: SociRankedSeason): + return soci_ranked_season.participants.count() diff --git a/economy/migrations/0006_socirankedseason.py b/economy/migrations/0006_socirankedseason.py new file mode 100644 index 00000000..367d230e --- /dev/null +++ b/economy/migrations/0006_socirankedseason.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.7 on 2024-10-25 20:02 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('economy', '0005_stockmarketcrash'), + ] + + operations = [ + migrations.CreateModel( + name='SociRankedSeason', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('season_start_date', models.DateField()), + ('season_end_date', models.DateField(blank=True, default=None, null=True)), + ('participants', models.ManyToManyField(to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/economy/models.py b/economy/models.py index 7b7cac11..aa35d940 100644 --- a/economy/models.py +++ b/economy/models.py @@ -560,3 +560,9 @@ def __str__(self): class StockMarketCrash(models.Model): timestamp = models.DateTimeField(auto_now=True) + + +class SociRankedSeason(models.Model): + season_start_date = models.DateField() + season_end_date = models.DateField(default=None, blank=True, null=True) + participants = models.ManyToManyField(User) diff --git a/economy/schema.py b/economy/schema.py index 142cd755..3f598e68 100644 --- a/economy/schema.py +++ b/economy/schema.py @@ -12,12 +12,8 @@ ) from django.db import transaction from graphene import Node -from django.db.models import ( - Q, - Sum, - Avg, -) -from django.db.models.functions import Coalesce, TruncDate +from django.db.models import Q, Sum, Avg, Window, F +from django.db.models.functions import Coalesce, TruncDate, Rank from graphene_django import DjangoObjectType from django.utils import timezone from graphene_django_cud.mutations import ( @@ -49,6 +45,7 @@ SociOrderSessionOrder, ProductGhostOrder, StockMarketCrash, + SociRankedSeason, ) from economy.price_strategies import calculate_stock_price_for_product from schedules.models import Schedule @@ -256,6 +253,12 @@ def get_node(cls, info, id): return SociOrderSession.objects.get(pk=id) +class SociRankedSeasonNode(DjangoObjectType): + class Meta: + model = SociRankedSeason + interfaces = (Node,) + + class SociProductQuery(graphene.ObjectType): soci_product = Node.Field(SociProductNode) all_active_soci_products = DjangoConnectionField(SociProductNode) @@ -685,6 +688,122 @@ def resolve_last_market_crash(self, info, *args, **kwargs): return StockMarketCrash.objects.all().order_by("-timestamp").first() +class LeaderboardEntry(graphene.ObjectType): + name = graphene.String() + expenditure = graphene.Int() + + +class CurrentRankSeason(graphene.ObjectType): + is_participant = graphene.Boolean() + season_expenditure = graphene.Int() + placement = graphene.Int() + season_start = graphene.Date() + season_end = graphene.Date() + top_ten = graphene.List(LeaderboardEntry) + participant_count = graphene.Int() + + +class SociRankedQuery(graphene.ObjectType): + current_ranked_season = graphene.Field(CurrentRankSeason) + + @gql_login_required() + def resolve_current_ranked_season(self, info, *args, **kwargs): + current_user = info.context.user + + current_season = ( + SociRankedSeason.objects.all().order_by("season_start_date").last() + ) + + if not current_season: + return CurrentRankSeason( + is_participant=False, season_expenditure=0, placement=None + ) + + season_start_datetime = timezone.make_aware( + timezone.datetime( + year=current_season.season_start_date.year, + month=current_season.season_start_date.month, + day=current_season.season_start_date.day, + hour=0, + minute=0, + second=0, + ), + timezone=pytz.timezone(settings.TIME_ZONE), + ) + season_filter = Q( + bank_account__product_orders__purchased_at__gte=season_start_datetime + ) + + leaderboard = current_season.participants.annotate( + expenditure=Coalesce( + Sum("bank_account__product_orders__cost", filter=season_filter), 0 + ), + ).order_by("-expenditure") + + + is_participant = current_season.participants.filter(id=current_user.id).exists() + if not is_participant and not current_user.is_superuser: + return CurrentRankSeason( + is_participant=False, + season_expenditure=0, + placement=None, + participant_count=leaderboard.count(), + ) + + placement = 0 + user_expenditure = 0 + + for index, entry in enumerate(leaderboard): + # I'm terrible at complex query expressions in Django + # - Alexander Orvik 25-10-2024 + if entry.id == current_user.id: + placement = index + 1 + user_expenditure = entry.expenditure + break + + top_ten = leaderboard[0:10] + + top_ten_data_list = [] + for entry in top_ten: + top_ten_data_list.append( + LeaderboardEntry( + name=entry.get_full_name(), expenditure=entry.expenditure + ) + ) + + return CurrentRankSeason( + is_participant=True, + season_expenditure=user_expenditure, + placement=placement, + top_ten=top_ten_data_list, + season_start=current_season.season_start_date, + season_end=current_season.season_end_date, + participant_count=leaderboard.count(), + ) + + +class JoinRankedSeasonMutation(graphene.Mutation): + + class Arguments: + pass + + success = graphene.Boolean() + message = graphene.String() + + @gql_login_required() + def mutate(self, info): + last_season = SociRankedSeason.objects.order_by("season_start_date").last() + if last_season.season_end_date: + # logger.error TODO + return JoinRankedSeasonMutation( + success=False, message="Cannot join finished season" + ) + + current_user = info.context.user + last_season.participants.add(current_user) + return JoinRankedSeasonMutation(success=True) + + class UndoProductOrderMutation(graphene.Mutation): class Arguments: id = graphene.ID(required=True) @@ -1316,3 +1435,5 @@ class EconomyMutations(graphene.ObjectType): increment_product_ghost_order = IncrementProductGhostOrderMutation.Field() crash_stock_market = CrashStockMarketMutation.Field() + + join_ranked_season = JoinRankedSeasonMutation.Field() diff --git a/ksg_nett/schema.py b/ksg_nett/schema.py index 3e483742..049994af 100644 --- a/ksg_nett/schema.py +++ b/ksg_nett/schema.py @@ -24,6 +24,7 @@ SociSessionQuery, StockMarketQuery, StripeQuery, + SociRankedQuery ) from bar_tab.schema import ( BarTabQuery, @@ -74,6 +75,7 @@ class Query( InternalGroupUserHighlightQuery, InterviewQuery, InterviewLocationQuery, + SociRankedQuery, ShiftQuery, ScheduleQuery, ScheduleTemplateQuery,