@@ -626,6 +626,8 @@ async def update_quotas(
626626 else 0
627627 )
628628
629+ quotas .context = None
630+
629631 update = quotas .dict (
630632 exclude_unset = True , exclude_defaults = True , exclude_none = True
631633 )
@@ -674,6 +676,71 @@ async def update_quotas(
674676 {"$inc" : {"giftedExecSecondsAvailable" : gifted_secs_diff }},
675677 )
676678
679+ async def add_to_org_quotas (
680+ self , org : Organization , quotas : OrgQuotasIn , context : str | None
681+ ):
682+ previous_extra_mins = (
683+ org .quotas .extraExecMinutes
684+ if (org .quotas and org .quotas .extraExecMinutes )
685+ else 0
686+ )
687+ previous_gifted_mins = (
688+ org .quotas .giftedExecMinutes
689+ if (org .quotas and org .quotas .giftedExecMinutes )
690+ else 0
691+ )
692+
693+ update : dict [str , Any ] = {
694+ "$inc" : {},
695+ }
696+
697+ for field , value in quotas .model_dump (exclude_unset = True ).items ():
698+ if field == "context" or value is None :
699+ continue
700+ inc = max (value , - org .quotas .model_dump ().get (field , 0 ))
701+ update ["$inc" ][f"quotas.{ field } " ] = inc
702+
703+ updated_org = await self .orgs .find_one_and_update (
704+ {"_id" : org .id },
705+ update ,
706+ projection = {"quotas" : True },
707+ return_document = ReturnDocument .AFTER ,
708+ )
709+ updated_quotas = OrgQuotas (** updated_org ["quotas" ])
710+
711+ quotaUpdate : dict [str , dict [str , dict [str , Any ] | int ]] = {
712+ "$push" : {
713+ "quotaUpdates" : OrgQuotaUpdate (
714+ modified = dt_now (),
715+ update = updated_quotas ,
716+ context = context ,
717+ ).model_dump ()
718+ },
719+ "$inc" : {},
720+ "$set" : {},
721+ }
722+
723+ # Inc org available fields for extra/gifted execution time as needed
724+ if updated_quotas .extraExecMinutes is not None :
725+ extra_secs_diff = (
726+ updated_quotas .extraExecMinutes - previous_extra_mins
727+ ) * 60
728+ if org .extraExecSecondsAvailable + extra_secs_diff <= 0 :
729+ quotaUpdate ["$set" ]["extraExecSecondsAvailable" ] = 0
730+ else :
731+ quotaUpdate ["$inc" ]["extraExecSecondsAvailable" ] = extra_secs_diff
732+
733+ if updated_quotas .giftedExecMinutes is not None :
734+ gifted_secs_diff = (
735+ updated_quotas .giftedExecMinutes - previous_gifted_mins
736+ ) * 60
737+ if org .giftedExecSecondsAvailable + gifted_secs_diff <= 0 :
738+ quotaUpdate ["$set" ]["giftedExecSecondsAvailable" ] = 0
739+ else :
740+ quotaUpdate ["$inc" ]["giftedExecSecondsAvailable" ] = gifted_secs_diff
741+
742+ await self .orgs .find_one_and_update ({"_id" : org .id }, quotaUpdate )
743+
677744 async def update_event_webhook_urls (
678745 self , org : Organization , urls : OrgWebhookUrls
679746 ) -> bool :
@@ -1678,14 +1745,27 @@ async def get_plans(user: User = Depends(user_dep)):
16781745 except ValidationError as err :
16791746 raise HTTPException (status_code = 400 , detail = "invalid_plans" ) from err
16801747
1748+ @router .post ("/quotas" , tags = ["organizations" ], response_model = UpdatedResponse )
1749+ async def update_quotas (
1750+ quotas : OrgQuotasIn ,
1751+ org : Organization = Depends (org_owner_dep ),
1752+ user : User = Depends (user_dep ),
1753+ ):
1754+ if not user .is_superuser :
1755+ raise HTTPException (status_code = 403 , detail = "Not Allowed" )
1756+
1757+ await ops .update_quotas (org , quotas , quotas .context )
1758+
1759+ return {"updated" : True }
1760+
16811761 @app .post (
1682- "/orgs/{oid}/quotas" , tags = ["organizations" ], response_model = UpdatedResponse
1762+ "/orgs/{oid}/quotas/add " , tags = ["organizations" ], response_model = UpdatedResponse
16831763 )
1684- async def update_quotas (
1764+ async def update_quotas_add (
16851765 quotas : OrgQuotasIn ,
16861766 org : Organization = Depends (org_superuser_or_shared_secret_dep ),
16871767 ):
1688- await ops .update_quotas (org , quotas , quotas .context )
1768+ await ops .add_to_org_quotas (org , quotas , quotas .context )
16891769
16901770 return {"updated" : True }
16911771
0 commit comments