Skip to content

Commit 399934f

Browse files
authored
Merge pull request #3055 from webrecorder/additional-minutes-addon-purchase--add-sub-add-minutes-events
2 parents 7a1781c + 19db5fa commit 399934f

File tree

9 files changed

+162
-99
lines changed

9 files changed

+162
-99
lines changed

.github/workflows/microk8s-ci.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ jobs:
1717
btrix-microk8s-test:
1818
runs-on: ubuntu-latest
1919
steps:
20+
- name: Initial Disk Cleanup
21+
uses: mathio/gha-cleanup@v1
22+
with:
23+
remove-browsers: true
24+
verbose: true
25+
2026
- uses: balchua/[email protected]
2127
with:
2228
channel: "1.25/stable"

backend/btrixcloud/main.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ def main() -> None:
181181
crawl_manager,
182182
invites,
183183
current_active_user,
184-
shared_secret_or_superuser,
185184
)
186185

187186
init_subs_api(app, mdb, org_ops, user_manager, shared_secret_or_superuser)

backend/btrixcloud/models.py

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,6 +1829,8 @@ class S3Storage(BaseModel):
18291829
REASON_PAUSED = "subscriptionPaused"
18301830
REASON_CANCELED = "subscriptionCanceled"
18311831

1832+
SubscriptionEventType = Literal["create", "import", "update", "cancel", "add-minutes"]
1833+
18321834

18331835
# ============================================================================
18341836
class OrgQuotas(BaseModel):
@@ -1857,8 +1859,6 @@ class OrgQuotasIn(BaseModel):
18571859
extraExecMinutes: Optional[int] = None
18581860
giftedExecMinutes: Optional[int] = None
18591861

1860-
context: str | None = None
1861-
18621862

18631863
# ============================================================================
18641864
class Plan(BaseModel):
@@ -1883,6 +1883,7 @@ class SubscriptionEventOut(BaseModel):
18831883

18841884
oid: UUID
18851885
timestamp: datetime
1886+
type: SubscriptionEventType
18861887

18871888

18881889
# ============================================================================
@@ -1948,18 +1949,55 @@ class SubscriptionCancel(BaseModel):
19481949

19491950

19501951
# ============================================================================
1951-
class SubscriptionTrialEndReminder(BaseModel):
1952-
"""Email reminder that subscription will end soon"""
1952+
class SubscriptionCancelOut(SubscriptionCancel, SubscriptionEventOut):
1953+
"""Output model for subscription cancellation event"""
19531954

1954-
subId: str
1955-
behavior_on_trial_end: Literal["cancel", "continue", "read-only"]
1955+
type: Literal["cancel"] = "cancel"
19561956

19571957

19581958
# ============================================================================
1959-
class SubscriptionCancelOut(SubscriptionCancel, SubscriptionEventOut):
1960-
"""Output model for subscription cancellation event"""
1959+
class SubscriptionAddMinutes(BaseModel):
1960+
"""Represents a purchase of additional minutes"""
1961+
1962+
oid: UUID
1963+
minutes: int
1964+
total_price: float
1965+
currency: str
1966+
1967+
context: str
19611968

1962-
type: Literal["cancel"] = "cancel"
1969+
1970+
# ============================================================================
1971+
class SubscriptionAddMinutesOut(SubscriptionAddMinutes, SubscriptionEventOut):
1972+
"""SubscriptionAddMinutes output model"""
1973+
1974+
type: Literal["add-minutes"] = "add-minutes"
1975+
1976+
1977+
# ============================================================================
1978+
SubscriptionEventAny = Union[
1979+
SubscriptionCreate,
1980+
SubscriptionUpdate,
1981+
SubscriptionCancel,
1982+
SubscriptionImport,
1983+
SubscriptionAddMinutes,
1984+
]
1985+
1986+
SubscriptionEventAnyOut = Union[
1987+
SubscriptionCreateOut,
1988+
SubscriptionUpdateOut,
1989+
SubscriptionCancelOut,
1990+
SubscriptionImportOut,
1991+
SubscriptionAddMinutesOut,
1992+
]
1993+
1994+
1995+
# ============================================================================
1996+
class SubscriptionTrialEndReminder(BaseModel):
1997+
"""Email reminder that subscription will end soon"""
1998+
1999+
subId: str
2000+
behavior_on_trial_end: Literal["cancel", "continue", "read-only"]
19632001

