feat(bot): add /dashboard command and MINI_APP_URL config

- Add /dashboard command to BotFather registration alongside /app
- Add new MINI_APP_URL env var for dashboard URL (separate from CORS origins)
- Pass MINI_APP_URL and BOT_API_URL in CD workflow
- Update Terraform with new variable for future infrastructure deployments
This commit is contained in:
2026-03-17 02:17:11 +04:00
parent 58cf69b9b6
commit fcdd0f2aaf
11 changed files with 42 additions and 7 deletions

View File

@@ -13,6 +13,9 @@ TELEGRAM_WEBHOOK_SECRET=your-webhook-secret
TELEGRAM_WEBHOOK_PATH=/webhook/telegram TELEGRAM_WEBHOOK_PATH=/webhook/telegram
# Mini app # Mini app
# URL for /app and /dashboard commands (e.g., https://your-app.cloud.run)
MINI_APP_URL=https://your-miniapp-domain.com
# CORS origins for mini app API (comma-separated)
MINI_APP_ALLOWED_ORIGINS=http://localhost:5173 MINI_APP_ALLOWED_ORIGINS=http://localhost:5173
# Parsing / AI # Parsing / AI

View File

@@ -234,15 +234,18 @@ jobs:
} >> "$GITHUB_OUTPUT" } >> "$GITHUB_OUTPUT"
- name: Deploy bot service - name: Deploy bot service
id: bot-api
run: | run: |
gcloud run deploy "household-${SERVICE_SUFFIX}-bot-api" \ gcloud run deploy "household-${SERVICE_SUFFIX}-bot-api" \
--image "${{ steps.images.outputs.bot_image }}" \ --image "${{ steps.images.outputs.bot_image }}" \
--region "${GCP_REGION}" \ --region "${GCP_REGION}" \
--project "${{ vars.GCP_PROJECT_ID }}" \ --project "${{ vars.GCP_PROJECT_ID }}" \
--set-env-vars "DB_SCHEMA=${DB_SCHEMA}" \ --set-env-vars "DB_SCHEMA=${DB_SCHEMA},MINI_APP_URL=${{ vars.MINI_APP_URL }}" \
--allow-unauthenticated \ --allow-unauthenticated \
--quiet --quiet
echo "url=$(gcloud run services describe "household-${SERVICE_SUFFIX}-bot-api" --region "${GCP_REGION}" --project "${{ vars.GCP_PROJECT_ID }}" --format 'value(status.url)')" >> "$GITHUB_OUTPUT"
- name: Route traffic to latest bot revision - name: Route traffic to latest bot revision
run: | run: |
gcloud run services update-traffic "household-${SERVICE_SUFFIX}-bot-api" \ gcloud run services update-traffic "household-${SERVICE_SUFFIX}-bot-api" \
@@ -257,6 +260,7 @@ jobs:
--image "${{ steps.images.outputs.mini_image }}" \ --image "${{ steps.images.outputs.mini_image }}" \
--region "${GCP_REGION}" \ --region "${GCP_REGION}" \
--project "${{ vars.GCP_PROJECT_ID }}" \ --project "${{ vars.GCP_PROJECT_ID }}" \
--set-env-vars "BOT_API_URL=${{ steps.bot-api.outputs.url }}" \
--allow-unauthenticated \ --allow-unauthenticated \
--quiet --quiet

View File

