Skip to content

Commit

Permalink
add get all published bots
Browse files Browse the repository at this point in the history
  • Loading branch information
statefb committed Mar 18, 2024
1 parent 3a44298 commit 27749e1
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 22 deletions.
47 changes: 47 additions & 0 deletions backend/app/repositories/custom_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,3 +670,50 @@ def query_dynamodb(table, bot_id):
)

return bots


def find_all_published_bots(
limit: int = 1000, next_token: str | None = None
) -> tuple[list[BotMetaWithOwnerUserId], str | None]:
"""Find all published bots. This method is intended for administrator use."""
table = _get_table_public_client()

query_params = {
"IndexName": "PublicBotIdIndex",
"FilterExpression": Attr("ApiPublishmentStackName").exists()
& Attr("ApiPublishmentStackName").ne(None),
"Limit": limit,
}
if next_token:
query_params["ExclusiveStartKey"] = json.loads(
base64.b64decode(next_token).decode("utf-8")
)

response = table.scan(**query_params)

bots = [
BotMetaWithOwnerUserId(
id=decompose_bot_id(item["SK"]),
owner_user_id=item["PK"],
title=item["Title"],
create_time=float(item["CreateTime"]),
last_used_time=float(item["LastBotUsed"]),
owned=True,
available=True,
is_pinned=item["IsPinned"],
description=item["Description"],
is_public="PublicBotId" in item,
sync_status=item["SyncStatus"],
published_api_stack_name=item["ApiPublishmentStackName"],
published_api_datetime=item.get("ApiPublishedDatetime", None),
)
for item in response["Items"]
]

next_token = None
if "LastEvaluatedKey" in response:
next_token = base64.b64encode(
json.dumps(response["LastEvaluatedKey"]).encode("utf-8")
).decode("utf-8")

return bots, next_token
32 changes: 31 additions & 1 deletion backend/app/routes/admin.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
from datetime import date

from app.dependencies import check_admin
from app.repositories.custom_bot import find_all_published_bots
from app.repositories.usage_analysis import (
find_bots_sorted_by_price,
find_users_sorted_by_price,
)
from app.routes.schemas.admin import UsagePerBotOutput, UsagePerUserOutput
from app.routes.schemas.admin import (
PublishedBotOutput,
PublishedBotOutputsWithNextToken,
UsagePerBotOutput,
UsagePerUserOutput,
)
from app.user import User
from fastapi import APIRouter, Depends, Request

router = APIRouter(tags=["admin"])


@router.get("/admin/published-bots", response_model=PublishedBotOutputsWithNextToken)
def get_all_published_bots(
next_token: str | None = None,
limit: int = 1000,
admin_check=Depends(check_admin),
):
"""Get all published bots. This is intended to be used by admin."""
bots, next_token = find_all_published_bots(next_token=next_token, limit=limit)

bot_outputs = [
PublishedBotOutput(
id=bot.id,
title=bot.title,
description=bot.description,
published_stack_name=bot.published_api_stack_name,
published_datetime=bot.published_api_datetime,
owner_user_id=bot.owner_user_id,
)
for bot in bots
]

return PublishedBotOutputsWithNextToken(bots=bot_outputs, next_token=next_token)


