Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
57270cf
allow updating org quotas with shared secret as well as active user dep
emma-sg Oct 28, 2025
4d6e98e
add checkout endpoint for addon minutes
emma-sg Oct 28, 2025
34d60ce
set up additional minute checkout interface when subscription is
emma-sg Oct 29, 2025
9d3c724
add endpoint for additional minute pricing
emma-sg Oct 30, 2025
552dc9a
add price fetching in FE & fix linting etc in backend changes
emma-sg Oct 30, 2025
c0de8df
fix additional price usage
emma-sg Oct 30, 2025
a72c5f1
fix autofetching price
emma-sg Oct 30, 2025
deaf034
fix various bugs and issues with cashew <-> btrix api calls
emma-sg Oct 30, 2025
b6dff1b
allow shared secret when determining org for quotas update endpoint
emma-sg Nov 3, 2025
43d26b9
add 100 min preset & note about minutes being adjustable during checkout
emma-sg Nov 3, 2025
076725b
update orgs router to allow shared secret when determining org
emma-sg Nov 4, 2025
2de76f2
show extra and gifted minutes in billing section
emma-sg Nov 4, 2025
60fa60c
update error log
emma-sg Nov 4, 2025
0e88912
allow only quotas update endpoint to use shared secret, rather than
emma-sg Nov 5, 2025
fac929a
rename to `shared_secret_or_superuser` in `users.py`
emma-sg Nov 5, 2025
c2feb9e
add optional "context" field to quota update log to store payment info
emma-sg Nov 5, 2025
9159c14
rework input to accept context alongside quota updates in a single
emma-sg Nov 5, 2025
7f047e4
add new /add quotas endpoint & restore original set quotas endpoint
emma-sg Nov 5, 2025
9f89fc1
make quota_updates update atomic in /quotas endpoint
emma-sg Nov 5, 2025
30c7a80
merge quota set & add methods into one quota update method
emma-sg Nov 10, 2025
7313293
add `subscription_change` context to quota update when changing plan
emma-sg Nov 10, 2025
5dbe10b
fix type issue in auth.py
emma-sg Nov 10, 2025
019e02a
revert type changes causing build failure
emma-sg Nov 12, 2025
3fbe0ea
add test for adding quotas
emma-sg Nov 12, 2025
00faca0
fix ci subs tests
emma-sg Nov 17, 2025
f4ac02b
set up `btrix-subs-app-secret` in nightly test environment
emma-sg Nov 17, 2025
33eda14
remove accidentally-copied microk8s prefix
emma-sg Nov 17, 2025
2541413
remove line breaks & fix env key
emma-sg Nov 17, 2025
acabbcc
include reset back to prev value in "add to quota" test
emma-sg Nov 17, 2025
1f0774a
remove unnecessary failing assertion that checked *used* execution
emma-sg Nov 17, 2025
2f565a3
move "add quota" test to end so it doesn't impact available execution
emma-sg Nov 17, 2025
24cd543
update quota add endpoint to fetch org directly and rely on existing
emma-sg Dec 4, 2025
b839dd1
update orgs router to allow shared secret when determining org
emma-sg Dec 3, 2025
ff9f774
wip meters updates
emma-sg Nov 24, 2025
c0230e1
add options & get floating popover working
emma-sg Nov 24, 2025
6f2a85e
fix inconsistency with execution time in usage history table
emma-sg Nov 24, 2025
8b3653c
wip
emma-sg Nov 25, 2025
92672a6
reimplement execution minute meter from scratch
emma-sg Nov 26, 2025
03aca23
clean up floating popover
emma-sg Nov 26, 2025
7db8a7e
update floating-ui dep
emma-sg Nov 27, 2025
92b78e5
move tick to the right of value bar when value is 0
emma-sg Dec 1, 2025
0f5d653
move relevant styles to meter bar host element
emma-sg Dec 1, 2025
9899e14
return "0%" when value is actually 0
emma-sg Dec 1, 2025
0eb25e2
fix NaN issue
emma-sg Dec 1, 2025
eede531
(wip) redo execution minute meter layout & popovers
emma-sg Dec 1, 2025
d22fcee
[wip] disable virtual element with reference to real element
emma-sg Dec 1, 2025
790d675
refactor meter implementations to shared structure
emma-sg Dec 2, 2025
12476ee
clean up execution time formatter & skip outdated tests
emma-sg Dec 2, 2025
6b2bad6
fix rendering of usage history fields displaying partial minutes
emma-sg Dec 2, 2025
033021e
fix remaining renames from execution second formatter cleanup
emma-sg Dec 2, 2025
6428fcb
remove unused virtual element code and clean up floating popover code
emma-sg Dec 2, 2025
5b04794
unify tooltip content for execution minutes meter
emma-sg Dec 2, 2025
53407f9
revert & simplify execution minute formatter changes
emma-sg Dec 2, 2025
07b7be9
update playwright version (was causing ci issues)
emma-sg Dec 2, 2025
58f64f9
update playwright test dep as well
emma-sg Dec 2, 2025
cd5d9a8
wip 1
emma-sg Dec 2, 2025
8df3b9e
reset package.json, yarn.lock, and web-test-runner config to contents
emma-sg Dec 3, 2025
6a334b2
redo storage tooltips to match tooltips for exec minutes
emma-sg Dec 3, 2025
541ae62
fix overlap issue & update meter bar hover styling
emma-sg Dec 3, 2025
857747a
fix misaligned floating popover when locked to an axis of an element
emma-sg Dec 3, 2025
9ccb55d
fix accidentally-changed org router dep
emma-sg Dec 3, 2025
d1a9903
improve billing/usage skeletons
emma-sg Dec 3, 2025
1080570
fix tooltip content edge cases
emma-sg Dec 3, 2025
f10768c
fix tooltip classes not being wrapped in `tw` helper
emma-sg Dec 3, 2025
38f8699
tweak monthly unused colour to match empty meter background
emma-sg Dec 3, 2025
d7f501b
remove unneeded empty strings
emma-sg Dec 3, 2025
7bccdc4
update quota logic to show meter/quota when any of the three exec min
emma-sg Dec 4, 2025
7026c10
fix org quota check & billing info
emma-sg Dec 4, 2025
711ff6e
add org status message for when subscription is available to prompt t…
emma-sg Dec 5, 2025
a8c1aab
add events for minutes purchased:
ikreymer Dec 6, 2025
f1adc07
remove context from OrgQuotasUpdate/In for now, only use with Subscri…
ikreymer Dec 6, 2025
0aeb803
tests: remove context from quotas in tests
ikreymer Dec 7, 2025
6afb5a5
readd 'context' to OrgQuotaUpdate
ikreymer Dec 7, 2025
a846fdd
tests: add add-minutes event test
ikreymer Dec 7, 2025
4e25b22
debug test
ikreymer Dec 7, 2025
c4a4e39
tests: change org
ikreymer Dec 7, 2025
9913986
org sub events: add 'type' filter, add type for possible subscription…
ikreymer Dec 7, 2025
1119a6a
additional cleanup / type fixes, consolidate Union types
ikreymer Dec 7, 2025
950dc0e
test: increase backend memory?
ikreymer Dec 7, 2025
5955ccf
update tests, remove nightly test for removed endpoint
ikreymer Dec 7, 2025
6a36264
ci: microk8s changes
ikreymer Dec 8, 2025
5057239
ci: microk8s add cleanup action
ikreymer Dec 8, 2025
9630d0a
ci: microk8s use old memory
ikreymer Dec 8, 2025
43bb583
additional minutes data model changes: (#3058)
ikreymer Dec 8, 2025
9f5d496
raise error when checkout url can't be created
emma-sg Dec 9, 2025
37eaee8
reformat
ikreymer Dec 10, 2025
8345156
Use dedicated mongo session in quota update method (#3057)
emma-sg Dec 10, 2025
e3911fd
fix rounding & display seconds when values are less than a minute
emma-sg Dec 10, 2025
b0123ab
update org status banner
emma-sg Dec 10, 2025
a9b7c36
update return type for minute price endpoint
emma-sg Dec 10, 2025
0cfc731
ci: undo microk8s mem change
ikreymer Dec 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/k3d-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ jobs:
version: 3.10.2

- name: Create secret
run: kubectl create secret generic btrix-subs-app-secret --from-literal=BTRIX_SUBS_APP_URL=${{ env.ECHO_SERVER_HOST_URL }}/portalUrl
run: kubectl create secret generic btrix-subs-app-secret --from-literal=BTRIX_SUBS_APP_URL=${{ env.ECHO_SERVER_HOST_URL }}

- name: Start Cluster with Helm
run: |
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/k3d-nightly-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ jobs:
with:
version: 3.10.2

- name: Create Secret
run: >
kubectl create secret generic btrix-subs-app-secret
--from-literal=BTRIX_SUBS_APP_URL=${{ env.ECHO_SERVER_HOST_URL }}
--from-literal=BTRIX_SUBS_APP_API_KEY=TEST_PRESHARED_SECRET_PASSWORD

- name: Start Cluster with Helm
run: |
helm upgrade --install -f ./chart/values.yaml -f ./chart/test/test.yaml -f ./chart/test/test-nightly-addons.yaml btrix ./chart/
Expand Down
8 changes: 7 additions & 1 deletion .github/workflows/microk8s-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ jobs:
btrix-microk8s-test:
runs-on: ubuntu-latest
steps:
- name: Initial Disk Cleanup
uses: mathio/gha-cleanup@v1
with:
remove-browsers: true
verbose: true

- uses: balchua/[email protected]
with:
channel: "1.25/stable"
Expand Down Expand Up @@ -60,7 +66,7 @@ jobs:
cache-to: type=gha,scope=frontend,mode=max

- name: Create Secret
run: sudo microk8s kubectl create secret generic btrix-subs-app-secret --from-literal=BTRIX_SUBS_APP_URL=${{ env.ECHO_SERVER_HOST_URL }}/portalUrl
run: sudo microk8s kubectl create secret generic btrix-subs-app-secret --from-literal=BTRIX_SUBS_APP_URL=${{ env.ECHO_SERVER_HOST_URL }}

- name: Start Cluster with Helm
run: |
Expand Down
8 changes: 5 additions & 3 deletions backend/btrixcloud/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ class OA2BearerOrQuery(OAuth2PasswordBearer):
"""Override bearer check to also test query"""

async def __call__(
self, request: Request = None, websocket: WebSocket = None # type: ignore
self,
request: Request = None, # type: ignore
websocket: WebSocket = None, # type: ignore
) -> str:
param = None
exc = None
Expand Down Expand Up @@ -163,7 +165,7 @@ async def get_current_user(
headers={"WWW-Authenticate": "Bearer"},
)

async def shared_secret_or_active_user(
async def shared_secret_or_superuser(
token: str = Depends(oauth2_scheme),
) -> User:
# allow superadmin access if token matches the known shared secret
Expand Down Expand Up @@ -257,4 +259,4 @@ async def refresh_jwt(user=Depends(current_active_user)):
user_info = await user_manager.get_user_info_with_orgs(user)
return get_bearer_response(user, user_info)

return auth_jwt_router, current_active_user, shared_secret_or_active_user
return auth_jwt_router, current_active_user, shared_secret_or_superuser
10 changes: 8 additions & 2 deletions backend/btrixcloud/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@

import importlib.util
import os
import urllib
import urllib.parse
import asyncio
from uuid import UUID, uuid4

from typing import Optional, Union, TypeVar, Type, TYPE_CHECKING
from typing import (
Optional,
Type,
TypeVar,
Union,
TYPE_CHECKING,
)

from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
from pydantic import BaseModel
Expand Down
7 changes: 3 additions & 4 deletions backend/btrixcloud/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,20 +172,19 @@ def main() -> None:

user_manager = init_user_manager(mdb, email, invites)

current_active_user, shared_secret_or_active_user = init_users_api(
app, user_manager
)
current_active_user, shared_secret_or_superuser = init_users_api(app, user_manager)

org_ops = init_orgs_api(
app,
dbclient,
mdb,
user_manager,
crawl_manager,
invites,
current_active_user,
)

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

event_webhook_ops = init_event_webhooks_api(mdb, org_ops, app_root)

Expand Down
97 changes: 81 additions & 16 deletions backend/btrixcloud/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1854,6 +1854,8 @@ class S3Storage(BaseModel):
REASON_PAUSED = "subscriptionPaused"
REASON_CANCELED = "subscriptionCanceled"

SubscriptionEventType = Literal["create", "import", "update", "cancel", "add-minutes"]


# ============================================================================
class OrgQuotas(BaseModel):
Expand Down Expand Up @@ -1906,6 +1908,7 @@ class SubscriptionEventOut(BaseModel):

oid: UUID
timestamp: datetime
type: SubscriptionEventType


# ============================================================================
Expand Down Expand Up @@ -1971,18 +1974,54 @@ class SubscriptionCancel(BaseModel):


# ============================================================================
class SubscriptionTrialEndReminder(BaseModel):
"""Email reminder that subscription will end soon"""
class SubscriptionCancelOut(SubscriptionCancel, SubscriptionEventOut):
"""Output model for subscription cancellation event"""

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


# ============================================================================
class SubscriptionCancelOut(SubscriptionCancel, SubscriptionEventOut):
"""Output model for subscription cancellation event"""
class SubscriptionAddMinutes(BaseModel):
"""Represents a purchase of additional minutes"""

oid: UUID
minutes: int
totalPrice: float
currency: str
paymentId: str

type: Literal["cancel"] = "cancel"

# ============================================================================
class SubscriptionAddMinutesOut(SubscriptionAddMinutes, SubscriptionEventOut):
"""SubscriptionAddMinutes output model"""

type: Literal["add-minutes"] = "add-minutes"


# ============================================================================
SubscriptionEventAny = Union[
SubscriptionCreate,
SubscriptionUpdate,
SubscriptionCancel,
SubscriptionImport,
SubscriptionAddMinutes,
]

SubscriptionEventAnyOut = Union[
SubscriptionCreateOut,
SubscriptionUpdateOut,
SubscriptionCancelOut,
SubscriptionImportOut,
SubscriptionAddMinutesOut,
]


# ============================================================================
class SubscriptionTrialEndReminder(BaseModel):
"""Email reminder that subscription will end soon"""

subId: str
behavior_on_trial_end: Literal["cancel", "continue", "read-only"]


# ============================================================================
Expand All @@ -2005,6 +2044,30 @@ class SubscriptionPortalUrlResponse(BaseModel):
portalUrl: str = ""


# ============================================================================
class AddonMinutesPricing(BaseModel):
"""Addon minutes pricing"""

value: float
currency: str


# ============================================================================
class CheckoutAddonMinutesRequest(BaseModel):
"""Request for additional minutes checkout session"""

orgId: str
subId: str
minutes: int | None = None
return_url: str


class CheckoutAddonMinutesResponse(BaseModel):
"""Response for additional minutes checkout session"""

checkoutUrl: str


# ============================================================================
class Subscription(BaseModel):
"""subscription data"""
Expand Down Expand Up @@ -2083,6 +2146,15 @@ class OrgQuotaUpdate(BaseModel):

modified: datetime
update: OrgQuotas
subEventId: str | None = None


# ============================================================================
class OrgQuotaUpdateOut(BaseModel):
"""Organization quota update output for admins"""

modified: datetime
update: OrgQuotas


# ============================================================================
Expand Down Expand Up @@ -2159,7 +2231,7 @@ class OrgOut(BaseMongoModel):
giftedExecSecondsAvailable: int = 0

quotas: OrgQuotas = OrgQuotas()
quotaUpdates: Optional[List[OrgQuotaUpdate]] = []
quotaUpdates: Optional[List[OrgQuotaUpdateOut]] = []

webhookUrls: Optional[OrgWebhookUrls] = OrgWebhookUrls()

Expand Down Expand Up @@ -3124,14 +3196,7 @@ class PaginatedProfileResponse(PaginatedResponse):
class PaginatedSubscriptionEventResponse(PaginatedResponse):
"""Response model for paginated subscription events"""

items: List[
Union[
SubscriptionCreateOut,
SubscriptionUpdateOut,
SubscriptionCancelOut,
SubscriptionImportOut,
]
]
items: List[SubscriptionEventAnyOut]


# ============================================================================
Expand Down
2 changes: 1 addition & 1 deletion backend/btrixcloud/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def init_ops() -> Tuple[

user_manager = UserManager(mdb, email, invite_ops)

org_ops = OrgOps(mdb, invite_ops, user_manager, crawl_manager)
org_ops = OrgOps(dbclient, mdb, invite_ops, user_manager, crawl_manager)

event_webhook_ops = EventWebhookOps(mdb, org_ops)

Expand Down
Loading
Loading