@@ -25,6 +25,7 @@ export interface BotRuntimeConfig {
assistantRateLimitBurstWindowMs: number assistantRateLimitBurstWindowMs: number
assistantRateLimitRolling: number assistantRateLimitRolling: number
assistantRateLimitRollingWindowMs: number assistantRateLimitRollingWindowMs: number
miniAppUrl?: string
} }
function parsePort(raw: string | undefined): number { function parsePort(raw: string | undefined): number {
@@ -103,6 +104,7 @@ export function getBotRuntimeConfig(env: NodeJS.ProcessEnv = process.env): BotRu
const schedulerSharedSecret = parseOptionalValue(env.SCHEDULER_SHARED_SECRET) const schedulerSharedSecret = parseOptionalValue(env.SCHEDULER_SHARED_SECRET)
const schedulerOidcAllowedEmails = parseOptionalCsv(env.SCHEDULER_OIDC_ALLOWED_EMAILS) const schedulerOidcAllowedEmails = parseOptionalCsv(env.SCHEDULER_OIDC_ALLOWED_EMAILS)
const miniAppAllowedOrigins = parseOptionalCsv(env.MINI_APP_ALLOWED_ORIGINS) const miniAppAllowedOrigins = parseOptionalCsv(env.MINI_APP_ALLOWED_ORIGINS)
const miniAppUrl = parseOptionalValue(env.MINI_APP_URL)
const purchaseTopicIngestionEnabled = databaseUrl !== undefined const purchaseTopicIngestionEnabled = databaseUrl !== undefined
@@ -174,6 +176,9 @@ export function getBotRuntimeConfig(env: NodeJS.ProcessEnv = process.env): BotRu
if (schedulerSharedSecret !== undefined) { if (schedulerSharedSecret !== undefined) {
runtime.schedulerSharedSecret = schedulerSharedSecret runtime.schedulerSharedSecret = schedulerSharedSecret
} }
if (miniAppUrl !== undefined) {
runtime.miniAppUrl = miniAppUrl
}
const openaiApiKey = parseOptionalValue(env.OPENAI_API_KEY) const openaiApiKey = parseOptionalValue(env.OPENAI_API_KEY)
if (openaiApiKey !== undefined) { if (openaiApiKey !== undefined) {
runtime.openaiApiKey = openaiApiKey runtime.openaiApiKey = openaiApiKey

View File

@@ -15,6 +15,7 @@ export const enBotTranslations: BotTranslationCatalog = {
pending_members: 'List pending household join requests', pending_members: 'List pending household join requests',
approve_member: 'Approve a pending household member', approve_member: 'Approve a pending household member',
app: 'Open the Kojori mini app', app: 'Open the Kojori mini app',
dashboard: 'Open the household dashboard',
keyboard: 'Toggle persistent dashboard button' keyboard: 'Toggle persistent dashboard button'
}, },
help: { help: {

View File

@@ -15,6 +15,7 @@ export const ruBotTranslations: BotTranslationCatalog = {
pending_members: 'Показать ожидающие заявки на вступление', pending_members: 'Показать ожидающие заявки на вступление',
approve_member: 'Подтвердить участника дома', approve_member: 'Подтвердить участника дома',
app: 'Открыть мини-приложение Kojori', app: 'Открыть мини-приложение Kojori',
dashboard: 'Открыть дашборд дома',
keyboard: 'Вкл/выкл кнопку дашборда' keyboard: 'Вкл/выкл кнопку дашборда'
}, },
help: { help: {

View File

@@ -13,6 +13,7 @@ export type TelegramCommandName =
| 'pending_members' | 'pending_members'
| 'approve_member' | 'approve_member'
| 'app' | 'app'
| 'dashboard'
| 'keyboard' | 'keyboard'
export interface BotCommandDescriptions { export interface BotCommandDescriptions {
@@ -28,6 +29,7 @@ export interface BotCommandDescriptions {
pending_members: string pending_members: string
approve_member: string approve_member: string
app: string app: string
dashboard: string
keyboard: string keyboard: string
} }

View File

@@ -313,7 +313,13 @@ if (purchaseRepositoryClient && householdConfigurationRepositoryClient) {
if (runtime.financeCommandsEnabled) { if (runtime.financeCommandsEnabled) {
const financeCommands = createFinanceCommandsService({ const financeCommands = createFinanceCommandsService({
householdConfigurationRepository: householdConfigurationRepositoryClient!.repository, householdConfigurationRepository: householdConfigurationRepositoryClient!.repository,
financeServiceForHousehold financeServiceForHousehold,
...(runtime.miniAppUrl
? {
miniAppUrl: runtime.miniAppUrl,
botUsername: bot.botInfo?.username
}
: {})
}) })
financeCommands.register(bot) financeCommands.register(bot)
@@ -343,9 +349,9 @@ if (householdConfigurationRepositoryClient) {
promptRepository: telegramPendingActionRepositoryClient.repository promptRepository: telegramPendingActionRepositoryClient.repository
} }
: {}), : {}),
...(runtime.miniAppAllowedOrigins[0] ...(runtime.miniAppUrl
? { ? {
miniAppUrl: runtime.miniAppAllowedOrigins[0] miniAppUrl: runtime.miniAppUrl
} }
: {}), : {}),
logger: getLogger('household-setup') logger: getLogger('household-setup')
@@ -399,9 +405,9 @@ const reminderJobs = runtime.reminderJobsEnabled
}) })
}, },
reminderService, reminderService,
...(runtime.miniAppAllowedOrigins[0] ...(runtime.miniAppUrl
? { ? {
miniAppUrl: runtime.miniAppAllowedOrigins[0] miniAppUrl: runtime.miniAppUrl
} }
: {}), : {}),
...(bot.botInfo?.username ...(bot.botInfo?.username

View File

@@ -24,7 +24,9 @@ const DEFAULT_COMMAND_NAMES = [
const PRIVATE_CHAT_COMMAND_NAMES = [ const PRIVATE_CHAT_COMMAND_NAMES = [
...DEFAULT_COMMAND_NAMES, ...DEFAULT_COMMAND_NAMES,
'anon', 'anon',
'cancel' 'cancel',
'app',
'dashboard'
] as const satisfies readonly TelegramCommandName[] ] as const satisfies readonly TelegramCommandName[]
const GROUP_CHAT_COMMAND_NAMES = DEFAULT_COMMAND_NAMES const GROUP_CHAT_COMMAND_NAMES = DEFAULT_COMMAND_NAMES
const GROUP_MEMBER_COMMAND_NAMES = [ const GROUP_MEMBER_COMMAND_NAMES = [

View File

@@ -166,6 +166,9 @@ module "bot_api_service" {
length(var.bot_mini_app_allowed_origins) == 0 ? {} : { length(var.bot_mini_app_allowed_origins) == 0 ? {} : {
MINI_APP_ALLOWED_ORIGINS = join(",", var.bot_mini_app_allowed_origins) MINI_APP_ALLOWED_ORIGINS = join(",", var.bot_mini_app_allowed_origins)
}, },
var.bot_mini_app_url == null ? {} : {
MINI_APP_URL = var.bot_mini_app_url
},
{ {
SCHEDULER_OIDC_ALLOWED_EMAILS = google_service_account.scheduler_invoker.email SCHEDULER_OIDC_ALLOWED_EMAILS = google_service_account.scheduler_invoker.email
} }

View File

@@ -20,6 +20,7 @@ bot_assistant_rate_limit_burst = 5
bot_assistant_rate_limit_burst_window_ms = 60000 bot_assistant_rate_limit_burst_window_ms = 60000
bot_assistant_rate_limit_rolling = 50 bot_assistant_rate_limit_rolling = 50
bot_assistant_rate_limit_rolling_window_ms = 86400000 bot_assistant_rate_limit_rolling_window_ms = 86400000
bot_mini_app_url = "https://household-dev-mini-app-abc123-ew.a.run.app"
bot_mini_app_allowed_origins = [ bot_mini_app_allowed_origins = [
"https://household-dev-mini-app-abc123-ew.a.run.app" "https://household-dev-mini-app-abc123-ew.a.run.app"
] ]

View File

@@ -139,6 +139,13 @@ variable "bot_assistant_rate_limit_rolling_window_ms" {
nullable = true nullable = true
} }
variable "bot_mini_app_url" {
description = "Optional URL for /app and /dashboard bot commands"
type = string
default = null
nullable = true
}
variable "bot_mini_app_allowed_origins" { variable "bot_mini_app_allowed_origins" {
description = "Optional allow-list of mini app origins for bot CORS handling" description = "Optional allow-list of mini app origins for bot CORS handling"
type = list(string) type = list(string)