import { webhookCallback } from 'grammy' import { createAdHocNotificationService, createAnonymousFeedbackService, createFinanceCommandService, createHouseholdAdminService, createHouseholdOnboardingService, createHouseholdSetupService, createLocalePreferenceService, createMiniAppAdminService, createPaymentConfirmationService, createScheduledDispatchService } from '@household/application' import { createDbAdHocNotificationRepository, createDbAnonymousFeedbackRepository, createDbFinanceRepository, createDbHouseholdConfigurationRepository, createDbProcessedBotMessageRepository, createDbScheduledDispatchRepository, createDbTelegramPendingActionRepository, createDbTopicMessageHistoryRepository } from '@household/adapters-db' import { configureLogger, getLogger } from '@household/observability' import { registerAdHocNotifications } from './ad-hoc-notifications' import { registerAnonymousFeedback } from './anonymous-feedback' import { createInMemoryAssistantConversationMemoryStore, createInMemoryAssistantRateLimiter, createInMemoryAssistantUsageTracker, registerDmAssistant } from './dm-assistant' import { createFinanceCommandsService } from './finance-commands' import { createTelegramBot } from './bot' import { getBotRuntimeConfig, type BotRuntimeConfig } from './config' import { registerHouseholdSetupCommands } from './household-setup' import { HouseholdContextCache } from './household-context-cache' import { createAwsScheduledDispatchScheduler } from './aws-scheduled-dispatch-scheduler' import { createGcpScheduledDispatchScheduler } from './gcp-scheduled-dispatch-scheduler' import { createSelfHostedScheduledDispatchScheduler } from './self-hosted-scheduled-dispatch-scheduler' import { createMiniAppAuthHandler, createMiniAppJoinHandler } from './miniapp-auth' import { createMiniAppApproveMemberHandler, createMiniAppPendingMembersHandler, createMiniAppPromoteMemberHandler, createMiniAppDemoteMemberHandler, createMiniAppRejectMemberHandler, createMiniAppSettingsHandler, createMiniAppUpdateMemberAbsencePolicyHandler, createMiniAppUpdateMemberDisplayNameHandler, createMiniAppUpdateMemberRentWeightHandler, createMiniAppUpdateMemberStatusHandler, createMiniAppUpdateOwnDisplayNameHandler, createMiniAppUpdateSettingsHandler, createMiniAppUpsertUtilityCategoryHandler } from './miniapp-admin' import { createMiniAppDashboardHandler } from './miniapp-dashboard' import { createMiniAppAddPaymentHandler, createMiniAppAddPurchaseHandler, createMiniAppAddUtilityBillHandler, createMiniAppBillingCycleHandler, createMiniAppCloseCycleHandler, createMiniAppDeletePaymentHandler, createMiniAppDeletePurchaseHandler, createMiniAppDeleteUtilityBillHandler, createMiniAppOpenCycleHandler, createMiniAppRentUpdateHandler, createMiniAppSubmitUtilityBillHandler, createMiniAppUpdatePaymentHandler, createMiniAppUpdatePurchaseHandler, createMiniAppUpdateUtilityBillHandler } from './miniapp-billing' import { createMiniAppLocalePreferenceHandler } from './miniapp-locale' import { createMiniAppCancelNotificationHandler, createMiniAppUpdateNotificationHandler } from './miniapp-notifications' import { createNbgExchangeRateProvider } from './nbg-exchange-rates' import { createOpenAiChatAssistant } from './openai-chat-assistant' import { createOpenAiAdHocNotificationInterpreter } from './openai-ad-hoc-notification-interpreter' import { createOpenAiPurchaseInterpreter } from './openai-purchase-interpreter' import { createPurchaseMessageRepository, registerConfiguredPurchaseTopicIngestion } from './purchase-topic-ingestion' import { registerConfiguredPaymentTopicIngestion } from './payment-topic-ingestion' import { registerReminderTopicUtilities } from './reminder-topic-utilities' import { createSchedulerRequestAuthorizer } from './scheduler-auth' import { createScheduledDispatchHandler } from './scheduled-dispatch-handler' import { createBotWebhookServer } from './server' import { createTopicProcessor } from './topic-processor' export interface BotRuntimeApp { readonly fetch: (request: Request) => Promise readonly shutdown: () => Promise readonly runtime: BotRuntimeConfig } export async function createBotRuntimeApp(): Promise { const runtime = getBotRuntimeConfig() configureLogger({ level: runtime.logLevel, service: '@household/bot' }) const logger = getLogger('runtime') const shutdownTasks: Array<() => Promise> = [] const householdConfigurationRepositoryClient = runtime.databaseUrl ? createDbHouseholdConfigurationRepository(runtime.databaseUrl) : null const bot = createTelegramBot( runtime.telegramBotToken, getLogger('telegram'), householdConfigurationRepositoryClient?.repository ) bot.botInfo = await bot.api.getMe() const webhookHandler = webhookCallback(bot, 'std/http', { onTimeout: 'return' }) const financeRepositoryClients = new Map>() const financeServices = new Map>() const paymentConfirmationServices = new Map< string, ReturnType >() const exchangeRateProvider = createNbgExchangeRateProvider({ logger: getLogger('fx') }) const householdOnboardingService = householdConfigurationRepositoryClient ? createHouseholdOnboardingService({ repository: householdConfigurationRepositoryClient.repository }) : null const scheduledDispatchRepositoryClient = runtime.databaseUrl && runtime.scheduledDispatch ? createDbScheduledDispatchRepository(runtime.databaseUrl) : null const scheduledDispatchScheduler = runtime.scheduledDispatch && (runtime.scheduledDispatch.provider === 'self-hosted' || runtime.schedulerSharedSecret) ? runtime.scheduledDispatch.provider === 'gcp-cloud-tasks' ? createGcpScheduledDispatchScheduler({ projectId: runtime.scheduledDispatch.projectId, location: runtime.scheduledDispatch.location, queue: runtime.scheduledDispatch.queue, publicBaseUrl: runtime.scheduledDispatch.publicBaseUrl, sharedSecret: runtime.schedulerSharedSecret! }) : runtime.scheduledDispatch.provider === 'aws-eventbridge' ? createAwsScheduledDispatchScheduler({ region: runtime.scheduledDispatch.region, targetLambdaArn: runtime.scheduledDispatch.targetLambdaArn, roleArn: runtime.scheduledDispatch.roleArn, groupName: runtime.scheduledDispatch.groupName }) : createSelfHostedScheduledDispatchScheduler() : null const scheduledDispatchService = scheduledDispatchRepositoryClient && scheduledDispatchScheduler && householdConfigurationRepositoryClient ? createScheduledDispatchService({ repository: scheduledDispatchRepositoryClient.repository, scheduler: scheduledDispatchScheduler, householdConfigurationRepository: householdConfigurationRepositoryClient.repository }) : null const miniAppAdminService = householdConfigurationRepositoryClient ? createMiniAppAdminService( householdConfigurationRepositoryClient.repository, scheduledDispatchService ?? undefined ) : null const localePreferenceService = householdConfigurationRepositoryClient ? createLocalePreferenceService(householdConfigurationRepositoryClient.repository) : null const telegramPendingActionRepositoryClient = runtime.databaseUrl ? createDbTelegramPendingActionRepository(runtime.databaseUrl) : null const processedBotMessageRepositoryClient = runtime.databaseUrl && runtime.assistantEnabled ? createDbProcessedBotMessageRepository(runtime.databaseUrl) : null const purchaseRepositoryClient = runtime.databaseUrl ? createPurchaseMessageRepository(runtime.databaseUrl) : null const topicMessageHistoryRepositoryClient = runtime.databaseUrl ? createDbTopicMessageHistoryRepository(runtime.databaseUrl) : null const purchaseInterpreter = createOpenAiPurchaseInterpreter( runtime.openaiApiKey, runtime.purchaseParserModel ) const adHocNotificationInterpreter = createOpenAiAdHocNotificationInterpreter({ apiKey: runtime.openaiApiKey, parserModel: runtime.purchaseParserModel, rendererModel: runtime.assistantModel, timeoutMs: runtime.assistantTimeoutMs }) const assistantMemoryStore = createInMemoryAssistantConversationMemoryStore( runtime.assistantMemoryMaxTurns ) const assistantRateLimiter = createInMemoryAssistantRateLimiter({ burstLimit: runtime.assistantRateLimitBurst, burstWindowMs: runtime.assistantRateLimitBurstWindowMs, rollingLimit: runtime.assistantRateLimitRolling, rollingWindowMs: runtime.assistantRateLimitRollingWindowMs }) const assistantUsageTracker = createInMemoryAssistantUsageTracker() const conversationalAssistant = createOpenAiChatAssistant( runtime.openaiApiKey, runtime.assistantModel, runtime.assistantTimeoutMs ) const topicProcessor = createTopicProcessor( runtime.openaiApiKey, runtime.topicProcessorModel, runtime.topicProcessorTimeoutMs, getLogger('topic-processor') ) const householdContextCache = new HouseholdContextCache() const anonymousFeedbackRepositoryClients = new Map< string, ReturnType >() const anonymousFeedbackServices = new Map< string, ReturnType >() const adHocNotificationRepositoryClient = runtime.databaseUrl ? createDbAdHocNotificationRepository(runtime.databaseUrl) : null const adHocNotificationService = adHocNotificationRepositoryClient && householdConfigurationRepositoryClient ? createAdHocNotificationService({ repository: adHocNotificationRepositoryClient.repository, householdConfigurationRepository: householdConfigurationRepositoryClient.repository, ...(scheduledDispatchService ? { scheduledDispatchService } : {}) }) : null function financeServiceForHousehold(householdId: string) { const existing = financeServices.get(householdId) if (existing) { return existing } const repositoryClient = financeRepositoryForHousehold(householdId) const service = createFinanceCommandService({ householdId, repository: repositoryClient.repository, householdConfigurationRepository: householdConfigurationRepositoryClient!.repository, exchangeRateProvider }) financeServices.set(householdId, service) return service } function financeRepositoryForHousehold(householdId: string) { const existing = financeRepositoryClients.get(householdId) if (existing) { return existing } const repositoryClient = createDbFinanceRepository(runtime.databaseUrl!, householdId) financeRepositoryClients.set(householdId, repositoryClient) shutdownTasks.push(repositoryClient.close) return repositoryClient } function paymentConfirmationServiceForHousehold(householdId: string) { const existing = paymentConfirmationServices.get(householdId) if (existing) { return existing } const service = createPaymentConfirmationService({ householdId, financeService: financeServiceForHousehold(householdId), repository: financeRepositoryForHousehold(householdId).repository, householdConfigurationRepository: householdConfigurationRepositoryClient!.repository, exchangeRateProvider }) paymentConfirmationServices.set(householdId, service) return service } function anonymousFeedbackServiceForHousehold(householdId: string) { const existing = anonymousFeedbackServices.get(householdId) if (existing) { return existing } const repositoryClient = createDbAnonymousFeedbackRepository(runtime.databaseUrl!, householdId) anonymousFeedbackRepositoryClients.set(householdId, repositoryClient) shutdownTasks.push(repositoryClient.close) const service = createAnonymousFeedbackService(repositoryClient.repository) anonymousFeedbackServices.set(householdId, service) return service } if (householdConfigurationRepositoryClient) { shutdownTasks.push(householdConfigurationRepositoryClient.close) } if (telegramPendingActionRepositoryClient) { shutdownTasks.push(telegramPendingActionRepositoryClient.close) } if (processedBotMessageRepositoryClient) { shutdownTasks.push(processedBotMessageRepositoryClient.close) } if (purchaseRepositoryClient) { shutdownTasks.push(purchaseRepositoryClient.close) } if (topicMessageHistoryRepositoryClient) { shutdownTasks.push(topicMessageHistoryRepositoryClient.close) } if (adHocNotificationRepositoryClient) { shutdownTasks.push(adHocNotificationRepositoryClient.close) } if (scheduledDispatchRepositoryClient) { shutdownTasks.push(scheduledDispatchRepositoryClient.close) } if (purchaseRepositoryClient && householdConfigurationRepositoryClient) { registerConfiguredPurchaseTopicIngestion( bot, householdConfigurationRepositoryClient.repository, purchaseRepositoryClient.repository, { ...(topicProcessor ? { topicProcessor, contextCache: householdContextCache, memoryStore: assistantMemoryStore, ...(topicMessageHistoryRepositoryClient ? { historyRepository: topicMessageHistoryRepositoryClient.repository } : {}) } : {}), ...(purchaseInterpreter ? { interpreter: purchaseInterpreter } : {}), logger: getLogger('purchase-ingestion') } ) registerConfiguredPaymentTopicIngestion( bot, householdConfigurationRepositoryClient.repository, telegramPendingActionRepositoryClient!.repository, financeServiceForHousehold, paymentConfirmationServiceForHousehold, { ...(topicProcessor ? { topicProcessor, contextCache: householdContextCache, memoryStore: assistantMemoryStore, ...(topicMessageHistoryRepositoryClient ? { historyRepository: topicMessageHistoryRepositoryClient.repository } : {}) } : {}), logger: getLogger('payment-ingestion') } ) } else { logger.warn( { event: 'runtime.feature_disabled', feature: 'purchase-topic-ingestion' }, 'Purchase topic ingestion is disabled. Set DATABASE_URL to enable Telegram topic lookups.' ) } if (runtime.financeCommandsEnabled) { const financeCommands = createFinanceCommandsService({ householdConfigurationRepository: householdConfigurationRepositoryClient!.repository, financeServiceForHousehold, ...(runtime.miniAppUrl ? { miniAppUrl: runtime.miniAppUrl, botUsername: bot.botInfo?.username } : {}) }) financeCommands.register(bot) } else { logger.warn( { event: 'runtime.feature_disabled', feature: 'finance-commands' }, 'Finance commands are disabled. Set DATABASE_URL to enable household lookups.' ) } if (householdConfigurationRepositoryClient) { registerHouseholdSetupCommands({ bot, householdSetupService: createHouseholdSetupService( householdConfigurationRepositoryClient.repository, scheduledDispatchService ?? undefined ), householdAdminService: createHouseholdAdminService( householdConfigurationRepositoryClient.repository ), householdOnboardingService: householdOnboardingService!, householdConfigurationRepository: householdConfigurationRepositoryClient.repository, ...(telegramPendingActionRepositoryClient ? { promptRepository: telegramPendingActionRepositoryClient.repository } : {}), ...(runtime.miniAppUrl ? { miniAppUrl: runtime.miniAppUrl } : {}), logger: getLogger('household-setup') }) } else { logger.warn( { event: 'runtime.feature_disabled', feature: 'household-setup' }, 'Household setup commands are disabled. Set DATABASE_URL to enable.' ) } if ( householdConfigurationRepositoryClient && telegramPendingActionRepositoryClient && adHocNotificationService ) { registerAdHocNotifications({ bot, householdConfigurationRepository: householdConfigurationRepositoryClient.repository, promptRepository: telegramPendingActionRepositoryClient.repository, notificationService: adHocNotificationService, reminderInterpreter: adHocNotificationInterpreter, logger: getLogger('ad-hoc-notifications') }) } const scheduledDispatchHandler = scheduledDispatchService && adHocNotificationRepositoryClient && householdConfigurationRepositoryClient ? createScheduledDispatchHandler({ scheduledDispatchService, adHocNotificationRepository: adHocNotificationRepositoryClient.repository, householdConfigurationRepository: householdConfigurationRepositoryClient.repository, sendTopicMessage: async (input) => { const threadId = input.threadId ? Number(input.threadId) : undefined await bot.api.sendMessage(input.chatId, input.text, { ...(threadId && Number.isInteger(threadId) ? { message_thread_id: threadId } : {}), ...(input.parseMode ? { parse_mode: input.parseMode } : {}), ...(input.replyMarkup ? { reply_markup: input.replyMarkup } : {}) }) }, sendDirectMessage: async (input) => { await bot.api.sendMessage(input.telegramUserId, input.text) }, ...(runtime.miniAppUrl ? { miniAppUrl: runtime.miniAppUrl } : {}), ...(bot.botInfo?.username ? { botUsername: bot.botInfo.username } : {}), logger: getLogger('scheduler') }) : null if (!scheduledDispatchHandler) { logger.warn( { event: 'runtime.feature_disabled', feature: 'scheduled-dispatch' }, 'Scheduled dispatch is disabled. Configure DATABASE_URL and SCHEDULED_DISPATCH_PROVIDER to enable reminder delivery.' ) } if ( runtime.anonymousFeedbackEnabled && householdConfigurationRepositoryClient && telegramPendingActionRepositoryClient ) { registerAnonymousFeedback({ bot, anonymousFeedbackServiceForHousehold, householdConfigurationRepository: householdConfigurationRepositoryClient.repository, promptRepository: telegramPendingActionRepositoryClient.repository, logger: getLogger('anonymous-feedback') }) } else { logger.warn( { event: 'runtime.feature_disabled', feature: 'anonymous-feedback' }, 'Anonymous feedback is disabled. Set DATABASE_URL to enable household and topic lookups.' ) } if ( runtime.assistantEnabled && householdConfigurationRepositoryClient && telegramPendingActionRepositoryClient ) { if (processedBotMessageRepositoryClient) { registerDmAssistant({ bot, householdConfigurationRepository: householdConfigurationRepositoryClient.repository, messageProcessingRepository: processedBotMessageRepositoryClient.repository, promptRepository: telegramPendingActionRepositoryClient.repository, financeServiceForHousehold, memoryStore: assistantMemoryStore, rateLimiter: assistantRateLimiter, usageTracker: assistantUsageTracker, ...(purchaseRepositoryClient ? { purchaseRepository: purchaseRepositoryClient.repository } : {}), ...(topicMessageHistoryRepositoryClient ? { topicMessageHistoryRepository: topicMessageHistoryRepositoryClient.repository } : {}), ...(purchaseInterpreter ? { purchaseInterpreter } : {}), ...(conversationalAssistant ? { assistant: conversationalAssistant } : {}), logger: getLogger('dm-assistant') }) } else { registerDmAssistant({ bot, householdConfigurationRepository: householdConfigurationRepositoryClient.repository, promptRepository: telegramPendingActionRepositoryClient.repository, financeServiceForHousehold, memoryStore: assistantMemoryStore, rateLimiter: assistantRateLimiter, usageTracker: assistantUsageTracker, ...(purchaseRepositoryClient ? { purchaseRepository: purchaseRepositoryClient.repository } : {}), ...(topicMessageHistoryRepositoryClient ? { topicMessageHistoryRepository: topicMessageHistoryRepositoryClient.repository } : {}), ...(purchaseInterpreter ? { purchaseInterpreter } : {}), ...(conversationalAssistant ? { assistant: conversationalAssistant } : {}), logger: getLogger('dm-assistant') }) } } if (householdConfigurationRepositoryClient && telegramPendingActionRepositoryClient) { registerReminderTopicUtilities({ bot, householdConfigurationRepository: householdConfigurationRepositoryClient.repository, promptRepository: telegramPendingActionRepositoryClient.repository, financeServiceForHousehold, logger: getLogger('reminder-utilities') }) } const server = createBotWebhookServer({ webhookPath: runtime.telegramWebhookPath, webhookSecret: runtime.telegramWebhookSecret, webhookHandler, miniAppAuth: householdOnboardingService ? createMiniAppAuthHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, logger: getLogger('miniapp-auth') }) : undefined, miniAppJoin: householdOnboardingService ? createMiniAppJoinHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, logger: getLogger('miniapp-auth') }) : undefined, miniAppDashboard: householdOnboardingService ? createMiniAppDashboardHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, financeServiceForHousehold, adHocNotificationService: adHocNotificationService!, onboardingService: householdOnboardingService, logger: getLogger('miniapp-dashboard') }) : undefined, miniAppUpdateNotification: householdOnboardingService && adHocNotificationService ? createMiniAppUpdateNotificationHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, adHocNotificationService, logger: getLogger('miniapp-notifications') }) : undefined, miniAppCancelNotification: householdOnboardingService && adHocNotificationService ? createMiniAppCancelNotificationHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, adHocNotificationService, logger: getLogger('miniapp-notifications') }) : undefined, miniAppPendingMembers: householdOnboardingService ? createMiniAppPendingMembersHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, miniAppAdminService: miniAppAdminService!, logger: getLogger('miniapp-admin') }) : undefined, miniAppApproveMember: householdOnboardingService ? createMiniAppApproveMemberHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, miniAppAdminService: miniAppAdminService!, logger: getLogger('miniapp-admin') }) : undefined, miniAppRejectMember: householdOnboardingService ? createMiniAppRejectMemberHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, miniAppAdminService: miniAppAdminService!, logger: getLogger('miniapp-admin') }) : undefined, miniAppSettings: householdOnboardingService ? createMiniAppSettingsHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, miniAppAdminService: miniAppAdminService!, assistantUsageTracker, logger: getLogger('miniapp-admin') }) : undefined, miniAppUpdateSettings: householdOnboardingService ? createMiniAppUpdateSettingsHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, miniAppAdminService: miniAppAdminService!, logger: getLogger('miniapp-admin') }) : undefined, miniAppUpsertUtilityCategory: householdOnboardingService ? createMiniAppUpsertUtilityCategoryHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, miniAppAdminService: miniAppAdminService!, logger: getLogger('miniapp-admin') }) : undefined, miniAppPromoteMember: householdOnboardingService ? createMiniAppPromoteMemberHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, miniAppAdminService: miniAppAdminService!, logger: getLogger('miniapp-admin') }) : undefined, miniAppDemoteMember: householdOnboardingService ? createMiniAppDemoteMemberHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, miniAppAdminService: miniAppAdminService!, logger: getLogger('miniapp-admin') }) : undefined, miniAppUpdateOwnDisplayName: householdOnboardingService ? createMiniAppUpdateOwnDisplayNameHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, miniAppAdminService: miniAppAdminService!, logger: getLogger('miniapp-admin') }) : undefined, miniAppUpdateMemberDisplayName: householdOnboardingService ? createMiniAppUpdateMemberDisplayNameHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, miniAppAdminService: miniAppAdminService!, logger: getLogger('miniapp-admin') }) : undefined, miniAppUpdateMemberRentWeight: householdOnboardingService ? createMiniAppUpdateMemberRentWeightHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, miniAppAdminService: miniAppAdminService!, logger: getLogger('miniapp-admin') }) : undefined, miniAppUpdateMemberStatus: householdOnboardingService ? createMiniAppUpdateMemberStatusHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, miniAppAdminService: miniAppAdminService!, logger: getLogger('miniapp-admin') }) : undefined, miniAppUpdateMemberAbsencePolicy: householdOnboardingService ? createMiniAppUpdateMemberAbsencePolicyHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, miniAppAdminService: miniAppAdminService!, logger: getLogger('miniapp-admin') }) : undefined, miniAppBillingCycle: householdOnboardingService ? createMiniAppBillingCycleHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppOpenCycle: householdOnboardingService ? createMiniAppOpenCycleHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppCloseCycle: householdOnboardingService ? createMiniAppCloseCycleHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppRentUpdate: householdOnboardingService ? createMiniAppRentUpdateHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppAddUtilityBill: householdOnboardingService ? createMiniAppAddUtilityBillHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppSubmitUtilityBill: householdOnboardingService ? createMiniAppSubmitUtilityBillHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppUpdateUtilityBill: householdOnboardingService ? createMiniAppUpdateUtilityBillHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppDeleteUtilityBill: householdOnboardingService ? createMiniAppDeleteUtilityBillHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppAddPurchase: householdOnboardingService ? createMiniAppAddPurchaseHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppUpdatePurchase: householdOnboardingService ? createMiniAppUpdatePurchaseHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppDeletePurchase: householdOnboardingService ? createMiniAppDeletePurchaseHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppAddPayment: householdOnboardingService ? createMiniAppAddPaymentHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppUpdatePayment: householdOnboardingService ? createMiniAppUpdatePaymentHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppDeletePayment: householdOnboardingService ? createMiniAppDeletePaymentHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, financeServiceForHousehold, logger: getLogger('miniapp-billing') }) : undefined, miniAppLocalePreference: householdOnboardingService ? createMiniAppLocalePreferenceHandler({ allowedOrigins: runtime.miniAppAllowedOrigins, botToken: runtime.telegramBotToken, onboardingService: householdOnboardingService, localePreferenceService: localePreferenceService!, logger: getLogger('miniapp-admin') }) : undefined, scheduler: scheduledDispatchHandler && runtime.schedulerSharedSecret ? { pathPrefix: '/jobs', authorize: createSchedulerRequestAuthorizer({ sharedSecret: runtime.schedulerSharedSecret, oidcAllowedEmails: runtime.schedulerOidcAllowedEmails }).authorize, handler: async (request, jobPath) => { if (jobPath === 'dispatch-due') { return scheduledDispatchHandler ? scheduledDispatchHandler.handleDueDispatches(request) : new Response('Not Found', { status: 404 }) } if (jobPath.startsWith('dispatch/')) { return scheduledDispatchHandler ? scheduledDispatchHandler.handle(request, jobPath.slice('dispatch/'.length)) : new Response('Not Found', { status: 404 }) } return new Response('Not Found', { status: 404 }) } } : scheduledDispatchHandler ? { pathPrefix: '/jobs', authorize: createSchedulerRequestAuthorizer({ oidcAllowedEmails: runtime.schedulerOidcAllowedEmails }).authorize, handler: async (request, jobPath) => { if (jobPath === 'dispatch-due') { return scheduledDispatchHandler ? scheduledDispatchHandler.handleDueDispatches(request) : new Response('Not Found', { status: 404 }) } if (jobPath.startsWith('dispatch/')) { return scheduledDispatchHandler ? scheduledDispatchHandler.handle(request, jobPath.slice('dispatch/'.length)) : new Response('Not Found', { status: 404 }) } return new Response('Not Found', { status: 404 }) } } : undefined }) if (scheduledDispatchService) { await scheduledDispatchService.reconcileAllBuiltInDispatches() } return { fetch: server.fetch, runtime, shutdown: async () => { await Promise.allSettled(shutdownTasks.map((close) => close())) } } }