mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 10:24:02 +00:00
feat(db): enforce runtime RLS boundaries
This commit is contained in:
@@ -4,7 +4,12 @@ LOG_LEVEL=info
|
||||
PORT=3000
|
||||
|
||||
# Database
|
||||
# Owner connection for migrations, seed, and schema checks only
|
||||
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:54322/postgres
|
||||
# Runtime connection used by mini app and authenticated user-triggered API flows
|
||||
APP_DATABASE_URL=postgres://housebot_app:housebot_app@127.0.0.1:54322/postgres
|
||||
# Runtime connection used by bot ingestion, reminders, schedulers, and other worker flows
|
||||
WORKER_DATABASE_URL=postgres://housebot_worker:housebot_worker@127.0.0.1:54322/postgres
|
||||
DB_SCHEMA=public
|
||||
|
||||
# Telegram
|
||||
|
||||
@@ -4,6 +4,8 @@ export interface BotRuntimeConfig {
|
||||
telegramBotToken: string
|
||||
telegramWebhookSecret: string
|
||||
telegramWebhookPath: string
|
||||
appDatabaseUrl?: string
|
||||
workerDatabaseUrl?: string
|
||||
databaseUrl?: string
|
||||
purchaseTopicIngestionEnabled: boolean
|
||||
financeCommandsEnabled: boolean
|
||||
@@ -101,20 +103,22 @@ function parsePositiveInteger(raw: string | undefined, fallback: number, key: st
|
||||
|
||||
export function getBotRuntimeConfig(env: NodeJS.ProcessEnv = process.env): BotRuntimeConfig {
|
||||
const databaseUrl = parseOptionalValue(env.DATABASE_URL)
|
||||
const appDatabaseUrl = parseOptionalValue(env.APP_DATABASE_URL)
|
||||
const workerDatabaseUrl = parseOptionalValue(env.WORKER_DATABASE_URL)
|
||||
const schedulerSharedSecret = parseOptionalValue(env.SCHEDULER_SHARED_SECRET)
|
||||
const schedulerOidcAllowedEmails = parseOptionalCsv(env.SCHEDULER_OIDC_ALLOWED_EMAILS)
|
||||
const miniAppAllowedOrigins = parseOptionalCsv(env.MINI_APP_ALLOWED_ORIGINS)
|
||||
const miniAppUrl = parseOptionalValue(env.MINI_APP_URL)
|
||||
|
||||
const purchaseTopicIngestionEnabled = databaseUrl !== undefined
|
||||
|
||||
const financeCommandsEnabled = databaseUrl !== undefined
|
||||
const anonymousFeedbackEnabled = databaseUrl !== undefined
|
||||
const assistantEnabled = databaseUrl !== undefined
|
||||
const miniAppAuthEnabled = databaseUrl !== undefined
|
||||
const purchaseTopicIngestionEnabled = workerDatabaseUrl !== undefined
|
||||
const financeCommandsEnabled = workerDatabaseUrl !== undefined
|
||||
const anonymousFeedbackEnabled = workerDatabaseUrl !== undefined
|
||||
const assistantEnabled = workerDatabaseUrl !== undefined
|
||||
const miniAppAuthEnabled = appDatabaseUrl !== undefined
|
||||
const hasSchedulerOidcConfig = schedulerOidcAllowedEmails.length > 0
|
||||
const reminderJobsEnabled =
|
||||
databaseUrl !== undefined && (schedulerSharedSecret !== undefined || hasSchedulerOidcConfig)
|
||||
workerDatabaseUrl !== undefined &&
|
||||
(schedulerSharedSecret !== undefined || hasSchedulerOidcConfig)
|
||||
|
||||
const runtime: BotRuntimeConfig = {
|
||||
port: parsePort(env.PORT),
|
||||
@@ -173,6 +177,12 @@ export function getBotRuntimeConfig(env: NodeJS.ProcessEnv = process.env): BotRu
|
||||
if (databaseUrl !== undefined) {
|
||||
runtime.databaseUrl = databaseUrl
|
||||
}
|
||||
if (appDatabaseUrl !== undefined) {
|
||||
runtime.appDatabaseUrl = appDatabaseUrl
|
||||
}
|
||||
if (workerDatabaseUrl !== undefined) {
|
||||
runtime.workerDatabaseUrl = workerDatabaseUrl
|
||||
}
|
||||
if (schedulerSharedSecret !== undefined) {
|
||||
runtime.schedulerSharedSecret = schedulerSharedSecret
|
||||
}
|
||||
|
||||
@@ -47,7 +47,11 @@ import { createReminderJobsHandler } from './reminder-jobs'
|
||||
import { registerReminderTopicUtilities } from './reminder-topic-utilities'
|
||||
import { createSchedulerRequestAuthorizer } from './scheduler-auth'
|
||||
import { createBotWebhookServer } from './server'
|
||||
import { createMiniAppAuthHandler, createMiniAppJoinHandler } from './miniapp-auth'
|
||||
import {
|
||||
createMiniAppAuthHandler,
|
||||
createMiniAppJoinHandler,
|
||||
type MiniAppAuthorizedSession
|
||||
} from './miniapp-auth'
|
||||
import { createMiniAppDashboardHandler } from './miniapp-dashboard'
|
||||
import {
|
||||
createMiniAppApproveMemberHandler,
|
||||
@@ -90,13 +94,13 @@ configureLogger({
|
||||
|
||||
const logger = getLogger('runtime')
|
||||
const shutdownTasks: Array<() => Promise<void>> = []
|
||||
const householdConfigurationRepositoryClient = runtime.databaseUrl
|
||||
? createDbHouseholdConfigurationRepository(runtime.databaseUrl)
|
||||
const workerHouseholdConfigurationRepositoryClient = runtime.workerDatabaseUrl
|
||||
? createDbHouseholdConfigurationRepository(runtime.workerDatabaseUrl)
|
||||
: null
|
||||
const bot = createTelegramBot(
|
||||
runtime.telegramBotToken,
|
||||
getLogger('telegram'),
|
||||
householdConfigurationRepositoryClient?.repository
|
||||
workerHouseholdConfigurationRepositoryClient?.repository
|
||||
)
|
||||
bot.botInfo = await bot.api.getMe()
|
||||
const webhookHandler = webhookCallback(bot, 'std/http', {
|
||||
@@ -111,29 +115,23 @@ const paymentConfirmationServices = new Map<
|
||||
const exchangeRateProvider = createNbgExchangeRateProvider({
|
||||
logger: getLogger('fx')
|
||||
})
|
||||
const householdOnboardingService = householdConfigurationRepositoryClient
|
||||
const householdOnboardingService = workerHouseholdConfigurationRepositoryClient
|
||||
? createHouseholdOnboardingService({
|
||||
repository: householdConfigurationRepositoryClient.repository
|
||||
repository: workerHouseholdConfigurationRepositoryClient.repository
|
||||
})
|
||||
: null
|
||||
const miniAppAdminService = householdConfigurationRepositoryClient
|
||||
? createMiniAppAdminService(householdConfigurationRepositoryClient.repository)
|
||||
: null
|
||||
const localePreferenceService = householdConfigurationRepositoryClient
|
||||
? createLocalePreferenceService(householdConfigurationRepositoryClient.repository)
|
||||
: null
|
||||
const telegramPendingActionRepositoryClient = runtime.databaseUrl
|
||||
? createDbTelegramPendingActionRepository(runtime.databaseUrl!)
|
||||
const telegramPendingActionRepositoryClient = runtime.workerDatabaseUrl
|
||||
? createDbTelegramPendingActionRepository(runtime.workerDatabaseUrl!)
|
||||
: null
|
||||
const processedBotMessageRepositoryClient =
|
||||
runtime.databaseUrl && runtime.assistantEnabled
|
||||
? createDbProcessedBotMessageRepository(runtime.databaseUrl!)
|
||||
runtime.workerDatabaseUrl && runtime.assistantEnabled
|
||||
? createDbProcessedBotMessageRepository(runtime.workerDatabaseUrl!)
|
||||
: null
|
||||
const purchaseRepositoryClient = runtime.databaseUrl
|
||||
? createPurchaseMessageRepository(runtime.databaseUrl!)
|
||||
const purchaseRepositoryClient = runtime.workerDatabaseUrl
|
||||
? createPurchaseMessageRepository(runtime.workerDatabaseUrl!)
|
||||
: null
|
||||
const topicMessageHistoryRepositoryClient = runtime.databaseUrl
|
||||
? createDbTopicMessageHistoryRepository(runtime.databaseUrl!)
|
||||
const topicMessageHistoryRepositoryClient = runtime.workerDatabaseUrl
|
||||
? createDbTopicMessageHistoryRepository(runtime.workerDatabaseUrl!)
|
||||
: null
|
||||
const purchaseInterpreter = createOpenAiPurchaseInterpreter(
|
||||
runtime.openaiApiKey,
|
||||
@@ -169,6 +167,167 @@ const anonymousFeedbackServices = new Map<
|
||||
string,
|
||||
ReturnType<typeof createAnonymousFeedbackService>
|
||||
>()
|
||||
const appHouseholdConfigurationRepositoryClients = new Map<
|
||||
string,
|
||||
ReturnType<typeof createDbHouseholdConfigurationRepository>
|
||||
>()
|
||||
const appOnboardingServices = new Map<string, ReturnType<typeof createHouseholdOnboardingService>>()
|
||||
const appFinanceRepositoryClients = new Map<string, ReturnType<typeof createDbFinanceRepository>>()
|
||||
const appFinanceServices = new Map<string, ReturnType<typeof createFinanceCommandService>>()
|
||||
const appMiniAppAdminServices = new Map<string, ReturnType<typeof createMiniAppAdminService>>()
|
||||
const appLocalePreferenceServices = new Map<
|
||||
string,
|
||||
ReturnType<typeof createLocalePreferenceService>
|
||||
>()
|
||||
|
||||
function miniAppSessionKey(session: MiniAppAuthorizedSession): string {
|
||||
return [
|
||||
session.telegramUserId,
|
||||
session.member.householdId,
|
||||
session.member.id,
|
||||
session.member.isAdmin ? 'admin' : 'member'
|
||||
].join(':')
|
||||
}
|
||||
|
||||
function appHouseholdConfigurationRepositoryKey(input: {
|
||||
telegramUserId: string
|
||||
householdId?: string
|
||||
memberId?: string
|
||||
isAdmin?: boolean
|
||||
}): string {
|
||||
return [
|
||||
input.telegramUserId,
|
||||
input.householdId ?? 'none',
|
||||
input.memberId ?? 'none',
|
||||
input.isAdmin === true ? 'admin' : 'member'
|
||||
].join(':')
|
||||
}
|
||||
|
||||
function appHouseholdConfigurationRepositoryForContext(input: {
|
||||
telegramUserId: string
|
||||
householdId?: string
|
||||
memberId?: string
|
||||
isAdmin?: boolean
|
||||
}) {
|
||||
const key = appHouseholdConfigurationRepositoryKey(input)
|
||||
const existing = appHouseholdConfigurationRepositoryClients.get(key)
|
||||
if (existing) {
|
||||
return existing
|
||||
}
|
||||
|
||||
const repositoryClient = createDbHouseholdConfigurationRepository(runtime.appDatabaseUrl!, {
|
||||
sessionContext: {
|
||||
telegramUserId: input.telegramUserId,
|
||||
...(input.householdId
|
||||
? {
|
||||
householdId: input.householdId
|
||||
}
|
||||
: {}),
|
||||
...(input.memberId
|
||||
? {
|
||||
memberId: input.memberId
|
||||
}
|
||||
: {}),
|
||||
...(input.isAdmin !== undefined
|
||||
? {
|
||||
isAdmin: input.isAdmin
|
||||
}
|
||||
: {})
|
||||
}
|
||||
})
|
||||
appHouseholdConfigurationRepositoryClients.set(key, repositoryClient)
|
||||
shutdownTasks.push(repositoryClient.close)
|
||||
return repositoryClient
|
||||
}
|
||||
|
||||
function appOnboardingServiceForTelegramUserId(telegramUserId: string) {
|
||||
const existing = appOnboardingServices.get(telegramUserId)
|
||||
if (existing) {
|
||||
return existing
|
||||
}
|
||||
|
||||
const service = createHouseholdOnboardingService({
|
||||
repository: appHouseholdConfigurationRepositoryForContext({
|
||||
telegramUserId
|
||||
}).repository
|
||||
})
|
||||
appOnboardingServices.set(telegramUserId, service)
|
||||
return service
|
||||
}
|
||||
|
||||
function appHouseholdConfigurationRepositoryForSession(session: MiniAppAuthorizedSession) {
|
||||
return appHouseholdConfigurationRepositoryForContext({
|
||||
telegramUserId: session.telegramUserId,
|
||||
householdId: session.member.householdId,
|
||||
memberId: session.member.id,
|
||||
isAdmin: session.member.isAdmin
|
||||
})
|
||||
}
|
||||
|
||||
function appFinanceServiceForSession(session: MiniAppAuthorizedSession) {
|
||||
const key = miniAppSessionKey(session)
|
||||
const existing = appFinanceServices.get(key)
|
||||
if (existing) {
|
||||
return existing
|
||||
}
|
||||
|
||||
const repositoryClient = createDbFinanceRepository(
|
||||
runtime.appDatabaseUrl!,
|
||||
session.member.householdId,
|
||||
{
|
||||
sessionContext: {
|
||||
telegramUserId: session.telegramUserId,
|
||||
householdId: session.member.householdId,
|
||||
memberId: session.member.id,
|
||||
...(session.member.isAdmin !== undefined
|
||||
? {
|
||||
isAdmin: session.member.isAdmin
|
||||
}
|
||||
: {})
|
||||
}
|
||||
}
|
||||
)
|
||||
appFinanceRepositoryClients.set(key, repositoryClient)
|
||||
shutdownTasks.push(repositoryClient.close)
|
||||
|
||||
const service = createFinanceCommandService({
|
||||
householdId: session.member.householdId,
|
||||
repository: repositoryClient.repository,
|
||||
householdConfigurationRepository:
|
||||
appHouseholdConfigurationRepositoryForSession(session).repository,
|
||||
exchangeRateProvider
|
||||
})
|
||||
appFinanceServices.set(key, service)
|
||||
return service
|
||||
}
|
||||
|
||||
function appMiniAppAdminServiceForSession(session: MiniAppAuthorizedSession) {
|
||||
const key = miniAppSessionKey(session)
|
||||
const existing = appMiniAppAdminServices.get(key)
|
||||
if (existing) {
|
||||
return existing
|
||||
}
|
||||
|
||||
const service = createMiniAppAdminService(
|
||||
appHouseholdConfigurationRepositoryForSession(session).repository
|
||||
)
|
||||
appMiniAppAdminServices.set(key, service)
|
||||
return service
|
||||
}
|
||||
|
||||
function appLocalePreferenceServiceForSession(session: MiniAppAuthorizedSession) {
|
||||
const key = miniAppSessionKey(session)
|
||||
const existing = appLocalePreferenceServices.get(key)
|
||||
if (existing) {
|
||||
return existing
|
||||
}
|
||||
|
||||
const service = createLocalePreferenceService(
|
||||
appHouseholdConfigurationRepositoryForSession(session).repository
|
||||
)
|
||||
appLocalePreferenceServices.set(key, service)
|
||||
return service
|
||||
}
|
||||
|
||||
function financeServiceForHousehold(householdId: string) {
|
||||
const existing = financeServices.get(householdId)
|
||||
@@ -180,7 +339,7 @@ function financeServiceForHousehold(householdId: string) {
|
||||
const service = createFinanceCommandService({
|
||||
householdId,
|
||||
repository: repositoryClient.repository,
|
||||
householdConfigurationRepository: householdConfigurationRepositoryClient!.repository,
|
||||
householdConfigurationRepository: workerHouseholdConfigurationRepositoryClient!.repository,
|
||||
exchangeRateProvider
|
||||
})
|
||||
financeServices.set(householdId, service)
|
||||
@@ -193,7 +352,7 @@ function financeRepositoryForHousehold(householdId: string) {
|
||||
return existing
|
||||
}
|
||||
|
||||
const repositoryClient = createDbFinanceRepository(runtime.databaseUrl!, householdId)
|
||||
const repositoryClient = createDbFinanceRepository(runtime.workerDatabaseUrl!, householdId)
|
||||
financeRepositoryClients.set(householdId, repositoryClient)
|
||||
shutdownTasks.push(repositoryClient.close)
|
||||
return repositoryClient
|
||||
@@ -209,7 +368,7 @@ function paymentConfirmationServiceForHousehold(householdId: string) {
|
||||
householdId,
|
||||
financeService: financeServiceForHousehold(householdId),
|
||||
repository: financeRepositoryForHousehold(householdId).repository,
|
||||
householdConfigurationRepository: householdConfigurationRepositoryClient!.repository,
|
||||
householdConfigurationRepository: workerHouseholdConfigurationRepositoryClient!.repository,
|
||||
exchangeRateProvider
|
||||
})
|
||||
paymentConfirmationServices.set(householdId, service)
|
||||
@@ -222,7 +381,10 @@ function anonymousFeedbackServiceForHousehold(householdId: string) {
|
||||
return existing
|
||||
}
|
||||
|
||||
const repositoryClient = createDbAnonymousFeedbackRepository(runtime.databaseUrl!, householdId)
|
||||
const repositoryClient = createDbAnonymousFeedbackRepository(
|
||||
runtime.workerDatabaseUrl!,
|
||||
householdId
|
||||
)
|
||||
anonymousFeedbackRepositoryClients.set(householdId, repositoryClient)
|
||||
shutdownTasks.push(repositoryClient.close)
|
||||
|
||||
@@ -231,8 +393,8 @@ function anonymousFeedbackServiceForHousehold(householdId: string) {
|
||||
return service
|
||||
}
|
||||
|
||||
if (householdConfigurationRepositoryClient) {
|
||||
shutdownTasks.push(householdConfigurationRepositoryClient.close)
|
||||
if (workerHouseholdConfigurationRepositoryClient) {
|
||||
shutdownTasks.push(workerHouseholdConfigurationRepositoryClient.close)
|
||||
}
|
||||
|
||||
if (telegramPendingActionRepositoryClient) {
|
||||
@@ -251,10 +413,10 @@ if (topicMessageHistoryRepositoryClient) {
|
||||
shutdownTasks.push(topicMessageHistoryRepositoryClient.close)
|
||||
}
|
||||
|
||||
if (purchaseRepositoryClient && householdConfigurationRepositoryClient) {
|
||||
if (purchaseRepositoryClient && workerHouseholdConfigurationRepositoryClient) {
|
||||
registerConfiguredPurchaseTopicIngestion(
|
||||
bot,
|
||||
householdConfigurationRepositoryClient.repository,
|
||||
workerHouseholdConfigurationRepositoryClient.repository,
|
||||
purchaseRepositoryClient.repository,
|
||||
{
|
||||
...(topicProcessor
|
||||
@@ -280,7 +442,7 @@ if (purchaseRepositoryClient && householdConfigurationRepositoryClient) {
|
||||
|
||||
registerConfiguredPaymentTopicIngestion(
|
||||
bot,
|
||||
householdConfigurationRepositoryClient.repository,
|
||||
workerHouseholdConfigurationRepositoryClient.repository,
|
||||
telegramPendingActionRepositoryClient!.repository,
|
||||
financeServiceForHousehold,
|
||||
paymentConfirmationServiceForHousehold,
|
||||
@@ -306,13 +468,13 @@ if (purchaseRepositoryClient && householdConfigurationRepositoryClient) {
|
||||
event: 'runtime.feature_disabled',
|
||||
feature: 'purchase-topic-ingestion'
|
||||
},
|
||||
'Purchase topic ingestion is disabled. Set DATABASE_URL to enable Telegram topic lookups.'
|
||||
'Purchase topic ingestion is disabled. Set WORKER_DATABASE_URL to enable Telegram topic lookups.'
|
||||
)
|
||||
}
|
||||
|
||||
if (runtime.financeCommandsEnabled) {
|
||||
const financeCommands = createFinanceCommandsService({
|
||||
householdConfigurationRepository: householdConfigurationRepositoryClient!.repository,
|
||||
householdConfigurationRepository: workerHouseholdConfigurationRepositoryClient!.repository,
|
||||
financeServiceForHousehold,
|
||||
...(runtime.miniAppUrl
|
||||
? {
|
||||
@@ -329,21 +491,21 @@ if (runtime.financeCommandsEnabled) {
|
||||
event: 'runtime.feature_disabled',
|
||||
feature: 'finance-commands'
|
||||
},
|
||||
'Finance commands are disabled. Set DATABASE_URL to enable household lookups.'
|
||||
'Finance commands are disabled. Set WORKER_DATABASE_URL to enable household lookups.'
|
||||
)
|
||||
}
|
||||
|
||||
if (householdConfigurationRepositoryClient) {
|
||||
if (workerHouseholdConfigurationRepositoryClient) {
|
||||
registerHouseholdSetupCommands({
|
||||
bot,
|
||||
householdSetupService: createHouseholdSetupService(
|
||||
householdConfigurationRepositoryClient.repository
|
||||
workerHouseholdConfigurationRepositoryClient.repository
|
||||
),
|
||||
householdAdminService: createHouseholdAdminService(
|
||||
householdConfigurationRepositoryClient.repository
|
||||
workerHouseholdConfigurationRepositoryClient.repository
|
||||
),
|
||||
householdOnboardingService: householdOnboardingService!,
|
||||
householdConfigurationRepository: householdConfigurationRepositoryClient.repository,
|
||||
householdConfigurationRepository: workerHouseholdConfigurationRepositoryClient.repository,
|
||||
...(telegramPendingActionRepositoryClient
|
||||
? {
|
||||
promptRepository: telegramPendingActionRepositoryClient.repository
|
||||
@@ -362,20 +524,22 @@ if (householdConfigurationRepositoryClient) {
|
||||
event: 'runtime.feature_disabled',
|
||||
feature: 'household-setup'
|
||||
},
|
||||
'Household setup commands are disabled. Set DATABASE_URL to enable.'
|
||||
'Household setup commands are disabled. Set WORKER_DATABASE_URL to enable.'
|
||||
)
|
||||
}
|
||||
|
||||
const reminderJobs = runtime.reminderJobsEnabled
|
||||
? (() => {
|
||||
const reminderRepositoryClient = createDbReminderDispatchRepository(runtime.databaseUrl!)
|
||||
const reminderRepositoryClient = createDbReminderDispatchRepository(
|
||||
runtime.workerDatabaseUrl!
|
||||
)
|
||||
const reminderService = createReminderJobService(reminderRepositoryClient.repository)
|
||||
|
||||
shutdownTasks.push(reminderRepositoryClient.close)
|
||||
|
||||
return createReminderJobsHandler({
|
||||
listReminderTargets: () =>
|
||||
householdConfigurationRepositoryClient!.repository.listReminderTargets(),
|
||||
workerHouseholdConfigurationRepositoryClient!.repository.listReminderTargets(),
|
||||
ensureBillingCycle: async ({ householdId, at }) => {
|
||||
await financeServiceForHousehold(householdId).ensureExpectedCycle(at)
|
||||
},
|
||||
@@ -426,19 +590,19 @@ if (!runtime.reminderJobsEnabled) {
|
||||
event: 'runtime.feature_disabled',
|
||||
feature: 'reminder-jobs'
|
||||
},
|
||||
'Reminder jobs are disabled. Set DATABASE_URL and either SCHEDULER_SHARED_SECRET or SCHEDULER_OIDC_ALLOWED_EMAILS to enable.'
|
||||
'Reminder jobs are disabled. Set WORKER_DATABASE_URL and either SCHEDULER_SHARED_SECRET or SCHEDULER_OIDC_ALLOWED_EMAILS to enable.'
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
runtime.anonymousFeedbackEnabled &&
|
||||
householdConfigurationRepositoryClient &&
|
||||
workerHouseholdConfigurationRepositoryClient &&
|
||||
telegramPendingActionRepositoryClient
|
||||
) {
|
||||
registerAnonymousFeedback({
|
||||
bot,
|
||||
anonymousFeedbackServiceForHousehold,
|
||||
householdConfigurationRepository: householdConfigurationRepositoryClient!.repository,
|
||||
householdConfigurationRepository: workerHouseholdConfigurationRepositoryClient!.repository,
|
||||
promptRepository: telegramPendingActionRepositoryClient!.repository,
|
||||
logger: getLogger('anonymous-feedback')
|
||||
})
|
||||
@@ -448,19 +612,19 @@ if (
|
||||
event: 'runtime.feature_disabled',
|
||||
feature: 'anonymous-feedback'
|
||||
},
|
||||
'Anonymous feedback is disabled. Set DATABASE_URL to enable household and topic lookups.'
|
||||
'Anonymous feedback is disabled. Set WORKER_DATABASE_URL to enable household and topic lookups.'
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
runtime.assistantEnabled &&
|
||||
householdConfigurationRepositoryClient &&
|
||||
workerHouseholdConfigurationRepositoryClient &&
|
||||
telegramPendingActionRepositoryClient
|
||||
) {
|
||||
if (processedBotMessageRepositoryClient) {
|
||||
registerDmAssistant({
|
||||
bot,
|
||||
householdConfigurationRepository: householdConfigurationRepositoryClient.repository,
|
||||
householdConfigurationRepository: workerHouseholdConfigurationRepositoryClient.repository,
|
||||
messageProcessingRepository: processedBotMessageRepositoryClient.repository,
|
||||
promptRepository: telegramPendingActionRepositoryClient.repository,
|
||||
financeServiceForHousehold,
|
||||
@@ -492,7 +656,7 @@ if (
|
||||
} else {
|
||||
registerDmAssistant({
|
||||
bot,
|
||||
householdConfigurationRepository: householdConfigurationRepositoryClient.repository,
|
||||
householdConfigurationRepository: workerHouseholdConfigurationRepositoryClient.repository,
|
||||
promptRepository: telegramPendingActionRepositoryClient.repository,
|
||||
financeServiceForHousehold,
|
||||
memoryStore: assistantMemoryStore,
|
||||
@@ -523,10 +687,10 @@ if (
|
||||
}
|
||||
}
|
||||
|
||||
if (householdConfigurationRepositoryClient && telegramPendingActionRepositoryClient) {
|
||||
if (workerHouseholdConfigurationRepositoryClient && telegramPendingActionRepositoryClient) {
|
||||
registerReminderTopicUtilities({
|
||||
bot,
|
||||
householdConfigurationRepository: householdConfigurationRepositoryClient.repository,
|
||||
householdConfigurationRepository: workerHouseholdConfigurationRepositoryClient.repository,
|
||||
promptRepository: telegramPendingActionRepositoryClient.repository,
|
||||
financeServiceForHousehold,
|
||||
logger: getLogger('reminder-utilities')
|
||||
@@ -537,272 +701,272 @@ const server = createBotWebhookServer({
|
||||
webhookPath: runtime.telegramWebhookPath,
|
||||
webhookSecret: runtime.telegramWebhookSecret,
|
||||
webhookHandler,
|
||||
miniAppAuth: householdOnboardingService
|
||||
miniAppAuth: runtime.miniAppAuthEnabled
|
||||
? createMiniAppAuthHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
logger: getLogger('miniapp-auth')
|
||||
})
|
||||
: undefined,
|
||||
miniAppJoin: householdOnboardingService
|
||||
miniAppJoin: runtime.miniAppAuthEnabled
|
||||
? createMiniAppJoinHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
logger: getLogger('miniapp-auth')
|
||||
})
|
||||
: undefined,
|
||||
miniAppDashboard: householdOnboardingService
|
||||
miniAppDashboard: runtime.miniAppAuthEnabled
|
||||
? createMiniAppDashboardHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
financeServiceForHousehold,
|
||||
onboardingService: householdOnboardingService!,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
logger: getLogger('miniapp-dashboard')
|
||||
})
|
||||
: undefined,
|
||||
miniAppPendingMembers: householdOnboardingService
|
||||
miniAppPendingMembers: runtime.miniAppAuthEnabled
|
||||
? createMiniAppPendingMembersHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
miniAppAdminService: miniAppAdminService!,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
miniAppAdminServiceForSession: appMiniAppAdminServiceForSession,
|
||||
logger: getLogger('miniapp-admin')
|
||||
})
|
||||
: undefined,
|
||||
miniAppApproveMember: householdOnboardingService
|
||||
miniAppApproveMember: runtime.miniAppAuthEnabled
|
||||
? createMiniAppApproveMemberHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
miniAppAdminService: miniAppAdminService!,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
miniAppAdminServiceForSession: appMiniAppAdminServiceForSession,
|
||||
logger: getLogger('miniapp-admin')
|
||||
})
|
||||
: undefined,
|
||||
miniAppRejectMember: householdOnboardingService
|
||||
miniAppRejectMember: runtime.miniAppAuthEnabled
|
||||
? createMiniAppRejectMemberHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
miniAppAdminService: miniAppAdminService!,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
miniAppAdminServiceForSession: appMiniAppAdminServiceForSession,
|
||||
logger: getLogger('miniapp-admin')
|
||||
})
|
||||
: undefined,
|
||||
miniAppSettings: householdOnboardingService
|
||||
miniAppSettings: runtime.miniAppAuthEnabled
|
||||
? createMiniAppSettingsHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
miniAppAdminService: miniAppAdminService!,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
miniAppAdminServiceForSession: appMiniAppAdminServiceForSession,
|
||||
assistantUsageTracker,
|
||||
logger: getLogger('miniapp-admin')
|
||||
})
|
||||
: undefined,
|
||||
miniAppUpdateSettings: householdOnboardingService
|
||||
miniAppUpdateSettings: runtime.miniAppAuthEnabled
|
||||
? createMiniAppUpdateSettingsHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
miniAppAdminService: miniAppAdminService!,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
miniAppAdminServiceForSession: appMiniAppAdminServiceForSession,
|
||||
logger: getLogger('miniapp-admin')
|
||||
})
|
||||
: undefined,
|
||||
miniAppUpsertUtilityCategory: householdOnboardingService
|
||||
miniAppUpsertUtilityCategory: runtime.miniAppAuthEnabled
|
||||
? createMiniAppUpsertUtilityCategoryHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
miniAppAdminService: miniAppAdminService!,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
miniAppAdminServiceForSession: appMiniAppAdminServiceForSession,
|
||||
logger: getLogger('miniapp-admin')
|
||||
})
|
||||
: undefined,
|
||||
miniAppPromoteMember: householdOnboardingService
|
||||
miniAppPromoteMember: runtime.miniAppAuthEnabled
|
||||
? createMiniAppPromoteMemberHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
miniAppAdminService: miniAppAdminService!,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
miniAppAdminServiceForSession: appMiniAppAdminServiceForSession,
|
||||
logger: getLogger('miniapp-admin')
|
||||
})
|
||||
: undefined,
|
||||
miniAppUpdateOwnDisplayName: householdOnboardingService
|
||||
miniAppUpdateOwnDisplayName: runtime.miniAppAuthEnabled
|
||||
? createMiniAppUpdateOwnDisplayNameHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
miniAppAdminService: miniAppAdminService!,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
miniAppAdminServiceForSession: appMiniAppAdminServiceForSession,
|
||||
logger: getLogger('miniapp-admin')
|
||||
})
|
||||
: undefined,
|
||||
miniAppUpdateMemberDisplayName: householdOnboardingService
|
||||
miniAppUpdateMemberDisplayName: runtime.miniAppAuthEnabled
|
||||
? createMiniAppUpdateMemberDisplayNameHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
miniAppAdminService: miniAppAdminService!,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
miniAppAdminServiceForSession: appMiniAppAdminServiceForSession,
|
||||
logger: getLogger('miniapp-admin')
|
||||
})
|
||||
: undefined,
|
||||
miniAppUpdateMemberRentWeight: householdOnboardingService
|
||||
miniAppUpdateMemberRentWeight: runtime.miniAppAuthEnabled
|
||||
? createMiniAppUpdateMemberRentWeightHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
miniAppAdminService: miniAppAdminService!,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
miniAppAdminServiceForSession: appMiniAppAdminServiceForSession,
|
||||
logger: getLogger('miniapp-admin')
|
||||
})
|
||||
: undefined,
|
||||
miniAppUpdateMemberStatus: householdOnboardingService
|
||||
miniAppUpdateMemberStatus: runtime.miniAppAuthEnabled
|
||||
? createMiniAppUpdateMemberStatusHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
miniAppAdminService: miniAppAdminService!,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
miniAppAdminServiceForSession: appMiniAppAdminServiceForSession,
|
||||
logger: getLogger('miniapp-admin')
|
||||
})
|
||||
: undefined,
|
||||
miniAppUpdateMemberAbsencePolicy: householdOnboardingService
|
||||
miniAppUpdateMemberAbsencePolicy: runtime.miniAppAuthEnabled
|
||||
? createMiniAppUpdateMemberAbsencePolicyHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
miniAppAdminService: miniAppAdminService!,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
miniAppAdminServiceForSession: appMiniAppAdminServiceForSession,
|
||||
logger: getLogger('miniapp-admin')
|
||||
})
|
||||
: undefined,
|
||||
miniAppBillingCycle: householdOnboardingService
|
||||
miniAppBillingCycle: runtime.miniAppAuthEnabled
|
||||
? createMiniAppBillingCycleHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppOpenCycle: householdOnboardingService
|
||||
miniAppOpenCycle: runtime.miniAppAuthEnabled
|
||||
? createMiniAppOpenCycleHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppCloseCycle: householdOnboardingService
|
||||
miniAppCloseCycle: runtime.miniAppAuthEnabled
|
||||
? createMiniAppCloseCycleHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppRentUpdate: householdOnboardingService
|
||||
miniAppRentUpdate: runtime.miniAppAuthEnabled
|
||||
? createMiniAppRentUpdateHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppAddUtilityBill: householdOnboardingService
|
||||
miniAppAddUtilityBill: runtime.miniAppAuthEnabled
|
||||
? createMiniAppAddUtilityBillHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppSubmitUtilityBill: householdOnboardingService
|
||||
miniAppSubmitUtilityBill: runtime.miniAppAuthEnabled
|
||||
? createMiniAppSubmitUtilityBillHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppUpdateUtilityBill: householdOnboardingService
|
||||
miniAppUpdateUtilityBill: runtime.miniAppAuthEnabled
|
||||
? createMiniAppUpdateUtilityBillHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppDeleteUtilityBill: householdOnboardingService
|
||||
miniAppDeleteUtilityBill: runtime.miniAppAuthEnabled
|
||||
? createMiniAppDeleteUtilityBillHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppAddPurchase: householdOnboardingService
|
||||
miniAppAddPurchase: runtime.miniAppAuthEnabled
|
||||
? createMiniAppAddPurchaseHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppUpdatePurchase: householdOnboardingService
|
||||
miniAppUpdatePurchase: runtime.miniAppAuthEnabled
|
||||
? createMiniAppUpdatePurchaseHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppDeletePurchase: householdOnboardingService
|
||||
miniAppDeletePurchase: runtime.miniAppAuthEnabled
|
||||
? createMiniAppDeletePurchaseHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppAddPayment: householdOnboardingService
|
||||
miniAppAddPayment: runtime.miniAppAuthEnabled
|
||||
? createMiniAppAddPaymentHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppUpdatePayment: householdOnboardingService
|
||||
miniAppUpdatePayment: runtime.miniAppAuthEnabled
|
||||
? createMiniAppUpdatePaymentHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppDeletePayment: householdOnboardingService
|
||||
miniAppDeletePayment: runtime.miniAppAuthEnabled
|
||||
? createMiniAppDeletePaymentHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
financeServiceForHousehold,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
financeServiceForSession: appFinanceServiceForSession,
|
||||
logger: getLogger('miniapp-billing')
|
||||
})
|
||||
: undefined,
|
||||
miniAppLocalePreference: householdOnboardingService
|
||||
miniAppLocalePreference: runtime.miniAppAuthEnabled
|
||||
? createMiniAppLocalePreferenceHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
localePreferenceService: localePreferenceService!,
|
||||
onboardingServiceForTelegramUserId: appOnboardingServiceForTelegramUserId,
|
||||
localePreferenceServiceForSession: appLocalePreferenceServiceForSession,
|
||||
logger: getLogger('miniapp-admin')
|
||||
})
|
||||
: undefined,
|
||||
|
||||
@@ -12,12 +12,59 @@ import type { AssistantUsageTracker } from './dm-assistant'
|
||||
|
||||
import {
|
||||
allowedMiniAppOrigin,
|
||||
type MiniAppAuthorizedSession,
|
||||
createMiniAppSessionService,
|
||||
miniAppErrorResponse,
|
||||
miniAppJsonResponse,
|
||||
readMiniAppRequestPayload
|
||||
} from './miniapp-auth'
|
||||
|
||||
interface MiniAppAdminHandlerBaseOptions {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingServiceForTelegramUserId?: (telegramUserId: string) => HouseholdOnboardingService
|
||||
onboardingService?: HouseholdOnboardingService
|
||||
miniAppAdminServiceForSession?: (session: MiniAppAuthorizedSession) => MiniAppAdminService
|
||||
miniAppAdminService?: MiniAppAdminService
|
||||
logger?: Logger
|
||||
}
|
||||
|
||||
function createConfiguredMiniAppSessionService(options: {
|
||||
botToken: string
|
||||
onboardingServiceForTelegramUserId?: (telegramUserId: string) => HouseholdOnboardingService
|
||||
onboardingService?: HouseholdOnboardingService
|
||||
}) {
|
||||
return createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
...(options.onboardingServiceForTelegramUserId
|
||||
? {
|
||||
onboardingServiceForTelegramUserId: options.onboardingServiceForTelegramUserId
|
||||
}
|
||||
: {}),
|
||||
...(options.onboardingService
|
||||
? {
|
||||
onboardingService: options.onboardingService
|
||||
}
|
||||
: {})
|
||||
})
|
||||
}
|
||||
|
||||
function resolveMiniAppAdminService(
|
||||
options: Pick<
|
||||
MiniAppAdminHandlerBaseOptions,
|
||||
'miniAppAdminServiceForSession' | 'miniAppAdminService'
|
||||
>,
|
||||
session: MiniAppAuthorizedSession
|
||||
): MiniAppAdminService {
|
||||
const service = options.miniAppAdminServiceForSession?.(session) ?? options.miniAppAdminService
|
||||
|
||||
if (!service) {
|
||||
throw new Error('Mini app admin service is not configured')
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
async function readApprovalPayload(request: Request): Promise<{
|
||||
initData: string
|
||||
pendingTelegramUserId: string
|
||||
@@ -401,6 +448,7 @@ async function authenticateAdminSession(
|
||||
| Response
|
||||
| {
|
||||
member: NonNullable<MiniAppSessionResult['member']>
|
||||
telegramUserId: string
|
||||
}
|
||||
> {
|
||||
const payload = await readMiniAppRequestPayload(request)
|
||||
@@ -413,7 +461,7 @@ async function authenticateAdminSession(
|
||||
return miniAppJsonResponse({ ok: false, error: 'Invalid Telegram init data' }, 401, origin)
|
||||
}
|
||||
|
||||
if (!session.authorized || !session.member) {
|
||||
if (!session.authorized || !session.member || !session.telegramUser) {
|
||||
return miniAppJsonResponse(
|
||||
{ ok: false, error: 'Admin access required for active household members' },
|
||||
403,
|
||||
@@ -430,23 +478,15 @@ async function authenticateAdminSession(
|
||||
}
|
||||
|
||||
return {
|
||||
member: session.member
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppPendingMembersHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
miniAppAdminService: MiniAppAdminService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppPendingMembersHandler(options: MiniAppAdminHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -478,6 +518,7 @@ export function createMiniAppPendingMembersHandler(options: {
|
||||
if (
|
||||
!session.authorized ||
|
||||
!session.member ||
|
||||
!session.telegramUser ||
|
||||
session.member.status !== 'active' ||
|
||||
!session.member.isAdmin
|
||||
) {
|
||||
@@ -488,7 +529,10 @@ export function createMiniAppPendingMembersHandler(options: {
|
||||
)
|
||||
}
|
||||
|
||||
const result = await options.miniAppAdminService.listPendingMembers({
|
||||
const result = await resolveMiniAppAdminService(options, {
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}).listPendingMembers({
|
||||
householdId: session.member.householdId,
|
||||
actorIsAdmin: session.member.isAdmin
|
||||
})
|
||||
@@ -513,20 +557,14 @@ export function createMiniAppPendingMembersHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppSettingsHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
miniAppAdminService: MiniAppAdminService
|
||||
export function createMiniAppSettingsHandler(
|
||||
options: MiniAppAdminHandlerBaseOptions & {
|
||||
assistantUsageTracker?: AssistantUsageTracker
|
||||
logger?: Logger
|
||||
}): {
|
||||
}
|
||||
): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -545,9 +583,12 @@ export function createMiniAppSettingsHandler(options: {
|
||||
if (auth instanceof Response) {
|
||||
return auth
|
||||
}
|
||||
const { member } = auth
|
||||
const { member, telegramUserId } = auth
|
||||
|
||||
const result = await options.miniAppAdminService.getSettings({
|
||||
const result = await resolveMiniAppAdminService(options, {
|
||||
member,
|
||||
telegramUserId
|
||||
}).getSettings({
|
||||
householdId: member.householdId,
|
||||
actorIsAdmin: member.isAdmin
|
||||
})
|
||||
@@ -580,19 +621,10 @@ export function createMiniAppSettingsHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppUpdateSettingsHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
miniAppAdminService: MiniAppAdminService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppUpdateSettingsHandler(options: MiniAppAdminHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -623,6 +655,7 @@ export function createMiniAppUpdateSettingsHandler(options: {
|
||||
if (
|
||||
!session.authorized ||
|
||||
!session.member ||
|
||||
!session.telegramUser ||
|
||||
session.member.status !== 'active' ||
|
||||
!session.member.isAdmin
|
||||
) {
|
||||
@@ -633,7 +666,10 @@ export function createMiniAppUpdateSettingsHandler(options: {
|
||||
)
|
||||
}
|
||||
|
||||
const result = await options.miniAppAdminService.updateSettings({
|
||||
const result = await resolveMiniAppAdminService(options, {
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}).updateSettings({
|
||||
householdId: session.member.householdId,
|
||||
actorIsAdmin: session.member.isAdmin,
|
||||
...(payload.householdName !== undefined
|
||||
@@ -715,19 +751,12 @@ export function createMiniAppUpdateSettingsHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppUpsertUtilityCategoryHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
miniAppAdminService: MiniAppAdminService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppUpsertUtilityCategoryHandler(
|
||||
options: MiniAppAdminHandlerBaseOptions
|
||||
): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -758,6 +787,7 @@ export function createMiniAppUpsertUtilityCategoryHandler(options: {
|
||||
if (
|
||||
!session.authorized ||
|
||||
!session.member ||
|
||||
!session.telegramUser ||
|
||||
session.member.status !== 'active' ||
|
||||
!session.member.isAdmin
|
||||
) {
|
||||
@@ -768,7 +798,10 @@ export function createMiniAppUpsertUtilityCategoryHandler(options: {
|
||||
)
|
||||
}
|
||||
|
||||
const result = await options.miniAppAdminService.upsertUtilityCategory({
|
||||
const result = await resolveMiniAppAdminService(options, {
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}).upsertUtilityCategory({
|
||||
householdId: session.member.householdId,
|
||||
actorIsAdmin: session.member.isAdmin,
|
||||
...(payload.slug
|
||||
@@ -811,19 +844,10 @@ export function createMiniAppUpsertUtilityCategoryHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppPromoteMemberHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
miniAppAdminService: MiniAppAdminService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppPromoteMemberHandler(options: MiniAppAdminHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -854,6 +878,7 @@ export function createMiniAppPromoteMemberHandler(options: {
|
||||
if (
|
||||
!session.authorized ||
|
||||
!session.member ||
|
||||
!session.telegramUser ||
|
||||
session.member.status !== 'active' ||
|
||||
!session.member.isAdmin
|
||||
) {
|
||||
@@ -864,7 +889,10 @@ export function createMiniAppPromoteMemberHandler(options: {
|
||||
)
|
||||
}
|
||||
|
||||
const result = await options.miniAppAdminService.promoteMemberToAdmin({
|
||||
const result = await resolveMiniAppAdminService(options, {
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}).promoteMemberToAdmin({
|
||||
householdId: session.member.householdId,
|
||||
actorIsAdmin: session.member.isAdmin,
|
||||
memberId: payload.memberId
|
||||
@@ -898,19 +926,10 @@ export function createMiniAppPromoteMemberHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppUpdateOwnDisplayNameHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
miniAppAdminService: MiniAppAdminService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppUpdateOwnDisplayNameHandler(options: MiniAppAdminHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -930,7 +949,7 @@ export function createMiniAppUpdateOwnDisplayNameHandler(options: {
|
||||
initData: payload.initData
|
||||
})
|
||||
|
||||
if (!session || !session.authorized || !session.member) {
|
||||
if (!session || !session.authorized || !session.member || !session.telegramUser) {
|
||||
return miniAppJsonResponse(
|
||||
{ ok: false, error: 'Active household membership required' },
|
||||
session ? 403 : 401,
|
||||
@@ -938,7 +957,10 @@ export function createMiniAppUpdateOwnDisplayNameHandler(options: {
|
||||
)
|
||||
}
|
||||
|
||||
const result = await options.miniAppAdminService.updateOwnDisplayName({
|
||||
const result = await resolveMiniAppAdminService(options, {
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}).updateOwnDisplayName({
|
||||
householdId: session.member.householdId,
|
||||
actorMemberId: session.member.id,
|
||||
displayName: payload.displayName
|
||||
@@ -974,19 +996,12 @@ export function createMiniAppUpdateOwnDisplayNameHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppUpdateMemberRentWeightHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
miniAppAdminService: MiniAppAdminService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppUpdateMemberRentWeightHandler(
|
||||
options: MiniAppAdminHandlerBaseOptions
|
||||
): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1017,6 +1032,7 @@ export function createMiniAppUpdateMemberRentWeightHandler(options: {
|
||||
if (
|
||||
!session.authorized ||
|
||||
!session.member ||
|
||||
!session.telegramUser ||
|
||||
session.member.status !== 'active' ||
|
||||
!session.member.isAdmin
|
||||
) {
|
||||
@@ -1027,7 +1043,10 @@ export function createMiniAppUpdateMemberRentWeightHandler(options: {
|
||||
)
|
||||
}
|
||||
|
||||
const result = await options.miniAppAdminService.updateMemberRentShareWeight({
|
||||
const result = await resolveMiniAppAdminService(options, {
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}).updateMemberRentShareWeight({
|
||||
householdId: session.member.householdId,
|
||||
actorIsAdmin: session.member.isAdmin,
|
||||
memberId: payload.memberId,
|
||||
@@ -1070,19 +1089,12 @@ export function createMiniAppUpdateMemberRentWeightHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppUpdateMemberDisplayNameHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
miniAppAdminService: MiniAppAdminService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppUpdateMemberDisplayNameHandler(
|
||||
options: MiniAppAdminHandlerBaseOptions
|
||||
): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1106,6 +1118,7 @@ export function createMiniAppUpdateMemberDisplayNameHandler(options: {
|
||||
!session ||
|
||||
!session.authorized ||
|
||||
!session.member ||
|
||||
!session.telegramUser ||
|
||||
session.member.status !== 'active' ||
|
||||
!session.member.isAdmin
|
||||
) {
|
||||
@@ -1120,7 +1133,10 @@ export function createMiniAppUpdateMemberDisplayNameHandler(options: {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Missing memberId' }, 400, origin)
|
||||
}
|
||||
|
||||
const result = await options.miniAppAdminService.updateMemberDisplayName({
|
||||
const result = await resolveMiniAppAdminService(options, {
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}).updateMemberDisplayName({
|
||||
householdId: session.member.householdId,
|
||||
actorIsAdmin: session.member.isAdmin,
|
||||
memberId: payload.memberId,
|
||||
@@ -1163,19 +1179,10 @@ export function createMiniAppUpdateMemberDisplayNameHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppUpdateMemberStatusHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
miniAppAdminService: MiniAppAdminService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppUpdateMemberStatusHandler(options: MiniAppAdminHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1199,6 +1206,7 @@ export function createMiniAppUpdateMemberStatusHandler(options: {
|
||||
!session ||
|
||||
!session.authorized ||
|
||||
!session.member ||
|
||||
!session.telegramUser ||
|
||||
session.member.status !== 'active' ||
|
||||
!session.member.isAdmin
|
||||
) {
|
||||
@@ -1209,7 +1217,10 @@ export function createMiniAppUpdateMemberStatusHandler(options: {
|
||||
)
|
||||
}
|
||||
|
||||
const result = await options.miniAppAdminService.updateMemberStatus({
|
||||
const result = await resolveMiniAppAdminService(options, {
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}).updateMemberStatus({
|
||||
householdId: session.member.householdId,
|
||||
actorIsAdmin: session.member.isAdmin,
|
||||
memberId: payload.memberId,
|
||||
@@ -1244,19 +1255,12 @@ export function createMiniAppUpdateMemberStatusHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppUpdateMemberAbsencePolicyHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
miniAppAdminService: MiniAppAdminService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppUpdateMemberAbsencePolicyHandler(
|
||||
options: MiniAppAdminHandlerBaseOptions
|
||||
): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1280,6 +1284,7 @@ export function createMiniAppUpdateMemberAbsencePolicyHandler(options: {
|
||||
!session ||
|
||||
!session.authorized ||
|
||||
!session.member ||
|
||||
!session.telegramUser ||
|
||||
session.member.status !== 'active' ||
|
||||
!session.member.isAdmin
|
||||
) {
|
||||
@@ -1290,7 +1295,10 @@ export function createMiniAppUpdateMemberAbsencePolicyHandler(options: {
|
||||
)
|
||||
}
|
||||
|
||||
const result = await options.miniAppAdminService.updateMemberAbsencePolicy({
|
||||
const result = await resolveMiniAppAdminService(options, {
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}).updateMemberAbsencePolicy({
|
||||
householdId: session.member.householdId,
|
||||
actorIsAdmin: session.member.isAdmin,
|
||||
memberId: payload.memberId,
|
||||
@@ -1325,19 +1333,10 @@ export function createMiniAppUpdateMemberAbsencePolicyHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppApproveMemberHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
miniAppAdminService: MiniAppAdminService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppApproveMemberHandler(options: MiniAppAdminHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1368,6 +1367,7 @@ export function createMiniAppApproveMemberHandler(options: {
|
||||
if (
|
||||
!session.authorized ||
|
||||
!session.member ||
|
||||
!session.telegramUser ||
|
||||
session.member.status !== 'active' ||
|
||||
!session.member.isAdmin
|
||||
) {
|
||||
@@ -1378,7 +1378,10 @@ export function createMiniAppApproveMemberHandler(options: {
|
||||
)
|
||||
}
|
||||
|
||||
const result = await options.miniAppAdminService.approvePendingMember({
|
||||
const result = await resolveMiniAppAdminService(options, {
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}).approvePendingMember({
|
||||
householdId: session.member.householdId,
|
||||
actorIsAdmin: session.member.isAdmin,
|
||||
pendingTelegramUserId: payload.pendingTelegramUserId
|
||||
@@ -1410,19 +1413,10 @@ export function createMiniAppApproveMemberHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppRejectMemberHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
miniAppAdminService: MiniAppAdminService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppRejectMemberHandler(options: MiniAppAdminHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1453,6 +1447,7 @@ export function createMiniAppRejectMemberHandler(options: {
|
||||
if (
|
||||
!session.authorized ||
|
||||
!session.member ||
|
||||
!session.telegramUser ||
|
||||
session.member.status !== 'active' ||
|
||||
!session.member.isAdmin
|
||||
) {
|
||||
@@ -1463,7 +1458,10 @@ export function createMiniAppRejectMemberHandler(options: {
|
||||
)
|
||||
}
|
||||
|
||||
const result = await options.miniAppAdminService.rejectPendingMember({
|
||||
const result = await resolveMiniAppAdminService(options, {
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}).rejectPendingMember({
|
||||
householdId: session.member.householdId,
|
||||
actorIsAdmin: session.member.isAdmin,
|
||||
pendingTelegramUserId: payload.pendingTelegramUserId
|
||||
|
||||
@@ -115,12 +115,28 @@ export interface MiniAppSessionResult {
|
||||
}
|
||||
}
|
||||
|
||||
export interface MiniAppAuthorizedSession {
|
||||
member: NonNullable<MiniAppSessionResult['member']>
|
||||
telegramUserId: string
|
||||
}
|
||||
|
||||
export function createMiniAppSessionService(options: {
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
onboardingServiceForTelegramUserId?: (telegramUserId: string) => HouseholdOnboardingService
|
||||
onboardingService?: HouseholdOnboardingService
|
||||
}): {
|
||||
authenticate: (payload: MiniAppRequestPayload) => Promise<MiniAppSessionResult | null>
|
||||
} {
|
||||
const resolveOnboardingService =
|
||||
options.onboardingServiceForTelegramUserId ??
|
||||
(() => {
|
||||
if (!options.onboardingService) {
|
||||
throw new Error('Mini app onboarding service is not configured')
|
||||
}
|
||||
|
||||
return options.onboardingService
|
||||
})
|
||||
|
||||
return {
|
||||
authenticate: async (payload) => {
|
||||
if (!payload.initData) {
|
||||
@@ -132,7 +148,7 @@ export function createMiniAppSessionService(options: {
|
||||
return null
|
||||
}
|
||||
|
||||
const access = await options.onboardingService.getMiniAppAccess({
|
||||
const access = await resolveOnboardingService(telegramUser.id).getMiniAppAccess({
|
||||
identity: {
|
||||
telegramUserId: telegramUser.id,
|
||||
displayName:
|
||||
@@ -190,14 +206,24 @@ export function createMiniAppSessionService(options: {
|
||||
export function createMiniAppAuthHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
onboardingServiceForTelegramUserId?: (telegramUserId: string) => HouseholdOnboardingService
|
||||
onboardingService?: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
...(options.onboardingServiceForTelegramUserId
|
||||
? {
|
||||
onboardingServiceForTelegramUserId: options.onboardingServiceForTelegramUserId
|
||||
}
|
||||
: {}),
|
||||
...(options.onboardingService
|
||||
? {
|
||||
onboardingService: options.onboardingService
|
||||
}
|
||||
: {})
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -264,7 +290,8 @@ export function createMiniAppAuthHandler(options: {
|
||||
export function createMiniAppJoinHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
onboardingServiceForTelegramUserId?: (telegramUserId: string) => HouseholdOnboardingService
|
||||
onboardingService?: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
@@ -304,7 +331,13 @@ export function createMiniAppJoinHandler(options: {
|
||||
)
|
||||
}
|
||||
|
||||
const result = await options.onboardingService.joinHousehold({
|
||||
const onboardingService =
|
||||
options.onboardingServiceForTelegramUserId?.(telegramUser.id) ?? options.onboardingService
|
||||
if (!onboardingService) {
|
||||
throw new Error('Mini app onboarding service is not configured')
|
||||
}
|
||||
|
||||
const result = await onboardingService.joinHousehold({
|
||||
identity: {
|
||||
telegramUserId: telegramUser.id,
|
||||
displayName:
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { FinanceCommandService, HouseholdOnboardingService } from '@househo
|
||||
import { BillingPeriod } from '@household/domain'
|
||||
import type { Logger } from '@household/observability'
|
||||
import type { HouseholdConfigurationRepository } from '@household/ports'
|
||||
import type { MiniAppSessionResult } from './miniapp-auth'
|
||||
import type { MiniAppAuthorizedSession, MiniAppSessionResult } from './miniapp-auth'
|
||||
|
||||
import {
|
||||
allowedMiniAppOrigin,
|
||||
@@ -12,6 +12,54 @@ import {
|
||||
readMiniAppRequestPayload
|
||||
} from './miniapp-auth'
|
||||
|
||||
interface MiniAppBillingHandlerBaseOptions {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForSession?: (session: MiniAppAuthorizedSession) => FinanceCommandService
|
||||
financeServiceForHousehold?: (householdId: string) => FinanceCommandService
|
||||
onboardingServiceForTelegramUserId?: (telegramUserId: string) => HouseholdOnboardingService
|
||||
onboardingService?: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}
|
||||
|
||||
function createConfiguredMiniAppBillingSessionService(options: {
|
||||
botToken: string
|
||||
onboardingServiceForTelegramUserId?: (telegramUserId: string) => HouseholdOnboardingService
|
||||
onboardingService?: HouseholdOnboardingService
|
||||
}) {
|
||||
return createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
...(options.onboardingServiceForTelegramUserId
|
||||
? {
|
||||
onboardingServiceForTelegramUserId: options.onboardingServiceForTelegramUserId
|
||||
}
|
||||
: {}),
|
||||
...(options.onboardingService
|
||||
? {
|
||||
onboardingService: options.onboardingService
|
||||
}
|
||||
: {})
|
||||
})
|
||||
}
|
||||
|
||||
function resolveFinanceService(
|
||||
options: Pick<
|
||||
MiniAppBillingHandlerBaseOptions,
|
||||
'financeServiceForSession' | 'financeServiceForHousehold'
|
||||
>,
|
||||
session: MiniAppAuthorizedSession
|
||||
): FinanceCommandService {
|
||||
const service =
|
||||
options.financeServiceForSession?.(session) ??
|
||||
options.financeServiceForHousehold?.(session.member.householdId)
|
||||
|
||||
if (!service) {
|
||||
throw new Error('Mini app finance service is not configured')
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
function serializeCycleState(
|
||||
state: Awaited<ReturnType<FinanceCommandService['getAdminCycleState']>>
|
||||
) {
|
||||
@@ -42,6 +90,7 @@ async function authenticateAdminSession(
|
||||
| Response
|
||||
| {
|
||||
member: NonNullable<MiniAppSessionResult['member']>
|
||||
telegramUserId: string
|
||||
}
|
||||
> {
|
||||
const payload = await readMiniAppRequestPayload(request)
|
||||
@@ -54,7 +103,7 @@ async function authenticateAdminSession(
|
||||
return miniAppJsonResponse({ ok: false, error: 'Invalid Telegram init data' }, 401, origin)
|
||||
}
|
||||
|
||||
if (!session.authorized || !session.member) {
|
||||
if (!session.authorized || !session.member || !session.telegramUser) {
|
||||
return miniAppJsonResponse(
|
||||
{ ok: false, error: 'Access limited to active household members' },
|
||||
403,
|
||||
@@ -67,7 +116,8 @@ async function authenticateAdminSession(
|
||||
}
|
||||
|
||||
return {
|
||||
member: session.member
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +129,7 @@ async function authenticateMemberSession(
|
||||
| Response
|
||||
| {
|
||||
member: NonNullable<MiniAppSessionResult['member']>
|
||||
telegramUserId: string
|
||||
}
|
||||
> {
|
||||
const payload = await readMiniAppRequestPayload(request)
|
||||
@@ -91,7 +142,12 @@ async function authenticateMemberSession(
|
||||
return miniAppJsonResponse({ ok: false, error: 'Invalid Telegram init data' }, 401, origin)
|
||||
}
|
||||
|
||||
if (!session.authorized || !session.member || session.member.status !== 'active') {
|
||||
if (
|
||||
!session.authorized ||
|
||||
!session.member ||
|
||||
!session.telegramUser ||
|
||||
session.member.status !== 'active'
|
||||
) {
|
||||
return miniAppJsonResponse(
|
||||
{ ok: false, error: 'Access limited to active household members' },
|
||||
403,
|
||||
@@ -100,7 +156,8 @@ async function authenticateMemberSession(
|
||||
}
|
||||
|
||||
return {
|
||||
member: session.member
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,19 +587,10 @@ async function readPaymentMutationPayload(request: Request): Promise<{
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppBillingCycleHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppBillingCycleHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -567,9 +615,9 @@ export function createMiniAppBillingCycleHandler(options: {
|
||||
}
|
||||
|
||||
const payload = await readCycleQueryPayload(request)
|
||||
const cycleState = await options
|
||||
.financeServiceForHousehold(auth.member.householdId)
|
||||
.getAdminCycleState(payload.period)
|
||||
const cycleState = await resolveFinanceService(options, auth).getAdminCycleState(
|
||||
payload.period
|
||||
)
|
||||
|
||||
return miniAppJsonResponse(
|
||||
{
|
||||
@@ -587,19 +635,10 @@ export function createMiniAppBillingCycleHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppOpenCycleHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppOpenCycleHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -624,7 +663,7 @@ export function createMiniAppOpenCycleHandler(options: {
|
||||
}
|
||||
|
||||
const payload = await readOpenCyclePayload(request)
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
await service.openCycle(payload.period, payload.currency)
|
||||
const cycleState = await service.getAdminCycleState(payload.period)
|
||||
|
||||
@@ -644,19 +683,10 @@ export function createMiniAppOpenCycleHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppCloseCycleHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppCloseCycleHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -681,7 +711,7 @@ export function createMiniAppCloseCycleHandler(options: {
|
||||
}
|
||||
|
||||
const payload = await readCycleQueryPayload(request)
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
await service.closeCycle(payload.period)
|
||||
const cycleState = await service.getAdminCycleState()
|
||||
|
||||
@@ -701,19 +731,10 @@ export function createMiniAppCloseCycleHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppRentUpdateHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppRentUpdateHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -738,7 +759,7 @@ export function createMiniAppRentUpdateHandler(options: {
|
||||
}
|
||||
|
||||
const payload = await readRentUpdatePayload(request)
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
const result = await service.setRent(payload.amountMajor, payload.currency, payload.period)
|
||||
if (!result) {
|
||||
return miniAppJsonResponse(
|
||||
@@ -766,19 +787,10 @@ export function createMiniAppRentUpdateHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppAddUtilityBillHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppAddUtilityBillHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -803,7 +815,7 @@ export function createMiniAppAddUtilityBillHandler(options: {
|
||||
}
|
||||
|
||||
const payload = await readUtilityBillPayload(request)
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
const result = await service.addUtilityBill(
|
||||
payload.billName,
|
||||
payload.amountMajor,
|
||||
@@ -837,19 +849,10 @@ export function createMiniAppAddUtilityBillHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppSubmitUtilityBillHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppSubmitUtilityBillHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -874,7 +877,7 @@ export function createMiniAppSubmitUtilityBillHandler(options: {
|
||||
}
|
||||
|
||||
const payload = await readUtilityBillPayload(request)
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
const result = await service.addUtilityBill(
|
||||
payload.billName,
|
||||
payload.amountMajor,
|
||||
@@ -905,35 +908,33 @@ export function createMiniAppSubmitUtilityBillHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppSubmitPaymentHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
householdConfigurationRepository: HouseholdConfigurationRepository
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppSubmitPaymentHandler(
|
||||
options: MiniAppBillingHandlerBaseOptions & {
|
||||
householdConfigurationRepositoryForSession: (
|
||||
session: MiniAppAuthorizedSession
|
||||
) => HouseholdConfigurationRepository
|
||||
}
|
||||
): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
async function notifyPaymentRecorded(input: {
|
||||
async function notifyPaymentRecorded(
|
||||
session: MiniAppAuthorizedSession,
|
||||
input: {
|
||||
householdId: string
|
||||
memberName: string
|
||||
kind: 'rent' | 'utilities'
|
||||
amountMajor: string
|
||||
currency: string
|
||||
period: string
|
||||
}) {
|
||||
}
|
||||
) {
|
||||
const householdConfigurationRepository =
|
||||
options.householdConfigurationRepositoryForSession(session)
|
||||
const [chat, topic] = await Promise.all([
|
||||
options.householdConfigurationRepository.getHouseholdChatByHouseholdId(input.householdId),
|
||||
options.householdConfigurationRepository.getHouseholdTopicBinding(
|
||||
input.householdId,
|
||||
'reminders'
|
||||
)
|
||||
householdConfigurationRepository.getHouseholdChatByHouseholdId(input.householdId),
|
||||
householdConfigurationRepository.getHouseholdTopicBinding(input.householdId, 'reminders')
|
||||
])
|
||||
|
||||
if (!chat || !topic) {
|
||||
@@ -996,7 +997,7 @@ export function createMiniAppSubmitPaymentHandler(options: {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Missing payment fields' }, 400, origin)
|
||||
}
|
||||
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
const payment = await service.addPayment(
|
||||
auth.member.id,
|
||||
payload.kind,
|
||||
@@ -1008,7 +1009,7 @@ export function createMiniAppSubmitPaymentHandler(options: {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Failed to record payment' }, 500, origin)
|
||||
}
|
||||
|
||||
await notifyPaymentRecorded({
|
||||
await notifyPaymentRecorded(auth, {
|
||||
householdId: auth.member.householdId,
|
||||
memberName: auth.member.displayName,
|
||||
kind: payload.kind,
|
||||
@@ -1032,19 +1033,10 @@ export function createMiniAppSubmitPaymentHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppUpdateUtilityBillHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppUpdateUtilityBillHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1069,7 +1061,7 @@ export function createMiniAppUpdateUtilityBillHandler(options: {
|
||||
}
|
||||
|
||||
const payload = await readUtilityBillUpdatePayload(request)
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
const result = await service.updateUtilityBill(
|
||||
payload.billId,
|
||||
payload.billName,
|
||||
@@ -1099,19 +1091,10 @@ export function createMiniAppUpdateUtilityBillHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppDeleteUtilityBillHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppDeleteUtilityBillHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1136,7 +1119,7 @@ export function createMiniAppDeleteUtilityBillHandler(options: {
|
||||
}
|
||||
|
||||
const payload = await readUtilityBillDeletePayload(request)
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
const deleted = await service.deleteUtilityBill(payload.billId)
|
||||
|
||||
if (!deleted) {
|
||||
@@ -1161,19 +1144,10 @@ export function createMiniAppDeleteUtilityBillHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppAddPurchaseHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppAddPurchaseHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1200,7 +1174,7 @@ export function createMiniAppAddPurchaseHandler(options: {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Missing purchase fields' }, 400, origin)
|
||||
}
|
||||
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
const payerMemberId = payload.payerMemberId ?? auth.member.id
|
||||
await service.addPurchase(
|
||||
payload.description,
|
||||
@@ -1218,19 +1192,10 @@ export function createMiniAppAddPurchaseHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppUpdatePurchaseHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppUpdatePurchaseHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1257,7 +1222,7 @@ export function createMiniAppUpdatePurchaseHandler(options: {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Missing purchase fields' }, 400, origin)
|
||||
}
|
||||
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
const payerMemberId = payload.payerMemberId
|
||||
const updated = await service.updatePurchase(
|
||||
payload.purchaseId,
|
||||
@@ -1280,19 +1245,10 @@ export function createMiniAppUpdatePurchaseHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppDeletePurchaseHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppDeletePurchaseHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1315,7 +1271,7 @@ export function createMiniAppDeletePurchaseHandler(options: {
|
||||
}
|
||||
|
||||
const payload = await readPurchaseMutationPayload(request)
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
const deleted = await service.deletePurchase(payload.purchaseId)
|
||||
|
||||
if (!deleted) {
|
||||
@@ -1330,19 +1286,10 @@ export function createMiniAppDeletePurchaseHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppAddPaymentHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppAddPaymentHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1369,7 +1316,7 @@ export function createMiniAppAddPaymentHandler(options: {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Missing payment fields' }, 400, origin)
|
||||
}
|
||||
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
const payment = await service.addPayment(
|
||||
payload.memberId,
|
||||
payload.kind,
|
||||
@@ -1389,19 +1336,10 @@ export function createMiniAppAddPaymentHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppUpdatePaymentHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppUpdatePaymentHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1428,7 +1366,7 @@ export function createMiniAppUpdatePaymentHandler(options: {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Missing payment fields' }, 400, origin)
|
||||
}
|
||||
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
const payment = await service.updatePayment(
|
||||
payload.paymentId,
|
||||
payload.memberId,
|
||||
@@ -1449,19 +1387,10 @@ export function createMiniAppUpdatePaymentHandler(options: {
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppDeletePaymentHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
export function createMiniAppDeletePaymentHandler(options: MiniAppBillingHandlerBaseOptions): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
const sessionService = createConfiguredMiniAppBillingSessionService(options)
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
@@ -1488,7 +1417,7 @@ export function createMiniAppDeletePaymentHandler(options: {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Missing payment id' }, 400, origin)
|
||||
}
|
||||
|
||||
const service = options.financeServiceForHousehold(auth.member.householdId)
|
||||
const service = resolveFinanceService(options, auth)
|
||||
const deleted = await service.deletePayment(payload.paymentId)
|
||||
|
||||
if (!deleted) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { Logger } from '@household/observability'
|
||||
|
||||
import {
|
||||
allowedMiniAppOrigin,
|
||||
type MiniAppAuthorizedSession,
|
||||
createMiniAppSessionService,
|
||||
miniAppErrorResponse,
|
||||
miniAppJsonResponse,
|
||||
@@ -12,15 +13,26 @@ import {
|
||||
export function createMiniAppDashboardHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
financeServiceForSession?: (session: MiniAppAuthorizedSession) => FinanceCommandService
|
||||
financeServiceForHousehold?: (householdId: string) => FinanceCommandService
|
||||
onboardingServiceForTelegramUserId?: (telegramUserId: string) => HouseholdOnboardingService
|
||||
onboardingService?: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
...(options.onboardingServiceForTelegramUserId
|
||||
? {
|
||||
onboardingServiceForTelegramUserId: options.onboardingServiceForTelegramUserId
|
||||
}
|
||||
: {}),
|
||||
...(options.onboardingService
|
||||
? {
|
||||
onboardingService: options.onboardingService
|
||||
}
|
||||
: {})
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -62,7 +74,7 @@ export function createMiniAppDashboardHandler(options: {
|
||||
)
|
||||
}
|
||||
|
||||
if (!session.member) {
|
||||
if (!session.member || !session.telegramUser) {
|
||||
return miniAppJsonResponse(
|
||||
{ ok: false, error: 'Authenticated session is missing member context' },
|
||||
500,
|
||||
@@ -70,9 +82,17 @@ export function createMiniAppDashboardHandler(options: {
|
||||
)
|
||||
}
|
||||
|
||||
const dashboard = await options
|
||||
.financeServiceForHousehold(session.member.householdId)
|
||||
.generateDashboard()
|
||||
const financeService =
|
||||
options.financeServiceForSession?.({
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}) ?? options.financeServiceForHousehold?.(session.member.householdId)
|
||||
|
||||
if (!financeService) {
|
||||
throw new Error('Mini app finance service is not configured')
|
||||
}
|
||||
|
||||
const dashboard = await financeService.generateDashboard()
|
||||
if (!dashboard) {
|
||||
return miniAppJsonResponse(
|
||||
{ ok: false, error: 'No billing cycle available' },
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { Logger } from '@household/observability'
|
||||
|
||||
import {
|
||||
allowedMiniAppOrigin,
|
||||
type MiniAppAuthorizedSession,
|
||||
createMiniAppSessionService,
|
||||
miniAppErrorResponse,
|
||||
miniAppJsonResponse
|
||||
@@ -53,15 +54,26 @@ async function readLocalePreferenceRequest(request: Request): Promise<LocalePref
|
||||
export function createMiniAppLocalePreferenceHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
localePreferenceService: LocalePreferenceService
|
||||
onboardingServiceForTelegramUserId?: (telegramUserId: string) => HouseholdOnboardingService
|
||||
onboardingService?: HouseholdOnboardingService
|
||||
localePreferenceServiceForSession?: (session: MiniAppAuthorizedSession) => LocalePreferenceService
|
||||
localePreferenceService?: LocalePreferenceService
|
||||
logger?: Logger
|
||||
}): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
...(options.onboardingServiceForTelegramUserId
|
||||
? {
|
||||
onboardingServiceForTelegramUserId: options.onboardingServiceForTelegramUserId
|
||||
}
|
||||
: {}),
|
||||
...(options.onboardingService
|
||||
? {
|
||||
onboardingService: options.onboardingService
|
||||
}
|
||||
: {})
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -100,9 +112,18 @@ export function createMiniAppLocalePreferenceHandler(options: {
|
||||
|
||||
let memberPreferredLocale = session.member.preferredLocale
|
||||
let householdDefaultLocale = session.member.householdDefaultLocale
|
||||
const localePreferenceService =
|
||||
options.localePreferenceServiceForSession?.({
|
||||
member: session.member,
|
||||
telegramUserId: session.telegramUser.id
|
||||
}) ?? options.localePreferenceService
|
||||
|
||||
if (!localePreferenceService) {
|
||||
throw new Error('Mini app locale preference service is not configured')
|
||||
}
|
||||
|
||||
if (payload.scope === 'member') {
|
||||
const result = await options.localePreferenceService.updateMemberLocale({
|
||||
const result = await localePreferenceService.updateMemberLocale({
|
||||
householdId: session.member.householdId,
|
||||
telegramUserId: session.telegramUser.id,
|
||||
locale: payload.locale
|
||||
@@ -115,7 +136,7 @@ export function createMiniAppLocalePreferenceHandler(options: {
|
||||
memberPreferredLocale = result.member.preferredLocale
|
||||
householdDefaultLocale = result.member.householdDefaultLocale
|
||||
} else {
|
||||
const result = await options.localePreferenceService.updateHouseholdLocale({
|
||||
const result = await localePreferenceService.updateHouseholdLocale({
|
||||
householdId: session.member.householdId,
|
||||
actorIsAdmin: session.member.isAdmin,
|
||||
locale: payload.locale
|
||||
|
||||
@@ -64,12 +64,13 @@ codex review --base origin/main
|
||||
- Copy `.env.example` to `.env` before running app/database commands.
|
||||
- `bun run db:seed` refreshes the committed fixture household and is destructive for previously seeded fixture rows.
|
||||
- Local bot feature flags come from env presence:
|
||||
- finance commands require `DATABASE_URL` plus household setup in Telegram via `/setup`
|
||||
- purchase ingestion requires `DATABASE_URL` plus a bound purchase topic via `/bind_purchase_topic`
|
||||
- anonymous feedback requires `DATABASE_URL` plus a bound feedback topic via `/bind_feedback_topic`
|
||||
- reminders require `DATABASE_URL` plus `SCHEDULER_SHARED_SECRET` or `SCHEDULER_OIDC_ALLOWED_EMAILS`
|
||||
- mini app auth and mini app API routes require `APP_DATABASE_URL`
|
||||
- finance commands require `WORKER_DATABASE_URL` plus household setup in Telegram via `/setup`
|
||||
- purchase ingestion requires `WORKER_DATABASE_URL` plus a bound purchase topic via `/bind_purchase_topic`
|
||||
- anonymous feedback requires `WORKER_DATABASE_URL` plus a bound feedback topic via `/bind_feedback_topic`
|
||||
- reminders require `WORKER_DATABASE_URL` plus `SCHEDULER_SHARED_SECRET` or `SCHEDULER_OIDC_ALLOWED_EMAILS`
|
||||
and optionally use a dedicated reminders topic via `/bind_reminders_topic`
|
||||
- mini app CORS can be constrained with `MINI_APP_ALLOWED_ORIGINS`
|
||||
- mini app CORS must be set explicitly with `MINI_APP_ALLOWED_ORIGINS`
|
||||
- Migration workflow is documented in `docs/runbooks/migrations.md`.
|
||||
- Destructive dev reset guidance is documented in `docs/runbooks/dev-reset.md`.
|
||||
- First deploy flow is documented in `docs/runbooks/first-deploy.md`.
|
||||
|
||||
@@ -12,7 +12,10 @@ Execute the first real deployment with a repeatable sequence that covers infrast
|
||||
- GCP project
|
||||
- GitHub repo settings
|
||||
- Telegram bot token
|
||||
- Supabase project and database URL
|
||||
- Supabase project and three database URLs:
|
||||
- owner `DATABASE_URL` for migrations only
|
||||
- `APP_DATABASE_URL` for authenticated request paths
|
||||
- `WORKER_DATABASE_URL` for bot and scheduler workers
|
||||
|
||||
## Required Configuration Inventory
|
||||
|
||||
@@ -28,7 +31,8 @@ Required in your environment `*.tfvars`:
|
||||
|
||||
Recommended:
|
||||
|
||||
- `database_url_secret_id = "database-url"`
|
||||
- `app_database_url_secret_id = "app-database-url"`
|
||||
- `worker_database_url_secret_id = "worker-database-url"`
|
||||
- `telegram_bot_token_secret_id = "telegram-bot-token"`
|
||||
- `openai_api_key_secret_id = "openai-api-key"`
|
||||
- `bot_mini_app_allowed_origins`
|
||||
@@ -46,10 +50,9 @@ Create the secret resources via Terraform, then add secret versions for:
|
||||
- `telegram-bot-token`
|
||||
- `telegram-webhook-secret`
|
||||
- `scheduler-shared-secret`
|
||||
- `database-url`
|
||||
- `app-database-url`
|
||||
- `worker-database-url`
|
||||
- optional `openai-api-key`
|
||||
- optional `supabase-url`
|
||||
- optional `supabase-publishable-key`
|
||||
|
||||
### GitHub Actions secrets
|
||||
|
||||
@@ -129,15 +132,19 @@ Use the real project ID from Terraform variables:
|
||||
echo -n "<telegram-bot-token>" | gcloud secrets versions add telegram-bot-token --data-file=- --project <project_id>
|
||||
echo -n "<telegram-webhook-secret>" | gcloud secrets versions add telegram-webhook-secret --data-file=- --project <project_id>
|
||||
echo -n "<scheduler-shared-secret>" | gcloud secrets versions add scheduler-shared-secret --data-file=- --project <project_id>
|
||||
echo -n "<database-url>" | gcloud secrets versions add database-url --data-file=- --project <project_id>
|
||||
echo -n "<app-database-url>" | gcloud secrets versions add app-database-url --data-file=- --project <project_id>
|
||||
echo -n "<worker-database-url>" | gcloud secrets versions add worker-database-url --data-file=- --project <project_id>
|
||||
```
|
||||
|
||||
Add optional secret versions only if those integrations are enabled.
|
||||
|
||||
For a functional household dev deployment, set `database_url_secret_id = "database-url"` in
|
||||
`dev.tfvars` before the apply that creates the Cloud Run services. Otherwise the bot deploys
|
||||
without `DATABASE_URL`, and finance commands, reminders, mini app auth/dashboard, and anonymous
|
||||
feedback remain disabled.
|
||||
For a functional household deployment, set both `app_database_url_secret_id` and
|
||||
`worker_database_url_secret_id` in `dev.tfvars` before the apply that creates the Cloud Run
|
||||
services. Otherwise the bot deploys without `APP_DATABASE_URL` and `WORKER_DATABASE_URL`, and mini
|
||||
app auth, finance commands, reminders, purchase ingestion, and anonymous feedback remain disabled.
|
||||
|
||||
Keep `DATABASE_URL` out of normal runtime secrets. It is only required in GitHub Actions for the
|
||||
migration step that runs before deploy.
|
||||
|
||||
Keep `telegram_bot_token_secret_id = "telegram-bot-token"` aligned with the actual bot token
|
||||
secret name. CD uses that secret to sync the Telegram command menu after deploy.
|
||||
@@ -218,6 +225,9 @@ The smoke script verifies:
|
||||
- scheduler endpoint rejects unauthenticated requests
|
||||
- Telegram webhook matches the expected URL when bot token is provided
|
||||
|
||||
Production deploys should also set `MINI_APP_ALLOWED_ORIGINS` explicitly. The browser path remains
|
||||
bot API only; there is no supported direct browser access to Supabase.
|
||||
|
||||
## Phase 8: Scheduler Enablement
|
||||
|
||||
First release:
|
||||
|
||||
@@ -47,9 +47,16 @@ bun run build
|
||||
|
||||
## CD behavior
|
||||
|
||||
- CD deploy runs migrations before deploy and now requires the `DATABASE_URL` GitHub secret.
|
||||
- CD deploy runs migrations before deploy and requires the owner-only `DATABASE_URL` GitHub secret.
|
||||
- If `DATABASE_URL` is missing, CD fails fast instead of deploying schema-dependent code without migrations.
|
||||
|
||||
## Runtime connection split
|
||||
|
||||
- `DATABASE_URL` is for migrations, schema checks, and other owner-only maintenance tasks.
|
||||
- `APP_DATABASE_URL` is for authenticated request paths such as mini app routes.
|
||||
- `WORKER_DATABASE_URL` is for Telegram ingestion, reminders, scheduler jobs, and other internal worker flows.
|
||||
- Runtime services should not use `DATABASE_URL`.
|
||||
|
||||
## Safety rules
|
||||
|
||||
- Prefer additive migrations first (new columns/tables) over destructive changes.
|
||||
|
||||
@@ -29,8 +29,8 @@ Define a reproducible GCP infrastructure baseline for deployment of the bot API
|
||||
- Bot runtime reads secret-backed env vars:
|
||||
- `TELEGRAM_WEBHOOK_SECRET`
|
||||
- `SCHEDULER_SHARED_SECRET`
|
||||
- `SUPABASE_URL` (optional)
|
||||
- `SUPABASE_PUBLISHABLE_KEY` (optional)
|
||||
- `APP_DATABASE_URL` (optional)
|
||||
- `WORKER_DATABASE_URL` (optional)
|
||||
|
||||
## Domain Rules
|
||||
|
||||
|
||||
@@ -58,8 +58,9 @@ echo -n "<value>" | gcloud secrets versions add telegram-webhook-secret --data-f
|
||||
echo -n "<value>" | gcloud secrets versions add scheduler-shared-secret --data-file=- --project <project_id>
|
||||
```
|
||||
|
||||
If you configure optional secret IDs such as `database_url_secret_id` or
|
||||
`openai_api_key_secret_id`, add versions for those secrets as well.
|
||||
If you configure optional secret IDs such as `app_database_url_secret_id`,
|
||||
`worker_database_url_secret_id`, or `openai_api_key_secret_id`, add versions for those secrets as
|
||||
well.
|
||||
|
||||
If GitHub OIDC deploy access is enabled, keep `telegram_bot_token_secret_id` aligned with the
|
||||
real bot token secret name so CD can read it and sync Telegram commands automatically.
|
||||
@@ -84,6 +85,9 @@ Recommended approach:
|
||||
`bot_assistant_rate_limit_rolling_window_ms`
|
||||
- optional `bot_mini_app_allowed_origins`
|
||||
- optional `alert_notification_emails`
|
||||
- runtime DB URLs should stay split:
|
||||
`APP_DATABASE_URL` for authenticated request flows and `WORKER_DATABASE_URL` for background
|
||||
workers
|
||||
|
||||
## Alerting baseline
|
||||
|
||||
@@ -115,3 +119,4 @@ CI runs:
|
||||
- Scheduler jobs default to `paused = true` and `dry_run = true` to prevent accidental sends before live reminder delivery is ready.
|
||||
- Bot API is public to accept Telegram webhooks; scheduler endpoint should still verify app-level auth.
|
||||
- `bot_mini_app_allowed_origins` cannot be auto-derived in Terraform because the bot and mini app Cloud Run services reference each other; set it explicitly once the mini app URL is known.
|
||||
- `DATABASE_URL` is migration-only and should not be injected into the bot runtime service.
|
||||
|
||||
@@ -30,7 +30,8 @@ locals {
|
||||
runtime_secret_ids = toset(compact([
|
||||
var.telegram_webhook_secret_id,
|
||||
var.scheduler_shared_secret_id,
|
||||
var.database_url_secret_id,
|
||||
var.app_database_url_secret_id,
|
||||
var.worker_database_url_secret_id,
|
||||
var.telegram_bot_token_secret_id,
|
||||
var.openai_api_key_secret_id
|
||||
]))
|
||||
|
||||
@@ -179,8 +179,11 @@ module "bot_api_service" {
|
||||
TELEGRAM_WEBHOOK_SECRET = var.telegram_webhook_secret_id
|
||||
SCHEDULER_SHARED_SECRET = var.scheduler_shared_secret_id
|
||||
},
|
||||
var.database_url_secret_id == null ? {} : {
|
||||
DATABASE_URL = var.database_url_secret_id
|
||||
var.app_database_url_secret_id == null ? {} : {
|
||||
APP_DATABASE_URL = var.app_database_url_secret_id
|
||||
},
|
||||
var.worker_database_url_secret_id == null ? {} : {
|
||||
WORKER_DATABASE_URL = var.worker_database_url_secret_id
|
||||
},
|
||||
var.telegram_bot_token_secret_id == null ? {} : {
|
||||
TELEGRAM_BOT_TOKEN = var.telegram_bot_token_secret_id
|
||||
|
||||
@@ -8,7 +8,8 @@ artifact_repository_id = "household-bot"
|
||||
bot_api_image = "europe-west1-docker.pkg.dev/my-gcp-project/household-bot/bot:latest"
|
||||
mini_app_image = "europe-west1-docker.pkg.dev/my-gcp-project/household-bot/miniapp:latest"
|
||||
|
||||
database_url_secret_id = "database-url"
|
||||
app_database_url_secret_id = "app-database-url"
|
||||
worker_database_url_secret_id = "worker-database-url"
|
||||
telegram_bot_token_secret_id = "telegram-bot-token"
|
||||
openai_api_key_secret_id = "openai-api-key"
|
||||
bot_purchase_parser_model = "gpt-4o-mini"
|
||||
|
||||
@@ -57,7 +57,21 @@ variable "scheduler_shared_secret_id" {
|
||||
}
|
||||
|
||||
variable "database_url_secret_id" {
|
||||
description = "Optional Secret Manager ID for DATABASE_URL"
|
||||
description = "Optional Secret Manager ID for owner-only DATABASE_URL used outside runtime deploys"
|
||||
type = string
|
||||
default = null
|
||||
nullable = true
|
||||
}
|
||||
|
||||
variable "app_database_url_secret_id" {
|
||||
description = "Optional Secret Manager ID for APP_DATABASE_URL"
|
||||
type = string
|
||||
default = null
|
||||
nullable = true
|
||||
}
|
||||
|
||||
variable "worker_database_url_secret_id" {
|
||||
description = "Optional Secret Manager ID for WORKER_DATABASE_URL"
|
||||
type = string
|
||||
default = null
|
||||
nullable = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { and, desc, eq, gte, inArray, isNotNull, isNull, lt, lte, or, sql } from 'drizzle-orm'
|
||||
|
||||
import { createDbClient, schema } from '@household/db'
|
||||
import { createDbClient, type DbSessionContext, schema } from '@household/db'
|
||||
import type { FinanceRepository } from '@household/ports'
|
||||
import {
|
||||
instantFromDatabaseValue,
|
||||
@@ -22,14 +22,22 @@ function toCurrencyCode(raw: string): CurrencyCode {
|
||||
|
||||
export function createDbFinanceRepository(
|
||||
databaseUrl: string,
|
||||
householdId: string
|
||||
householdId: string,
|
||||
options: {
|
||||
sessionContext?: DbSessionContext
|
||||
} = {}
|
||||
): {
|
||||
repository: FinanceRepository
|
||||
close: () => Promise<void>
|
||||
} {
|
||||
const { db, queryClient } = createDbClient(databaseUrl, {
|
||||
max: 5,
|
||||
prepare: false
|
||||
prepare: false,
|
||||
...(options.sessionContext
|
||||
? {
|
||||
sessionContext: options.sessionContext
|
||||
}
|
||||
: {})
|
||||
})
|
||||
|
||||
async function loadPurchaseParticipants(purchaseIds: readonly string[]): Promise<
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { and, asc, eq, sql } from 'drizzle-orm'
|
||||
|
||||
import { createDbClient, schema } from '@household/db'
|
||||
import { createDbClient, type DbSessionContext, schema } from '@household/db'
|
||||
import {
|
||||
instantToDate,
|
||||
normalizeSupportedLocale,
|
||||
@@ -334,13 +334,23 @@ function utilityCategorySlug(name: string): string {
|
||||
.slice(0, 48)
|
||||
}
|
||||
|
||||
export function createDbHouseholdConfigurationRepository(databaseUrl: string): {
|
||||
export function createDbHouseholdConfigurationRepository(
|
||||
databaseUrl: string,
|
||||
options: {
|
||||
sessionContext?: DbSessionContext
|
||||
} = {}
|
||||
): {
|
||||
repository: HouseholdConfigurationRepository
|
||||
close: () => Promise<void>
|
||||
} {
|
||||
const { db, queryClient } = createDbClient(databaseUrl, {
|
||||
max: 5,
|
||||
prepare: false
|
||||
prepare: false,
|
||||
...(options.sessionContext
|
||||
? {
|
||||
sessionContext: options.sessionContext
|
||||
}
|
||||
: {})
|
||||
})
|
||||
|
||||
const defaultUtilityCategories = [
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"0019_faithful_madame_masque.sql": "38711341799b04a7c47fcc64fd19faf5b26e6f183d6a4c01d492b9929cd63641",
|
||||
"0020_natural_mauler.sql": "a80a4a0196a3b4931040850089346d1bc99b34a5afca77d6d62478ee4b8902c1",
|
||||
"0020_silver_payments.sql": "9686235c75453f1eaa016f2f4ab7fce8fe964c76a4e3515987a2b9f90bd7b1ad",
|
||||
"0021_sharp_payer.sql": "973596e154382984ba7769979ea58298b6d93c5139540854be01e8b283ddb4f1"
|
||||
"0021_sharp_payer.sql": "973596e154382984ba7769979ea58298b6d93c5139540854be01e8b283ddb4f1",
|
||||
"0022_harden_rls.sql": "d2e24b3e5b7ec7ef9da7e90c0ddf0e408764f3578af3872f76b9b3198ffbd70e"
|
||||
}
|
||||
}
|
||||
|
||||
1225
packages/db/drizzle/0022_harden_rls.sql
Normal file
1225
packages/db/drizzle/0022_harden_rls.sql
Normal file
File diff suppressed because it is too large
Load Diff
@@ -155,6 +155,13 @@
|
||||
"when": 1774200000000,
|
||||
"tag": "0021_sharp_payer",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 22,
|
||||
"version": "7",
|
||||
"when": 1774204831000,
|
||||
"tag": "0022_harden_rls",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,9 +1,34 @@
|
||||
import postgres from 'postgres'
|
||||
import { drizzle } from 'drizzle-orm/postgres-js'
|
||||
|
||||
export interface DbSessionContext {
|
||||
telegramUserId?: string
|
||||
householdId?: string
|
||||
memberId?: string
|
||||
isAdmin?: boolean
|
||||
isWorker?: boolean
|
||||
}
|
||||
|
||||
export interface DbClientOptions {
|
||||
max?: number
|
||||
prepare?: boolean
|
||||
sessionContext?: DbSessionContext
|
||||
}
|
||||
|
||||
function quoteRuntimeOptionValue(value: string): string {
|
||||
return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'`
|
||||
}
|
||||
|
||||
function appendRuntimeOption(
|
||||
options: string[],
|
||||
key: string,
|
||||
value: string | boolean | undefined
|
||||
): void {
|
||||
if (value === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
options.push(`-c ${key}=${quoteRuntimeOptionValue(String(value))}`)
|
||||
}
|
||||
|
||||
export function createDbClient(databaseUrl: string, options: DbClientOptions = {}) {
|
||||
@@ -17,7 +42,17 @@ export function createDbClient(databaseUrl: string, options: DbClientOptions = {
|
||||
url.searchParams.delete('options')
|
||||
|
||||
// Set search_path via options parameter (required for PgBouncer compatibility)
|
||||
url.searchParams.set('options', `-c search_path=${dbSchema}`)
|
||||
const runtimeOptions = [`-c search_path=${dbSchema}`]
|
||||
appendRuntimeOption(
|
||||
runtimeOptions,
|
||||
'app.telegram_user_id',
|
||||
options.sessionContext?.telegramUserId
|
||||
)
|
||||
appendRuntimeOption(runtimeOptions, 'app.household_id', options.sessionContext?.householdId)
|
||||
appendRuntimeOption(runtimeOptions, 'app.member_id', options.sessionContext?.memberId)
|
||||
appendRuntimeOption(runtimeOptions, 'app.is_admin', options.sessionContext?.isAdmin)
|
||||
appendRuntimeOption(runtimeOptions, 'app.is_worker', options.sessionContext?.isWorker)
|
||||
url.searchParams.set('options', runtimeOptions.join(' '))
|
||||
|
||||
const cleanUrl = url.toString()
|
||||
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export { createDbClient } from './client'
|
||||
export type { DbSessionContext } from './client'
|
||||
export * as schema from './schema'
|
||||
|
||||
Reference in New Issue
Block a user