@router.get("/admin/public-bots", response_model=list[UsagePerBotOutput])
async def get_all_public_bots(
limit: int = 100,
Expand Down
14 changes: 14 additions & 0 deletions backend/app/routes/schemas/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@
from pydantic import Field


class PublishedBotOutput(BaseSchema):
id: str = Field(..., description="bot_id")
title: str
description: str
published_stack_name: str | None
published_datetime: int | None
owner_user_id: str


class PublishedBotOutputsWithNextToken(BaseSchema):
bots: list[PublishedBotOutput]
next_token: str | None


class UsagePerBotOutput(BaseSchema):
id: str = Field(..., description="bot_id")
title: str
Expand Down
24 changes: 23 additions & 1 deletion backend/app/routes/schemas/api_publication.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,41 @@
from typing import Literal, Optional

from app.routes.schemas.base import BaseSchema
from pydantic import Field
from pydantic import Field, root_validator


class PublishedApiQuota(BaseSchema):
limit: Optional[int]
offset: Optional[int]
period: Optional[Literal["DAY", "WEEK", "MONTH"]]

@root_validator(pre=True)
def validate_quota(cls, values):
limit, period = values.get("limit"), values.get("period")
if (limit is None) != (period is None):
raise ValueError("limit and period must both be None or both have values")
if limit is not None and limit <= 0:
raise ValueError("limit must be a positive integer")
return values


class PublishedApiThrottle(BaseSchema):
rate_limit: Optional[float]
burst_limit: Optional[int]

@root_validator(pre=True)
def validate_throttle(cls, values):
rate_limit, burst_limit = values.get("rate_limit"), values.get("burst_limit")
if (rate_limit is None) != (burst_limit is None):
raise ValueError(
"rate_limit and burst_limit must both be None or both have values"
)
if rate_limit is not None and rate_limit <= 0:
raise ValueError("rate_limit must be a positive number")
if burst_limit is not None and burst_limit <= 0:
raise ValueError("burst_limit must be a positive integer")
return values


class BotPublishInput(BaseSchema):
stage: Optional[str] # "dev" | "stg" | "prd" | etc.
Expand Down
12 changes: 12 additions & 0 deletions backend/tests/repositories/test_custom_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@

sys.path.append(".")

from pprint import pprint

from app.repositories.custom_bot import (
delete_alias_by_id,
delete_bot_by_id,
delete_bot_publication,
find_all_bots_by_user_id,
find_all_published_bots,
find_private_bot_by_id,
find_private_bots_by_user_id,
find_public_bots_by_ids,
Expand Down Expand Up @@ -439,6 +442,15 @@ async def test_find_public_bots_by_ids(self):
# 2 public bots and 2 private bots
self.assertEqual(len(bots), 2)

async def test_find_all_published_bots(self):
bots, next_token = find_all_published_bots()
# Bot should not contain unpublished bots
for bot in bots:
self.assertIsNotNone(bot.published_api_stack_name)
self.assertIsNotNone(bot.published_api_datetime)
# Next token should be None
self.assertIsNone(next_token)


class TestUpdateBotVisibility(unittest.TestCase):
def setUp(self) -> None:
Expand Down
39 changes: 23 additions & 16 deletions cdk/bin/api-publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,23 @@ const app = new cdk.App();
const BEDROCK_REGION = app.node.tryGetContext("bedrockRegion");

// Usage plan for the published API
const PUBLISHED_API_THROTTLE_RATE_LIMIT: string = app.node.tryGetContext(
"publishedApiThrottleRateLimit"
);
const PUBLISHED_API_THROTTLE_BURST_LIMIT: string = app.node.tryGetContext(
"publishedApiThrottleBurstLimit"
);
const PUBLISHED_API_QUOTA_LIMIT: string = app.node.tryGetContext(
const PUBLISHED_API_THROTTLE_RATE_LIMIT: number | undefined =
app.node.tryGetContext("publishedApiThrottleRateLimit")
? Number(app.node.tryGetContext("publishedApiThrottleRateLimit"))
: undefined;
const PUBLISHED_API_THROTTLE_BURST_LIMIT: number | undefined =
app.node.tryGetContext("publishedApiThrottleBurstLimit")
? Number(app.node.tryGetContext("publishedApiThrottleBurstLimit"))
: undefined;
const PUBLISHED_API_QUOTA_LIMIT: number | undefined = app.node.tryGetContext(
"publishedApiQuotaLimit"
);
const PUBLISHED_API_QUOTA_PERIOD: "DAY" | "WEEK" | "MONTH" =
app.node.tryGetContext("publishedApiQuotaPeriod");
)
? Number(app.node.tryGetContext("publishedApiQuotaLimit"))
: undefined;
const PUBLISHED_API_QUOTA_PERIOD: "DAY" | "WEEK" | "MONTH" | undefined =
app.node.tryGetContext("publishedApiQuotaPeriod")
? app.node.tryGetContext("publishedApiQuotaPeriod")
: undefined;
const PUBLISHED_API_DEPLOYMENT_STAGE = app.node.tryGetContext(
"publishedApiDeploymentStage"
);
Expand Down Expand Up @@ -71,8 +77,7 @@ const dbConfigHostname = cdk.Fn.importValue(
const dbConfigPort = cdk.Token.asNumber(
cdk.Fn.importValue("BedrockClaudeChatDbConfigPort")
);
// TODO: remove
console.log(dbConfigPort);

const dbConfigSecretArn = cdk.Fn.importValue(
"BedrockClaudeChatDbConfigSecretArn"
);
Expand Down Expand Up @@ -103,12 +108,14 @@ const publishedApi = new ApiPublishmentStack(
largeMessageBucketName: largeMessageBucketName,
usagePlan: {
throttle: {
rateLimit: Number(PUBLISHED_API_THROTTLE_RATE_LIMIT),
burstLimit: Number(PUBLISHED_API_THROTTLE_BURST_LIMIT),
rateLimit: PUBLISHED_API_THROTTLE_RATE_LIMIT,
burstLimit: PUBLISHED_API_THROTTLE_BURST_LIMIT,
},
quota: {
limit: Number(PUBLISHED_API_QUOTA_LIMIT),
period: apigateway.Period[PUBLISHED_API_QUOTA_PERIOD],
limit: PUBLISHED_API_QUOTA_LIMIT,
period: PUBLISHED_API_QUOTA_PERIOD
? apigateway.Period[PUBLISHED_API_QUOTA_PERIOD]
: undefined,
},
},
deploymentStage: PUBLISHED_API_DEPLOYMENT_STAGE,
Expand Down
8 changes: 4 additions & 4 deletions cdk/lib/constructs/api-publish-codebuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ export class ApiPublishCodebuild extends Construct {
},
environmentVariables: {
// Need to be overridden when invoke the project
PUBLISHED_API_THROTTLE_RATE_LIMIT: { value: "100" },
PUBLISHED_API_THROTTLE_BURST_LIMIT: { value: "100" },
PUBLISHED_API_QUOTA_LIMIT: { value: "1000" },
PUBLISHED_API_QUOTA_PERIOD: { value: "DAY" },
// PUBLISHED_API_THROTTLE_RATE_LIMIT: { value: undefined },
// PUBLISHED_API_THROTTLE_BURST_LIMIT: { value: undefined },
// PUBLISHED_API_QUOTA_LIMIT: { value: undefined },
// PUBLISHED_API_QUOTA_PERIOD: { value: undefined },
PUBLISHED_API_DEPLOYMENT_STAGE: { value: "api" },
PUBLISHED_API_ID: { value: "xy1234" },
PUBLISHED_API_ALLOWED_ORIGINS: { value: '["*"]' },
Expand Down

0 comments on commit 27749e1

Please sign in to comment.