From 382ba01f3e3a08aa78f3f3f1e0b155276005e996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Gremaud?= Date: Wed, 7 Oct 2026 21:44:33 +0200 Subject: [PATCH] fix(market): per-event bet loss+win --- polylan_submitter/market/api.py | 41 +++++++++++++++----------- polylan_submitter/market/models.py | 18 +++++++++++ polylan_submitter/market/schemas.py | 4 +++ polylan_submitter/src/Market.vue | 4 +-- polylan_submitter/src/api/index.ts | 4 +-- polylan_submitter/src/api/sdk.gen.ts | 9 +++++- polylan_submitter/src/api/types.gen.ts | 31 +++++++++++++++++++ polylan_submitter/src/stores/market.ts | 22 ++++++++++++-- 8 files changed, 107 insertions(+), 26 deletions(-) diff --git a/polylan_submitter/market/api.py b/polylan_submitter/market/api.py index 507245b..d746471 100644 --- a/polylan_submitter/market/api.py +++ b/polylan_submitter/market/api.py @@ -6,12 +6,19 @@ from django.db.models import Sum, Prefetch from django.db.models.functions import Coalesce from django.db import transaction -from market.models import Market, MarketOption, UserBet, UserPointChange +from market.models import ( + Market, + MarketOption, + UserBet, + UserPointChange, + get_event_points, +) from market.schemas import ( MarketListSchema, ResolveMarketSchema, UserBetCreateSchema, UserBetSchema, + UserPointsSchema, ) @@ -45,6 +52,15 @@ def list_user_bets(request, event_path: str): ) +@router.get(f"{PREFIX}/user/points", response=UserPointsSchema) +def get_user_points(request, event_path: str): + """Get the current user's points balance for this event.""" + if not request.user.is_authenticated: + raise HttpError(401, "Authentication required") + + return {"points": get_event_points(request.user, request.event)} + + @router.post(f"{PREFIX}/{{market_uuid}}/actions/close") def close_market(request, event_path: str, market_uuid: str): """Close a market. Admin only.""" @@ -76,9 +92,7 @@ def resolve_market( market.save(update_fields=["winning_option", "status", "updated_at"]) # Calculate and distribute winnings - all_bets = list( - UserBet.objects.filter(option__market=market).select_related("user") - ) + all_bets = list(UserBet.objects.filter(option__market=market)) # Calculate total pot total_pot = sum(bet.amount for bet in all_bets) @@ -92,7 +106,6 @@ def resolve_market( total_winning = sum(bet.amount for bet in winning_bets) point_changes = [] - users_to_update = [] with transaction.atomic(): # Award payouts to winners with multiplier @@ -101,11 +114,9 @@ def resolve_market( payout = round( bet.amount / total_winning * total_pot * market.multiplier ) - bet.user.points += payout - users_to_update.append(bet.user) point_changes.append( UserPointChange( - user=bet.user, + user_id=bet.user_id, market=market, amount=payout, reason=UserPointChange.Reason.BET_WON, @@ -116,17 +127,13 @@ def resolve_market( for bet in losing_bets: point_changes.append( UserPointChange( - user=bet.user, + user_id=bet.user_id, market=market, amount=-bet.amount, reason=UserPointChange.Reason.BET_LOST, ) ) - # Bulk update users - for user in users_to_update: - user.save(update_fields=["points"]) - # Bulk create point changes UserPointChange.objects.bulk_create(point_changes) @@ -167,8 +174,8 @@ def create_bet( # Calculate delta (amount to deduct from user's points) delta = payload.amount - (existing_bet.amount if existing_bet else 0) - # Check if user has enough points - if request.user.points < delta: + # Check if user has enough points for this event + if get_event_points(request.user, request.event) < delta: raise HttpError(400, "Insufficient points for this bet") user_bet, created = UserBet.objects.update_or_create( @@ -177,9 +184,7 @@ def create_bet( defaults={"amount": payload.amount}, ) - # Deduct points and record the change - request.user.points -= delta - request.user.save(update_fields=["points"]) + # Record the change (the event balance is derived from these) UserPointChange.objects.create( user=request.user, market=market, diff --git a/polylan_submitter/market/models.py b/polylan_submitter/market/models.py index 507274b..9e50b7b 100644 --- a/polylan_submitter/market/models.py +++ b/polylan_submitter/market/models.py @@ -1,10 +1,28 @@ import uuid from django.db import models +from django.db.models import Sum from events.models import Event from events.scoping import EventScopedManager +# Each event's market starts every user with this balance +STARTING_POINTS = 1000 + + +def get_event_points(user, event) -> int: + """User's market balance for an event: starting points + the event's point changes. + + BET_LOST rows are informational (the stake was already deducted by BET_PLACED). + """ + total = ( + UserPointChange.objects.filter(user=user, market__event=event) + .exclude(reason=UserPointChange.Reason.BET_LOST) + .aggregate(total=Sum("amount"))["total"] + ) + return STARTING_POINTS + (total or 0) + + class BaseModel(models.Model): uuid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False) created_at = models.DateTimeField(auto_now_add=True) diff --git a/polylan_submitter/market/schemas.py b/polylan_submitter/market/schemas.py index c10da3e..9248745 100644 --- a/polylan_submitter/market/schemas.py +++ b/polylan_submitter/market/schemas.py @@ -35,6 +35,10 @@ class ResolveMarketSchema(Schema): winning_option_uuid: str +class UserPointsSchema(Schema): + points: int + + class UserBetCreateSchema(Schema): option_uuid: str amount: int diff --git a/polylan_submitter/src/Market.vue b/polylan_submitter/src/Market.vue index 8305304..e8e0533 100644 --- a/polylan_submitter/src/Market.vue +++ b/polylan_submitter/src/Market.vue @@ -19,7 +19,7 @@ const { t } = useI18n(); useTitle(computed(() => t("market.pageTitle"))); const marketStore = useMarketStore(); -const { markets, userInfo, isLoading } = storeToRefs(marketStore); +const { markets, userInfo, userPoints, isLoading } = storeToRefs(marketStore); const reloadPage = async () => { await marketStore.refreshPage(props.eventPath); @@ -56,7 +56,7 @@ onMounted(() => { {{ t("market.myBets") }}
- {{ userInfo.points }} + {{ userPoints }} {{ t("common.pts") }}
diff --git a/polylan_submitter/src/api/index.ts b/polylan_submitter/src/api/index.ts index a102f44..65a3e46 100644 --- a/polylan_submitter/src/api/index.ts +++ b/polylan_submitter/src/api/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { animationsApiPuzzleResults, animationsApiResults, animationsApiTopSubmissions, clockApiCurrentState, clockApiMyStats, gamesApiListEventGames, gamesApiListGames, marketApiCloseMarket, marketApiCreateBet, marketApiListMarkets, marketApiListUserBets, marketApiResolveMarket, noitaApiGetLeaderboard, noitaApiGetResults, noitaApiSubmitLogFile, type Options, opusMagnumApiCreateSubmission, opusMagnumApiDeleteSubmission, opusMagnumApiGetCollection, opusMagnumApiGetStats, opusMagnumApiGetSubmission, opusMagnumApiListPuzzles, opusMagnumApiListResponsesNeedingValidation, opusMagnumApiListSubmissions, opusMagnumApiValidateAuto, opusMagnumApiValidateResponse, opusMagnumApiValidateSubmission, placeApiCanvas, placeApiEvolutionGif, polylanSubmitterApiClearCache, polylanSubmitterApiGetConfig, polylanSubmitterApiGetUserInfo, polylanSubmitterApiHealthCheck, submissionsApiCreateImageCrop, submissionsApiCreateSubmission, submissionsApiDeleteImageCrop, submissionsApiGetSubmission, submissionsApiListAvailableScopes, submissionsApiListMyScopes, submissionsApiListSubmissionsByGame, submissionsApiListUserSubmissions, submissionsApiProcessCropOcr, submissionsApiProcessFileOcr } from './sdk.gen'; -export type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponse, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponse, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponse, AnimationsApiTopSubmissionsResponses, ClientOptions, ClockApiCurrentStateData, ClockApiCurrentStateResponse, ClockApiCurrentStateResponses, ClockApiMyStatsData, ClockApiMyStatsResponse, ClockApiMyStatsResponses, ClockStateOut, ConfigOut, GameOut, GamesApiListEventGamesData, GamesApiListEventGamesResponse, GamesApiListEventGamesResponses, GamesApiListGamesData, GamesApiListGamesResponse, GamesApiListGamesResponses, ImageCropIn, ImageCropOut, Input, LeaderboardEntryOut, LeaderboardOut, MarketApiCloseMarketData, MarketApiCloseMarketResponses, MarketApiCreateBetData, MarketApiCreateBetResponse, MarketApiCreateBetResponses, MarketApiListMarketsData, MarketApiListMarketsResponse, MarketApiListMarketsResponses, MarketApiListUserBetsData, MarketApiListUserBetsResponse, MarketApiListUserBetsResponses, MarketApiResolveMarketData, MarketApiResolveMarketResponse, MarketApiResolveMarketResponses, MarketListSchema, MarketOptionSchema, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponse, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponse, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileError, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponse, NoitaApiSubmitLogFileResponses, NoitaSubmissionOut, ObjectivResultOut, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponse, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponse, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponse, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponse, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponse, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponse, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponse, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponse, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponse, OpusMagnumApiValidateSubmissionResponses, PagedSubmissionOut, PlaceApiCanvasData, PlaceApiCanvasResponses, PlaceApiEvolutionGifData, PlaceApiEvolutionGifResponses, PlayerStatsOut, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetConfigData, PolylanSubmitterApiGetConfigResponse, PolylanSubmitterApiGetConfigResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponse, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, PuzzlePointsFactorOut, PuzzleResponseIn, PuzzleResponseOut, PuzzleResponseRankingOut, PuzzleResultsOut, PuzzleSubmissionsOut, PuzzleSubmissionWithRankOut, RankingSchema, ResolveMarketSchema, ResultsOut, ScopeOut, SteamCollectionItemOut, SteamCollectionOut, SubmissionFileOut, SubmissionIn, SubmissionOut, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropResponse, SubmissionsApiCreateImageCropResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionError, SubmissionsApiCreateSubmissionErrors, SubmissionsApiCreateSubmissionResponse, SubmissionsApiCreateSubmissionResponses, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropResponse, SubmissionsApiDeleteImageCropResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponse, SubmissionsApiGetSubmissionResponses, SubmissionsApiListAvailableScopesData, SubmissionsApiListAvailableScopesError, SubmissionsApiListAvailableScopesErrors, SubmissionsApiListAvailableScopesResponse, SubmissionsApiListAvailableScopesResponses, SubmissionsApiListMyScopesData, SubmissionsApiListMyScopesError, SubmissionsApiListMyScopesErrors, SubmissionsApiListMyScopesResponse, SubmissionsApiListMyScopesResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponse, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsError, SubmissionsApiListUserSubmissionsErrors, SubmissionsApiListUserSubmissionsResponse, SubmissionsApiListUserSubmissionsResponses, SubmissionsApiProcessCropOcrData, SubmissionsApiProcessCropOcrResponse, SubmissionsApiProcessCropOcrResponses, SubmissionsApiProcessFileOcrData, SubmissionsApiProcessFileOcrResponse, SubmissionsApiProcessFileOcrResponses, TournamentPuzzleResultsOut, TournamentSubmissionsOut, UserBetCreateSchema, UserBetSchema, UserDisplayOut, UserInfoOut, ValidationIn, WinnerFileOut, WinnerResponseOut } from './types.gen'; +export { animationsApiPuzzleResults, animationsApiResults, animationsApiTopSubmissions, clockApiCurrentState, clockApiMyStats, gamesApiListEventGames, gamesApiListGames, marketApiCloseMarket, marketApiCreateBet, marketApiGetUserPoints, marketApiListMarkets, marketApiListUserBets, marketApiResolveMarket, noitaApiGetLeaderboard, noitaApiGetResults, noitaApiSubmitLogFile, type Options, opusMagnumApiCreateSubmission, opusMagnumApiDeleteSubmission, opusMagnumApiGetCollection, opusMagnumApiGetStats, opusMagnumApiGetSubmission, opusMagnumApiListPuzzles, opusMagnumApiListResponsesNeedingValidation, opusMagnumApiListSubmissions, opusMagnumApiValidateAuto, opusMagnumApiValidateResponse, opusMagnumApiValidateSubmission, placeApiCanvas, placeApiEvolutionGif, polylanSubmitterApiClearCache, polylanSubmitterApiGetConfig, polylanSubmitterApiGetUserInfo, polylanSubmitterApiHealthCheck, submissionsApiCreateImageCrop, submissionsApiCreateSubmission, submissionsApiDeleteImageCrop, submissionsApiGetSubmission, submissionsApiListAvailableScopes, submissionsApiListMyScopes, submissionsApiListSubmissionsByGame, submissionsApiListUserSubmissions, submissionsApiProcessCropOcr, submissionsApiProcessFileOcr } from './sdk.gen'; +export type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponse, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponse, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponse, AnimationsApiTopSubmissionsResponses, ClientOptions, ClockApiCurrentStateData, ClockApiCurrentStateResponse, ClockApiCurrentStateResponses, ClockApiMyStatsData, ClockApiMyStatsResponse, ClockApiMyStatsResponses, ClockStateOut, ConfigOut, GameOut, GamesApiListEventGamesData, GamesApiListEventGamesResponse, GamesApiListEventGamesResponses, GamesApiListGamesData, GamesApiListGamesResponse, GamesApiListGamesResponses, ImageCropIn, ImageCropOut, Input, LeaderboardEntryOut, LeaderboardOut, MarketApiCloseMarketData, MarketApiCloseMarketResponses, MarketApiCreateBetData, MarketApiCreateBetResponse, MarketApiCreateBetResponses, MarketApiGetUserPointsData, MarketApiGetUserPointsResponse, MarketApiGetUserPointsResponses, MarketApiListMarketsData, MarketApiListMarketsResponse, MarketApiListMarketsResponses, MarketApiListUserBetsData, MarketApiListUserBetsResponse, MarketApiListUserBetsResponses, MarketApiResolveMarketData, MarketApiResolveMarketResponse, MarketApiResolveMarketResponses, MarketListSchema, MarketOptionSchema, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponse, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponse, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileError, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponse, NoitaApiSubmitLogFileResponses, NoitaSubmissionOut, ObjectivResultOut, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponse, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponse, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponse, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponse, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponse, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponse, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponse, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponse, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponse, OpusMagnumApiValidateSubmissionResponses, PagedSubmissionOut, PlaceApiCanvasData, PlaceApiCanvasResponses, PlaceApiEvolutionGifData, PlaceApiEvolutionGifResponses, PlayerStatsOut, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetConfigData, PolylanSubmitterApiGetConfigResponse, PolylanSubmitterApiGetConfigResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponse, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, PuzzlePointsFactorOut, PuzzleResponseIn, PuzzleResponseOut, PuzzleResponseRankingOut, PuzzleResultsOut, PuzzleSubmissionsOut, PuzzleSubmissionWithRankOut, RankingSchema, ResolveMarketSchema, ResultsOut, ScopeOut, SteamCollectionItemOut, SteamCollectionOut, SubmissionFileOut, SubmissionIn, SubmissionOut, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropResponse, SubmissionsApiCreateImageCropResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionError, SubmissionsApiCreateSubmissionErrors, SubmissionsApiCreateSubmissionResponse, SubmissionsApiCreateSubmissionResponses, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropResponse, SubmissionsApiDeleteImageCropResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponse, SubmissionsApiGetSubmissionResponses, SubmissionsApiListAvailableScopesData, SubmissionsApiListAvailableScopesError, SubmissionsApiListAvailableScopesErrors, SubmissionsApiListAvailableScopesResponse, SubmissionsApiListAvailableScopesResponses, SubmissionsApiListMyScopesData, SubmissionsApiListMyScopesError, SubmissionsApiListMyScopesErrors, SubmissionsApiListMyScopesResponse, SubmissionsApiListMyScopesResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponse, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsError, SubmissionsApiListUserSubmissionsErrors, SubmissionsApiListUserSubmissionsResponse, SubmissionsApiListUserSubmissionsResponses, SubmissionsApiProcessCropOcrData, SubmissionsApiProcessCropOcrResponse, SubmissionsApiProcessCropOcrResponses, SubmissionsApiProcessFileOcrData, SubmissionsApiProcessFileOcrResponse, SubmissionsApiProcessFileOcrResponses, TournamentPuzzleResultsOut, TournamentSubmissionsOut, UserBetCreateSchema, UserBetSchema, UserDisplayOut, UserInfoOut, UserPointsSchema, ValidationIn, WinnerFileOut, WinnerResponseOut } from './types.gen'; diff --git a/polylan_submitter/src/api/sdk.gen.ts b/polylan_submitter/src/api/sdk.gen.ts index b56bdc1..b9cb5d0 100644 --- a/polylan_submitter/src/api/sdk.gen.ts +++ b/polylan_submitter/src/api/sdk.gen.ts @@ -2,7 +2,7 @@ import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape } from './client'; import { client } from './client.gen'; -import type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponses, ClockApiCurrentStateData, ClockApiCurrentStateResponses, ClockApiMyStatsData, ClockApiMyStatsResponses, GamesApiListEventGamesData, GamesApiListEventGamesResponses, GamesApiListGamesData, GamesApiListGamesResponses, MarketApiCloseMarketData, MarketApiCloseMarketResponses, MarketApiCreateBetData, MarketApiCreateBetResponses, MarketApiListMarketsData, MarketApiListMarketsResponses, MarketApiListUserBetsData, MarketApiListUserBetsResponses, MarketApiResolveMarketData, MarketApiResolveMarketResponses, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponses, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponses, PlaceApiCanvasData, PlaceApiCanvasResponses, PlaceApiEvolutionGifData, PlaceApiEvolutionGifResponses, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetConfigData, PolylanSubmitterApiGetConfigResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionErrors, SubmissionsApiCreateSubmissionResponses, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponses, SubmissionsApiListAvailableScopesData, SubmissionsApiListAvailableScopesErrors, SubmissionsApiListAvailableScopesResponses, SubmissionsApiListMyScopesData, SubmissionsApiListMyScopesErrors, SubmissionsApiListMyScopesResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsErrors, SubmissionsApiListUserSubmissionsResponses, SubmissionsApiProcessCropOcrData, SubmissionsApiProcessCropOcrResponses, SubmissionsApiProcessFileOcrData, SubmissionsApiProcessFileOcrResponses } from './types.gen'; +import type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponses, ClockApiCurrentStateData, ClockApiCurrentStateResponses, ClockApiMyStatsData, ClockApiMyStatsResponses, GamesApiListEventGamesData, GamesApiListEventGamesResponses, GamesApiListGamesData, GamesApiListGamesResponses, MarketApiCloseMarketData, MarketApiCloseMarketResponses, MarketApiCreateBetData, MarketApiCreateBetResponses, MarketApiGetUserPointsData, MarketApiGetUserPointsResponses, MarketApiListMarketsData, MarketApiListMarketsResponses, MarketApiListUserBetsData, MarketApiListUserBetsResponses, MarketApiResolveMarketData, MarketApiResolveMarketResponses, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponses, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponses, PlaceApiCanvasData, PlaceApiCanvasResponses, PlaceApiEvolutionGifData, PlaceApiEvolutionGifResponses, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetConfigData, PolylanSubmitterApiGetConfigResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionErrors, SubmissionsApiCreateSubmissionResponses, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponses, SubmissionsApiListAvailableScopesData, SubmissionsApiListAvailableScopesErrors, SubmissionsApiListAvailableScopesResponses, SubmissionsApiListMyScopesData, SubmissionsApiListMyScopesErrors, SubmissionsApiListMyScopesResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsErrors, SubmissionsApiListUserSubmissionsResponses, SubmissionsApiProcessCropOcrData, SubmissionsApiProcessCropOcrResponses, SubmissionsApiProcessFileOcrData, SubmissionsApiProcessFileOcrResponses } from './types.gen'; export type Options = Options2 & { /** @@ -319,6 +319,13 @@ export const marketApiListMarkets = (optio */ export const marketApiListUserBets = (options: Options) => (options.client ?? client).get({ url: '/api/events/{event_path}/market/user/bets', ...options }); +/** + * Get User Points + * + * Get the current user's points balance for this event. + */ +export const marketApiGetUserPoints = (options: Options) => (options.client ?? client).get({ url: '/api/events/{event_path}/market/user/points', ...options }); + /** * Close Market * diff --git a/polylan_submitter/src/api/types.gen.ts b/polylan_submitter/src/api/types.gen.ts index b8c6bf4..ec2ea7b 100644 --- a/polylan_submitter/src/api/types.gen.ts +++ b/polylan_submitter/src/api/types.gen.ts @@ -1153,6 +1153,16 @@ export type UserBetSchema = { market?: MarketListSchema | null; }; +/** + * UserPointsSchema + */ +export type UserPointsSchema = { + /** + * Points + */ + points: number; +}; + /** * ResolveMarketSchema */ @@ -2173,6 +2183,27 @@ export type MarketApiListUserBetsResponses = { export type MarketApiListUserBetsResponse = MarketApiListUserBetsResponses[keyof MarketApiListUserBetsResponses]; +export type MarketApiGetUserPointsData = { + body?: never; + path: { + /** + * Event Path + */ + event_path: string; + }; + query?: never; + url: '/api/events/{event_path}/market/user/points'; +}; + +export type MarketApiGetUserPointsResponses = { + /** + * OK + */ + 200: UserPointsSchema; +}; + +export type MarketApiGetUserPointsResponse = MarketApiGetUserPointsResponses[keyof MarketApiGetUserPointsResponses]; + export type MarketApiCloseMarketData = { body?: never; path: { diff --git a/polylan_submitter/src/stores/market.ts b/polylan_submitter/src/stores/market.ts index a59ddae..9f8f196 100644 --- a/polylan_submitter/src/stores/market.ts +++ b/polylan_submitter/src/stores/market.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia' import { ref } from 'vue' -import { polylanSubmitterApiGetUserInfo, marketApiListMarkets, marketApiListUserBets } from '@/api' +import { polylanSubmitterApiGetUserInfo, marketApiListMarkets, marketApiListUserBets, marketApiGetUserPoints } from '@/api' import type { Market } from '@/types' import type { UserInfoOut, UserBetSchema } from '@/api/types.gen' @@ -9,6 +9,7 @@ export const useMarketStore = defineStore('market', () => { const markets = ref([]) const userInfo = ref() const userBets = ref([]) + const userPoints = ref(0) const isLoading = ref(true) const error = ref('') @@ -49,6 +50,18 @@ export const useMarketStore = defineStore('market', () => { } } + const loadUserPoints = async (eventPath: string) => { + try { + const response = await marketApiGetUserPoints({ path: { event_path: eventPath } }) + if (response.data) { + userPoints.value = response.data.points + } + } catch (err) { + error.value = 'Failed to load user points' + console.error('Error loading user points:', err) + } + } + const initializeMarketPage = async (eventPath: string) => { isLoading.value = true error.value = '' @@ -59,9 +72,9 @@ export const useMarketStore = defineStore('market', () => { loadMarkets(eventPath), ]) - // Load user bets if authenticated + // Load user bets and points if authenticated if (userInfo.value?.is_authenticated) { - await loadUserBets(eventPath) + await Promise.all([loadUserBets(eventPath), loadUserPoints(eventPath)]) } } finally { isLoading.value = false @@ -73,6 +86,7 @@ export const useMarketStore = defineStore('market', () => { loadUserInfo(), loadMarkets(eventPath), userInfo.value?.is_authenticated ? loadUserBets(eventPath) : Promise.resolve(), + userInfo.value?.is_authenticated ? loadUserPoints(eventPath) : Promise.resolve(), ]) } @@ -81,6 +95,7 @@ export const useMarketStore = defineStore('market', () => { markets, userInfo, userBets, + userPoints, isLoading, error, @@ -88,6 +103,7 @@ export const useMarketStore = defineStore('market', () => { loadUserInfo, loadMarkets, loadUserBets, + loadUserPoints, initializeMarketPage, refreshPage, }