19642002

19652003
# ============================================================================
@@ -3126,14 +3164,7 @@ class PaginatedProfileResponse(PaginatedResponse):
31263164
class PaginatedSubscriptionEventResponse(PaginatedResponse):
31273165
"""Response model for paginated subscription events"""
31283166

3129-
items: List[
3130-
Union[
3131-
SubscriptionCreateOut,
3132-
SubscriptionUpdateOut,
3133-
SubscriptionCancelOut,
3134-
SubscriptionImportOut,
3135-
]
3136-
]
3167+
items: List[SubscriptionEventAnyOut]
31373168

31383169

31393170
# ============================================================================

backend/btrixcloud/orgs.py

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -625,8 +625,6 @@ async def update_quotas(
625625
) -> None:
626626
"""update organization quotas"""
627627

628-
quotas.context = None
629-
630628
previous_extra_mins = (
631629
org.quotas.extraExecMinutes
632630
if (org.quotas and org.quotas.extraExecMinutes)
@@ -1546,7 +1544,6 @@ def init_orgs_api(
15461544
crawl_manager: CrawlManager,
15471545
invites: InviteOps,
15481546
user_dep: Callable[[str], Awaitable[User]],
1549-
superuser_or_shared_secret_dep: Callable[[str], Awaitable[User]],
15501547
):
15511548
"""Init organizations api router for /orgs"""
15521549
# pylint: disable=too-many-locals,invalid-name
@@ -1701,20 +1698,7 @@ async def update_quotas(
17011698
if not user.is_superuser:
17021699
raise HTTPException(status_code=403, detail="Not Allowed")
17031700

1704-
await ops.update_quotas(org, quotas, mode="set", context=quotas.context)
1705-
1706-
return {"updated": True}
1707-
1708-
@app.post(
1709-
"/orgs/{oid}/quotas/add", tags=["organizations"], response_model=UpdatedResponse
1710-
)
1711-
async def update_quotas_add(
1712-
oid: UUID,
1713-
quotas: OrgQuotasIn,
1714-
_user: User = Depends(superuser_or_shared_secret_dep),
1715-
):
1716-
org = await ops.get_org_by_id(oid)
1717-
await ops.update_quotas(org, quotas, mode="add", context=quotas.context)
1701+
await ops.update_quotas(org, quotas, mode="set")
17181702

17191703
return {"updated": True}
17201704

backend/btrixcloud/subs.py

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
Subscription API handling
33
"""
44

5-
from typing import Awaitable, Callable, Union, Any, Optional, Tuple, List
5+
from typing import Awaitable, Callable, Any, Optional, Tuple, List, Annotated
66
import os
77
import asyncio
88
from uuid import UUID
99
from datetime import datetime
1010

11-
from fastapi import APIRouter, Depends, HTTPException, Request
11+
from fastapi import APIRouter, Depends, HTTPException, Request, Query
1212
import aiohttp
1313
from motor.motor_asyncio import AsyncIOMotorDatabase
1414

@@ -23,16 +23,22 @@
2323
SubscriptionImport,
2424
SubscriptionUpdate,
2525
SubscriptionCancel,
26+
SubscriptionAddMinutes,
27+
SubscriptionEventAny,
2628
SubscriptionCreateOut,
2729
SubscriptionImportOut,
2830
SubscriptionUpdateOut,
2931
SubscriptionCancelOut,
32+
SubscriptionAddMinutesOut,
33+
SubscriptionEventAnyOut,
34+
SubscriptionEventType,
3035
Subscription,
3136
SubscriptionPortalUrlRequest,
3237
SubscriptionPortalUrlResponse,
3338
SubscriptionCanceledResponse,
3439
SubscriptionTrialEndReminder,
3540
Organization,
41+
OrgQuotasIn,
3642
InviteToOrgRequest,
3743
InviteAddedResponse,
3844
User,
@@ -228,15 +234,22 @@ async def send_trial_end_reminder(
228234

229235
return SuccessResponse(success=True)
230236

237+
async def add_sub_minutes(self, add_min: SubscriptionAddMinutes):
238+
"""add extra minutes for subscription"""
239+
org = await self.org_ops.get_org_by_id(add_min.oid)
240+
quotas = OrgQuotasIn(extraExecMinutes=add_min.minutes)
241+
await self.org_ops.update_quotas(
242+
org, quotas, mode="add", context=add_min.context
243+
)
244+
245+
await self.add_sub_event("add-minutes", add_min, add_min.oid)
246+
247+
return {"updated": True}
248+
231249
async def add_sub_event(
232250
self,
233-
type_: str,
234-
event: Union[
235-
SubscriptionCreate,
236-
SubscriptionImport,
237-
SubscriptionUpdate,
238-
SubscriptionCancel,
239-
],
251+
type_: SubscriptionEventType,
252+
event: SubscriptionEventAny,
240253
oid: UUID,
241254
) -> None:
242255
"""add a subscription event to the db"""
@@ -246,20 +259,22 @@ async def add_sub_event(
246259
data["oid"] = oid
247260
await self.subs.insert_one(data)
248261

249-
def _get_sub_by_type_from_data(self, data: dict[str, object]) -> Union[
250-
SubscriptionCreateOut,
251-
SubscriptionImportOut,
252-
SubscriptionUpdateOut,
253-
SubscriptionCancelOut,
254-
]:
262+
def _get_sub_by_type_from_data(
263+
self, data: dict[str, object]
264+
) -> SubscriptionEventAnyOut:
255265
"""convert dict to propert background job type"""
256266
if data["type"] == "create":
257267
return SubscriptionCreateOut(**data)
258268
if data["type"] == "import":
259269
return SubscriptionImportOut(**data)
260270
if data["type"] == "update":
261271
return SubscriptionUpdateOut(**data)
262-
return SubscriptionCancelOut(**data)
272+
if data["type"] == "cancel":
273+
return SubscriptionCancelOut(**data)
274+
if data["type"] == "add-minutes":
275+
return SubscriptionAddMinutesOut(**data)
276+
277+
raise HTTPException(status_code=500, detail="unknown sub event")
263278

264279
# pylint: disable=too-many-arguments
265280
async def list_sub_events(
@@ -268,19 +283,13 @@ async def list_sub_events(
268283
sub_id: Optional[str] = None,
269284
oid: Optional[UUID] = None,
270285
plan_id: Optional[str] = None,
286+
type_: Optional[SubscriptionEventType] = None,
271287
page_size: int = DEFAULT_PAGE_SIZE,
272288
page: int = 1,
273289
sort_by: Optional[str] = None,
274290
sort_direction: Optional[int] = -1,
275291
) -> Tuple[
276-
List[
277-
Union[
278-
SubscriptionCreateOut,
279-
SubscriptionImportOut,
280-
SubscriptionUpdateOut,
281-
SubscriptionCancelOut,
282-
]
283-
],
292+
List[SubscriptionEventAnyOut],
284293
int,
285294
]:
286295
"""list subscription events"""
@@ -298,6 +307,8 @@ async def list_sub_events(
298307
query["planId"] = plan_id
299308
if oid:
300309
query["oid"] = oid
310+
if type_:
311+
query["type"] = type_
301312

302313
aggregate = [{"$match": query}]
303314

@@ -515,6 +526,15 @@ async def send_trial_end_reminder(
515526
):
516527
return await ops.send_trial_end_reminder(reminder)
517528

529+
@app.post(
530+
"/subscriptions/add-minutes",
531+
tags=["subscriptions"],
532+
dependencies=[Depends(superuser_or_shared_secret_dep)],
533+
response_model=UpdatedResponse,
534+
)
535+
async def add_sub_minutes(add_min: SubscriptionAddMinutes):
536+
return await ops.add_sub_minutes(add_min)
537+
518538
assert org_ops.router
519539

520540
@app.get(
@@ -540,6 +560,7 @@ async def get_sub_events(
540560
subId: Optional[str] = None,
541561
oid: Optional[UUID] = None,
542562
planId: Optional[str] = None,
563+
type_: Annotated[Optional[SubscriptionEventType], Query(alias="type")] = None,
543564
pageSize: int = DEFAULT_PAGE_SIZE,
544565
page: int = 1,
545566
sortBy: Optional[str] = "timestamp",
@@ -551,6 +572,7 @@ async def get_sub_events(
551572
oid=oid,
552573
plan_id=planId,
553574
page_size=pageSize,
575+
type_=type_,
554576
page=page,
555577
sort_by=sortBy,
556578
sort_direction=sortDirection,

0 commit comments

Comments
 (0)